From ac39ecdeb17ca7c8139ef39fa750dc7fa48596a9 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Fri, 25 Nov 2022 10:55:12 +0100 Subject: [PATCH 01/31] start developing water use module with water quality --- ..._rhine-meuse_water_quality_development.ini | 469 ++++++++++++++++++ model/landSurface.py | 12 + 2 files changed, 481 insertions(+) create mode 100644 config/setup_05min_rhine-meuse_water_quality_development.ini diff --git a/config/setup_05min_rhine-meuse_water_quality_development.ini b/config/setup_05min_rhine-meuse_water_quality_development.ini new file mode 100644 index 000000000..b609ce09f --- /dev/null +++ b/config/setup_05min_rhine-meuse_water_quality_development.ini @@ -0,0 +1,469 @@ +[globalOptions] + +# Please set the pcrglobwb output directory (outputDir) in an absolute path. +# - Please make sure that you have access to it. +outputDir = /scratch/sutan101/test_water_use_with_quality/ + +# Please set the clone map file (cloneMap), which defines the spatial resolution and extent of your study area. +# - Please make sure that the file is stored locally in your computing machine. +# - The file must be in the pcraster format. +cloneMap = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/global_05min/cloneMaps/RhineMeuse05min.clone.map +#~ cloneMap = /quanta1/home/hydrowld/data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta/pcrglobwb2_input/global_05min/cloneMaps/clone_global_05min.map + + +# Set the input directory map in an absolute path. The input forcing and parameter directories and files will be relative to this. +# - The following is an example using files from the opendap server. +#~ inputDir = https://opendap.4tu.nl/thredds/dodsC/data2/pcrglobwb/version_2019_11_beta/pcrglobwb2_input/ +#~ # - The following is an example using input files stored locally in your computing machine. +#~ inputDir = /quanta1/home/hydrowld/data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta/pcrglobwb2_input/ +# - on velocity +inputDir = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/ + +# The area/landmask of interest: +# If None, area/landmask is limited for cells with ldd value. +#~ landmask = None +landmask = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/global_05min/cloneMaps/RhineMeuse05min.landmask.map + + +# netcdf attributes for output files: +institution = Department of Physical Geography, Utrecht University +title = PCR-GLOBWB 2 output, with human factors (non-natural) +description = PCR-GLOBWB run with human factors (non-natural) at 5 arcmin resolution + + +startTime = 2000-01-01 +endTime = 2010-12-31 +# Format: YYYY-MM-DD ; The model runs on daily time step. + + +# spinning up options: +maxSpinUpsInYears = 0 +minConvForSoilSto = 0.0 +minConvForGwatSto = 0.0 +minConvForChanSto = 0.0 +minConvForTotlSto = 0.0 + + +[meteoOptions] + +# Set the forcing temperature and precipitation files (relative to inputDir) +precipitationNC = global_30min/meteo/forcing/daily_precipitation_cru_era-interim_1979_to_2010.nc +temperatureNC = global_30min/meteo/forcing/daily_temperature_cru_era-interim_1979_to_2010.nc + +# Method to calculate referencePotETP (reference potential evaporation+transpiration) +# options are "Hamon" and "Input" ; If "Input", the netcdf input file must be given: +referenceETPotMethod = Input +refETPotFileNC = global_30min/meteo/forcing/daily_referencePotET_cru_era-interim_1979_to_2010.nc + + +[meteoDownscalingOptions] +# This section is for a 5 arcmin run, for downscaling meteorological forcing at 30 arcmin to 5 arcmin. + +downscalePrecipitation = False +downscaleTemperature = True +downscaleReferenceETPot = False + +# downscaling (based on the digital elevation model): +# The downscaling will be performed by providing the "cellIds" (meteoDownscaleIds) of lower resolution cells. +meteoDownscaleIds = global_05min/meteo/downscaling_from_30min/uniqueIds_30min.nc +highResolutionDEM = global_05min/meteo/downscaling_from_30min/gtopo05min.nc + +# lapse rates: +temperLapseRateNC = global_05min/meteo/downscaling_from_30min/temperature_slope.nc +precipLapseRateNC = global_05min/meteo/downscaling_from_30min/precipitation_slope.nc + +# downscaling criteria (TODO: remove these): +temperatCorrelNC = global_05min/meteo/downscaling_from_30min/temperature_correl.nc +precipitCorrelNC = global_05min/meteo/downscaling_from_30min/precipitation_correl.nc + +# windows length (unit: arc-degree) for smoothing/averaging forcing data (not recommended): +smoothingWindowsLength = 0 + + +[landSurfaceOptions] + +debugWaterBalance = True + +numberOfUpperSoilLayers = 2 + +# soil and parameters +# - they are used for all land cover types, unless they are are defined in certain land cover type options +# (e.g. different/various soil types for agriculture areas) +topographyNC = global_05min/landSurface/topography/topography_parameters_5_arcmin_october_2015.nc +soilPropertiesNC = global_05min/landSurface/soil/soilProperties5ArcMin.nc + + +includeIrrigation = True + +# netcdf time series for historical expansion of irrigation areas (unit: hectares). +# Note: The resolution of this map must be consisten with the resolution of cellArea. +historicalIrrigationArea = global_05min/waterUse/irrigation/irrigated_areas/irrigationArea05ArcMin.nc + +# a pcraster map/value defining irrigation efficiency (dimensionless) - optional +irrigationEfficiency = global_30min/waterUse/irrigation/irrigation_efficiency/efficiency.nc + +considerWaterQuality = True +inputFileBOD = /scratch/sutan101/data/dynqual/organic_monthlyAvg_1980_2019_without_time_bnds.nc +# threshold for BOD unit: mg/L +thresholdBODForIrrigation = 15. + +includeDomesticWaterDemand = True +includeIndustryWaterDemand = True +includeLivestockWaterDemand = True + +# domestic, industrial and livestock water demand data (unit must be in m.day-1) +domesticWaterDemandFile = global_05min/waterUse/waterDemand/domestic/domestic_water_demand_version_april_2015.nc +industryWaterDemandFile = global_05min/waterUse/waterDemand/industry/industry_water_demand_version_april_2015.nc +livestockWaterDemandFile = global_05min/waterUse/waterDemand/livestock/livestock_water_demand_version_april_2015.nc + + +# desalination water supply (maximum/potential/capacity) +desalinationWater = global_05min/waterUse/desalination/desalination_water_version_april_2015.nc + + +# zone IDs (scale) at which allocations of groundwater and surface water (as well as desalinated water) are performed +allocationSegmentsForGroundSurfaceWater = global_05min/waterUse/abstraction_zones/abstraction_zones_60min_05min.nc + + +# pcraster maps defining the partitioning of groundwater - surface water source +# +# - predefined surface water - groundwater partitioning for irrigation demand (e.g. based on Siebert, Global Map of Irrigation Areas version 5) +irrigationSurfaceWaterAbstractionFractionData = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_SWFRAC.nc +# -- quality map +irrigationSurfaceWaterAbstractionFractionDataQuality = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_QUAL.nc +# +# - threshold values defining the preference for surface water source for irrigation purpose +# -- treshold to maximize surface water irrigation use (cells with irrSurfaceWaterAbstractionFraction above this will prioritize irrigation surface water use) +treshold_to_maximize_irrigation_surface_water = 0.50 +# -- treshold to minimize fossil water withdrawal for irrigation (cells with irrSurfaceWaterAbstractionFraction below this have no fossil withdrawal for irrigation) +treshold_to_minimize_fossil_groundwater_irrigation = 0.70 +# +# - predefined surface water - groundwater partitioning for non irrigation demand (e.g. based on McDonald, 2014) +maximumNonIrrigationSurfaceWaterAbstractionFractionData = global_30min/waterUse/source_partitioning/surface_water_fraction_for_non_irrigation/max_city_sw_fraction.nc + + +[forestOptions] + +name = forest +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 + +cropCoefficientNC = global_05min/landSurface/landCover/naturalTall/cropCoefficientForest.nc +interceptCapNC = global_05min/landSurface/landCover/naturalTall/interceptCapInputForest.nc +coverFractionNC = global_05min/landSurface/landCover/naturalTall/coverFractionInputForest.nc + +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following pcraster maps: +fracVegCover = global_05min/landSurface/landCover/naturalTall/vegf_tall.nc +minSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/minf_tall_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/maxf_tall.nc +rootFraction1 = global_05min/landSurface/landCover/naturalTall/rfrac1_tall.nc +rootFraction2 = global_05min/landSurface/landCover/naturalTall/rfrac2_tall.nc +maxRootDepth = 1.0 +# Note: The maxRootDepth is not used for non irrigated land cover type. +# +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_forest_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_forest_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_forest_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_forest_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_forest_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_forest_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_forest_1999-12-31.nc + + +[grasslandOptions] + +name = grassland +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 + +cropCoefficientNC = global_05min/landSurface/landCover/naturalShort/cropCoefficientGrassland.nc +interceptCapNC = global_05min/landSurface/landCover/naturalShort/interceptCapInputGrassland.nc +coverFractionNC = global_05min/landSurface/landCover/naturalShort/coverFractionInputGrassland.nc + +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/naturalShort/vegf_short.nc +minSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/minf_short_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/maxf_short.nc +rootFraction1 = global_05min/landSurface/landCover/naturalShort/rfrac1_short.nc +rootFraction2 = global_05min/landSurface/landCover/naturalShort/rfrac2_short.nc +maxRootDepth = 0.5 +# Note: The maxRootDepth is not used for non irrigated land cover type. +# +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_grassland_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_grassland_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_grassland_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_grassland_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_grassland_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_grassland_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_grassland_1999-12-31.nc + + +[irrPaddyOptions] + +name = irrPaddy +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 +# +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/irrPaddy/fractionPaddy.nc +minSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/minf_paddy_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/maxf_paddy.nc +rootFraction1 = global_30min/landSurface/landCover/irrPaddy/rfrac1_paddy.nc +rootFraction2 = global_30min/landSurface/landCover/irrPaddy/rfrac2_paddy.nc +maxRootDepth = 0.5 +# +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. +# +# other paramater values +minTopWaterLayer = 0.05 +minCropKC = 0.2 +cropDeplFactor = 0.2 +minInterceptCap = 0.0002 + +cropCoefficientNC = global_30min/landSurface/landCover/irrPaddy/Global_CropCoefficientKc-IrrPaddy_30min.nc + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrPaddy_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrPaddy_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrPaddy_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrPaddy_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrPaddy_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrPaddy_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrPaddy_1999-12-31.nc + + +[irrNonPaddyOptions] + +name = irrNonPaddy +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 +# +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/irrNonPaddy/fractionNonPaddy.nc +minSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/minf_nonpaddy_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/maxf_nonpaddy.nc +rootFraction1 = global_30min/landSurface/landCover/irrNonPaddy/rfrac1_nonpaddy.nc +rootFraction2 = global_30min/landSurface/landCover/irrNonPaddy/rfrac2_nonpaddy.nc +maxRootDepth = 1.0 +# +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. +# +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 +cropDeplFactor = 0.5 +minInterceptCap = 0.0002 + +cropCoefficientNC = global_30min/landSurface/landCover/irrNonPaddy/Global_CropCoefficientKc-IrrNonPaddy_30min.nc + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrNonPaddy_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrNonPaddy_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrNonPaddy_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrNonPaddy_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrNonPaddy_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrNonPaddy_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrNonPaddy_1999-12-31.nc + + + + +[groundwaterOptions] + +debugWaterBalance = True + +groundwaterPropertiesNC = global_05min/groundwater/properties/groundwaterProperties5ArcMin.nc +# The file will containspecificYield (m3.m-3), kSatAquifer (m.day-1), recessionCoeff (day-1) +# +# - minimum value for groundwater recession coefficient (day-1) +minRecessionCoeff = 1.0e-4 + +# some options for constraining groundwater abstraction +limitFossilGroundWaterAbstraction = True +estimateOfRenewableGroundwaterCapacity = 0.0 +estimateOfTotalGroundwaterThickness = global_05min/groundwater/aquifer_thickness_estimate/thickness_05min.nc +# minimum and maximum total groundwater thickness +minimumTotalGroundwaterThickness = 100. +maximumTotalGroundwaterThickness = None + +# annual pumping capacity for each region (unit: billion cubic meter per year), should be given in a netcdf file +pumpingCapacityNC = global_30min/waterUse/groundwater_pumping_capacity/regional_abstraction_limit.nc + +# initial conditions: +storGroundwaterIni = global_05min/initialConditions/non-natural/1999/storGroundwater_1999-12-31.nc +storGroundwaterFossilIni = global_05min/initialConditions/non-natural/1999/storGroundwaterFossil_1999-12-31.nc +# +# additional initial conditions for pumping behaviors +avgNonFossilGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationLong_1999-12-31.nc +avgNonFossilGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationShort_1999-12-31.nc +avgTotalGroundwaterAbstractionIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAbstraction_1999-12-31.nc +avgTotalGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationLong_1999-12-31.nc +avgTotalGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationShort_1999-12-31.nc +# +# additional initial conditions (needed only for MODFLOW run) +relativeGroundwaterHeadIni = global_05min/initialConditions/non-natural/1999/relativeGroundwaterHead_1999-12-31.nc +baseflowIni = global_05min/initialConditions/non-natural/1999/baseflow_1999-12-31.nc + +# zonal IDs (scale) at which zonal allocation of groundwater is performed +allocationSegmentsForGroundwater = global_05min/waterUse/abstraction_zones/abstraction_zones_30min_05min.nc + + + +[routingOptions] + +debugWaterBalance = True + +# drainage direction map +lddMap = global_05min/routing/ldd_and_cell_area/lddsound_05min.nc + +# cell area (unit: m2) +cellAreaMap = global_05min/routing/ldd_and_cell_area/cellsize05min.correct.nc + +# routing method: +routingMethod = accuTravelTime +#~ routingMethod = kinematicWave + +# manning coefficient +manningsN = 0.04 + +# Option for flood plain simulation +dynamicFloodPlain = True + +# manning coefficient for floodplain +floodplainManningsN = 0.07 + + +# channel gradient +gradient = global_05min/routing/channel_properties/channel_gradient.nc + +# constant channel depth +constantChannelDepth = global_05min/routing/channel_properties/bankfull_depth.nc + +# constant channel width (optional) +constantChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc + +# minimum channel width (optional) +minimumChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc + +# channel properties for flooding +bankfullCapacity = None +# - If None, it will be estimated from (bankfull) channel depth (m) and width (m) + + +# files for relative elevation (above minimum dem) +relativeElevationFiles = global_05min/routing/channel_properties/dzRel%04d.nc +relativeElevationLevels = 0.0, 0.01, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00 + + +# composite crop factors for WaterBodies: +cropCoefficientWaterNC = global_30min/routing/kc_surface_water/cropCoefficientForOpenWater.nc +minCropWaterKC = 1.00 + + +# lake and reservoir parameters +waterBodyInputNC = global_05min/routing/surface_water_bodies/waterBodies5ArcMin.nc +onlyNaturalWaterBodies = False + + +# initial conditions: +waterBodyStorageIni = global_05min/initialConditions/non-natural/1999/waterBodyStorage_1999-12-31.nc +channelStorageIni = global_05min/initialConditions/non-natural/1999/channelStorage_1999-12-31.nc +readAvlChannelStorageIni = global_05min/initialConditions/non-natural/1999/readAvlChannelStorage_1999-12-31.nc +avgDischargeLongIni = global_05min/initialConditions/non-natural/1999/avgDischargeLong_1999-12-31.nc +avgDischargeShortIni = global_05min/initialConditions/non-natural/1999/avgDischargeShort_1999-12-31.nc +m2tDischargeLongIni = global_05min/initialConditions/non-natural/1999/m2tDischargeLong_1999-12-31.nc +avgBaseflowLongIni = global_05min/initialConditions/non-natural/1999/avgBaseflowLong_1999-12-31.nc +riverbedExchangeIni = global_05min/initialConditions/non-natural/1999/riverbedExchange_1999-12-31.nc +# +# initial condition of sub-time step discharge (needed for estimating number of time steps in kinematic wave methods) +subDischargeIni = global_05min/initialConditions/non-natural/1999/subDischarge_1999-12-31.nc +# +avgLakeReservoirInflowShortIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirInflowShort_1999-12-31.nc +avgLakeReservoirOutflowLongIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirOutflowLong_1999-12-31.nc +# +# number of days (timesteps) that have been performed for spinning up initial conditions in the routing module (i.e. channelStorageIni, avgDischargeLongIni, avgDischargeShortIni, etc.) +timestepsToAvgDischargeIni = global_05min/initialConditions/non-natural/1999/timestepsToAvgDischarge_1999-12-31.nc +# Note that: +# - maximum number of days (timesteps) to calculate long term average flow values (default: 5 years = 5 * 365 days = 1825) +# - maximum number of days (timesteps) to calculate short term average values (default: 1 month = 1 * 30 days = 30) + + + + +[reportingOptions] + +# output files that will be written in the disk in netcdf files: +# - daily resolution +outDailyTotNC = discharge,totalRunoff,gwRecharge,totalGroundwaterAbstraction,surfaceWaterStorage +# - monthly resolution +outMonthTotNC = actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,totLandSurfaceActuaET,totalLandSurfacePotET,waterBodyActEvaporation,waterBodyPotEvaporation +outMonthAvgNC = discharge,temperature,dynamicFracWat,surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalActiveStorageThickness,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage +outMonthEndNC = storGroundwater,storGroundwaterFossil,waterBodyStorage,channelStorage,totalWaterStorageThickness,totalActiveStorageThickness +# - annual resolution +outAnnuaTotNC = totalEvaporation,precipitation,gwRecharge,totalRunoff,baseflow,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,totalGroundwaterAbstraction,totalAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,runoff,actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,irrigationWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,precipitation_at_irrigation,netLqWaterToSoil_at_irrigation,evaporation_from_irrigation,transpiration_from_irrigation,referencePotET +outAnnuaAvgNC = temperature,discharge,surfaceWaterStorage,waterBodyStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage,fractionWaterBodyEvaporation,fractionTotalEvaporation,fracSurfaceWaterAllocation,fracDesalinatedWaterAllocation,gwRecharge +outAnnuaEndNC = surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness +# - monthly and annual maxima +outMonthMaxNC = channelStorage,dynamicFracWat,floodVolume,floodDepth,surfaceWaterLevel,discharge,totalRunoff +outAnnuaMaxNC = None + +# netcdf format and zlib setup +formatNetCDF = NETCDF4 +zlib = True + + + diff --git a/model/landSurface.py b/model/landSurface.py index 7f59fdbae..fb27f6988 100644 --- a/model/landSurface.py +++ b/model/landSurface.py @@ -379,6 +379,18 @@ def __init__(self,iniItems,landmask,initialState=None): self.landCoverObj[coverType].irrTypeFracOverIrr = vos.getValDivZero(self.landCoverObj[coverType].fracVegCover,\ totalIrrAreaFrac, vos.smallNumber) + + # option to consider water quality + self.consider_water_quality = False + if "considerWaterQuality" is in list(iniItems.landSurfaceOptions.keys()) and iniItems.landSurfaceOptions["considerWaterQuality"] == "True": + logger.info('Water use in this model run considers water quality.') + self.consider_water_quality = True + # - input files and values (e.g. thresholds) that are related to water quality + self.inputFileBOD = iniItems.landSurfaceOptions["inputFileBOD"] + self.thresholdBODForIrrigation = iniItems.landSurfaceOptions["thresholdBODForIrrigation"] + + + # get the initial conditions (for every land cover type) self.getInitialConditions(iniItems, initialState) From 0215914a8a70316947b2499a1f9c0c88ebc59061 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Fri, 25 Nov 2022 12:17:55 +0100 Subject: [PATCH 02/31] first development --- model/landCover.py | 15 +++++++++++++-- model/landSurface.py | 21 ++++++++++++++++++--- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 23fbbc460..990e624ac 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -936,8 +936,15 @@ def updateLC(self,meteo,groundwater,routing,\ allocSegments,\ desalinationWaterUse,\ groundwater_pumping_region_ids,\ - regionalAnnualGroundwaterAbstractionLimit): + regionalAnnualGroundwaterAbstractionLimit, + consider_water_quality = False, + inputBOD = 0.0,\ + thresholdBODForIrrigation = 0.0): + self.consider_water_quality = consider_water_quality + self.inputBOD = inputBOD + self.thresholdBODForIrrigation = thresholdBODForIrrigation + # get land cover parameters at the first day of the year or the first day of the simulation if self.noAnnualChangesInLandCoverParameter == False and\ (currTimeStep.timeStepPCR == 1 or currTimeStep.doy == 1): @@ -1843,6 +1850,10 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # if surface water abstraction as the first priority if self.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination # + available_surface_water_volume = pcr.max(0.00, routing.readAvlChannelStorage) + if self.consider_water_quality == True: + available_surface_water_volume = pcr.ifthenelse(self.inputBOD < self.thresholdBODForIrrigation, available_surface_water_volume, 0.0) + if self.usingAllocSegments: # using zone/segment at which supply network is defined # logger.debug("Allocation of surface water abstraction.") @@ -1850,7 +1861,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ vos.waterAbstractionAndAllocation( water_demand_volume = surface_water_demand*routing.cellArea,\ - available_water_volume = pcr.max(0.00, routing.readAvlChannelStorage),\ + available_water_volume = available_surface_water_volume,\ allocation_zones = allocSegments,\ zone_area = self.segmentArea,\ high_volume_treshold = None,\ diff --git a/model/landSurface.py b/model/landSurface.py index fb27f6988..a8fcf1983 100644 --- a/model/landSurface.py +++ b/model/landSurface.py @@ -382,12 +382,12 @@ def __init__(self,iniItems,landmask,initialState=None): # option to consider water quality self.consider_water_quality = False - if "considerWaterQuality" is in list(iniItems.landSurfaceOptions.keys()) and iniItems.landSurfaceOptions["considerWaterQuality"] == "True": + if ("considerWaterQuality" in list(iniItems.landSurfaceOptions.keys()) and iniItems.landSurfaceOptions["considerWaterQuality"] == "True"): logger.info('Water use in this model run considers water quality.') self.consider_water_quality = True # - input files and values (e.g. thresholds) that are related to water quality self.inputFileBOD = iniItems.landSurfaceOptions["inputFileBOD"] - self.thresholdBODForIrrigation = iniItems.landSurfaceOptions["thresholdBODForIrrigation"] + self.thresholdBODForIrrigation = float(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) @@ -1243,6 +1243,17 @@ def scaleDynamicIrrigation(self,yearInInteger): def update(self,meteo,groundwater,routing,currTimeStep): + # updating any information related to water quality + if self.consider_water_quality == True: + + # - read BOD for every time step + self.inputBOD = vos.netcdf2PCRobjClone(ncFile = self.inputFileBOD,\ + varName = "automatic" , \ + dateInput = currTimeStep.fulldate,\ + useDoy = "daily", + cloneMapFileName = self.cloneMap) + + # updating regional groundwater abstraction limit (at the begining of the year or at the beginning of simulation) if groundwater.limitRegionalAnnualGroundwaterAbstraction: @@ -1445,7 +1456,11 @@ def update(self,meteo,groundwater,routing,currTimeStep): currTimeStep,\ self.allocSegments,\ self.desalinationWaterUse,\ - self.groundwater_pumping_region_ids,self.regionalAnnualGroundwaterAbstractionLimit) + self.groundwater_pumping_region_ids,self.regionalAnnualGroundwaterAbstractionLimit,\ + self.consider_water_quality, + self.inputBOD, + self.thresholdBODForIrrigation) + # TODO: Please organize how we will deal with water quality problems (e.g. self.inputBOD) # first, we set all aggregated values/variables to zero: for var in self.aggrVars: vars(self)[var] = pcr.scalar(0.0) From 8494906b0abb36e6465a89bc519484898eb92d77 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Wed, 30 Nov 2022 12:30:43 +0100 Subject: [PATCH 03/31] note for water allocation --- notes_with_gabriel.txt | 43 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 notes_with_gabriel.txt diff --git a/notes_with_gabriel.txt b/notes_with_gabriel.txt new file mode 100644 index 000000000..e7197d4a4 --- /dev/null +++ b/notes_with_gabriel.txt @@ -0,0 +1,43 @@ +principle: +- keep proportional allocation based on demand magnitude (this is the principle of PCR-GLOBWB 2) +- we start allocating from the pixels that have the best quality () + +how many loops? +- water quality constituents, e.g. TOD, FC, etc. +- water quality thresholds for every constituent and for every sector + +input: +- available_surface_water_without_qual (without considering quality) +- water demand for every sector, e.g. irrigation, livestock, domestic and industry +- note, industry will be splitted into manufacturing and thermo-electric +- water quality concetration for every constituent +- water quality thresholds + + +step 0: + +available_surface_water_with_qual = available_surface_water_without_qual +water_use_irrigation = 0.0 + +for every consti (TOD, FC, etc) + +for threshold_consti_sector + +available_surface_water_with_qual_for_this_conti_sector = available_surface_water_with_qual +available_surface_water_with_qual_for_this_conti_sector = ifthenelse(wq_consti < threshold_consti_sector, available_surface_water_with_qual_for_this_conti_sector, 0.0) + +- start allocating available_surface_water_with_qual_for_this_conti_sector for every sector (proportional based on demand magnitudes); this will consider zones and etc +- this will be called water_use_each_sector (irrigation, livestock, industry and domestic) +- we should track this for every sector +-- water_use_irrigation = water_use_irrigation + water_use_irrigation + +for every sector + +available_surface_water_with_qual = available_surface_water_with_qual - water_use_each_sector + +end loop for every sector + +end loop for threshold_consti_sector + +end loop for every_sector + From bc0bf6f1032faba8eb222053c8c36f4a109a1a31 Mon Sep 17 00:00:00 2001 From: gcardenas1891 <71383566+gcardenas1891@users.noreply.github.com> Date: Thu, 1 Dec 2022 16:04:46 +0100 Subject: [PATCH 04/31] Create notes_with_gabriel_2 Tidying up ideas written in original document ('notes_with_gabriel.txt') --- notes_with_gabriel_2 | 46 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 notes_with_gabriel_2 diff --git a/notes_with_gabriel_2 b/notes_with_gabriel_2 new file mode 100644 index 000000000..41b4334ef --- /dev/null +++ b/notes_with_gabriel_2 @@ -0,0 +1,46 @@ +# Principle +# - Keep proportional allocation of water withdrawn based on demand magnitude (current principle of PCR-GLOBWB 2) +# - Water allocation starts from the pixels with the best water quality + +# How many loops? +# - Water quality constituents (i.e., Temp, BOD, TDS, FC, [DO, N, P]) +# - Water quality thresholds for each constituent and for each sector + +# Input +# - available_surface_water (without considering quality) +# - water_demand_sector (e.g., irrigation, livestock, domestic and industry) -> (industry will be splitted into manufacturing and thermoelectric) +# - water_quality_concetration_constituent [Temp, BOD, TDS, FC] +# - water_quality_thresholds +water_quality_thresholds = { +'waterTemperature': ('irrigation':'' ,'domestic':'' ,'thermoelectric':'' ,'manufacturing':'' ,'livestock':''), # Water temperature (oC) +'organic': ('irrigation':15 ,'domestic':5 ,'thermoelectric':30 ,'manufacturing':30 ,'livestock':''), # Biochemical oxigen demand (mg/l) +'salinity': ('irrigation':450,'domestic':600,'thermoelectric':7000,'manufacturing':7000,'livestock':''), # Total disolved solids (mg/l) +'pathogen': ('irrigation':'' ,'domestic':'' ,'thermoelectric':'' ,'manufacturing':'' ,'livestock':''), # Fecal coliforms (cfu100/ml) +} + +water_quality_dict = {'TMP':'waterTemperature','BOD':'organic','TDS':'salinity':,'FC':'pathogen'} + +# Step 0 +available_surface_water_with_qual = available_surface_water + +# Looping for every constituent +for consti in ['TMP','BOD','TDS','FC']: + + # Defining datasets with water quality concentrations and constituen's name + water_quality_concetration_consti = eval(f'self.input{consti}') + constituent = water_quality_dict[consti] + +# Looping for every sector + for sector in ['domestic','manufacturing','livestock','thermoelectric','irrigation']: # Looping by sectors (as a list) is sort of a prioritization rule, the only difference is that water demands were distributed proportionally, diluting the idea + + # Defining water quality thresholds + threshold_consti_sector = water_quality_thresholds[constituent][sector] + + # Defining actual water available depending on constituent threshold + available_surface_water_with_qual_conti_sector = ifthenelse(water_quality_concetration_consti < threshold_consti_sector, available_surface_water_with_qual, 0.) + + # Water allocation based on proportional water demand distribution, zonation scheme and further considerations (groundwater, desalinated water) + water_withdrawal_sector = f(current PCR-GLOBWB2 water allocation scheme) + + # Calculting remaining water available in + available_surface_water_with_qual = available_surface_water_with_qual - water_withdrawal_sector From f60cf3f06132567d0b58a2370f732630c9916217 Mon Sep 17 00:00:00 2001 From: gcardenas1891 <71383566+gcardenas1891@users.noreply.github.com> Date: Fri, 2 Dec 2022 10:58:49 +0100 Subject: [PATCH 05/31] Update notes_with_gabriel_2 adding tracking of water demand satisficed and remaining adding step to order sectors based on quality, from less stringent to more stringent --- notes_with_gabriel_2 | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/notes_with_gabriel_2 b/notes_with_gabriel_2 index 41b4334ef..3e1edc50e 100644 --- a/notes_with_gabriel_2 +++ b/notes_with_gabriel_2 @@ -12,16 +12,19 @@ # - water_quality_concetration_constituent [Temp, BOD, TDS, FC] # - water_quality_thresholds water_quality_thresholds = { -'waterTemperature': ('irrigation':'' ,'domestic':'' ,'thermoelectric':'' ,'manufacturing':'' ,'livestock':''), # Water temperature (oC) -'organic': ('irrigation':15 ,'domestic':5 ,'thermoelectric':30 ,'manufacturing':30 ,'livestock':''), # Biochemical oxigen demand (mg/l) -'salinity': ('irrigation':450,'domestic':600,'thermoelectric':7000,'manufacturing':7000,'livestock':''), # Total disolved solids (mg/l) -'pathogen': ('irrigation':'' ,'domestic':'' ,'thermoelectric':'' ,'manufacturing':'' ,'livestock':''), # Fecal coliforms (cfu100/ml) +'waterTemperature': ('irrigation':1000,'domestic':1000,'thermoelectric':1000,'manufacturing':1000,'livestock':1000), # Water temperature (oC) +'organic': ('irrigation':15 ,'domestic':5 ,'thermoelectric':30 ,'manufacturing':30 ,'livestock':1000), # Biochemical oxigen demand (mg/l) +'salinity': ('irrigation':450 ,'domestic':600 ,'thermoelectric':7000,'manufacturing':7000,'livestock':100000), # Total disolved solids (mg/l) +'pathogen': ('irrigation':1000,'domestic':1000,'thermoelectric':1000,'manufacturing':1000,'livestock':1000), # Fecal coliforms (cfu100/ml) } water_quality_dict = {'TMP':'waterTemperature','BOD':'organic','TDS':'salinity':,'FC':'pathogen'} # Step 0 available_surface_water_with_qual = available_surface_water +for sector in ['domestic','manufacturing','livestock','thermoelectric','irrigation']: + globals()['water_demand_remaining'+str(sector)] = water_demand_sector # water_demand_remaining_sector + globals()['water_demand_satisfied'+str(sector)] = 0. # water_demand_satisfied_sector # Looping for every constituent for consti in ['TMP','BOD','TDS','FC']: @@ -31,16 +34,24 @@ for consti in ['TMP','BOD','TDS','FC']: constituent = water_quality_dict[consti] # Looping for every sector - for sector in ['domestic','manufacturing','livestock','thermoelectric','irrigation']: # Looping by sectors (as a list) is sort of a prioritization rule, the only difference is that water demands were distributed proportionally, diluting the idea + sector_order = f(starting from the lowest threshold) # ['domestic','manufacturing','livestock','thermoelectric','irrigation'] + for sector in sector_order: # Defining water quality thresholds threshold_consti_sector = water_quality_thresholds[constituent][sector] # Defining actual water available depending on constituent threshold available_surface_water_with_qual_conti_sector = ifthenelse(water_quality_concetration_consti < threshold_consti_sector, available_surface_water_with_qual, 0.) - + +# Looping for every sector to keep the proportional distribution + for sector in sector_order: + # Water allocation based on proportional water demand distribution, zonation scheme and further considerations (groundwater, desalinated water) - water_withdrawal_sector = f(current PCR-GLOBWB2 water allocation scheme) + water_withdrawal_sector = f(current PCR-GLOBWB2 water allocation scheme using available_surface_water_with_qual_conti_sector) - # Calculting remaining water available in - available_surface_water_with_qual = available_surface_water_with_qual - water_withdrawal_sector + # Calculting remaining water available + available_surface_water_with_qual = available_surface_water_with_qual - water_withdrawal_sector + + # Tracking the water demand: satisficed and remaining + water_demand_remaining_sector = water_demand_remaining_sector - water_withdrawal_sector + water_demand_satisficed_sector = water_demand_satisficed_sector + water_withdrawal_sector From c2455d82e94984561bc1249822d00540919f90aa Mon Sep 17 00:00:00 2001 From: gcardenas1891 <71383566+gcardenas1891@users.noreply.github.com> Date: Fri, 2 Dec 2022 11:04:23 +0100 Subject: [PATCH 06/31] Update notes_with_gabriel_2 Adding function to order sectors from less to more stringent --- notes_with_gabriel_2 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/notes_with_gabriel_2 b/notes_with_gabriel_2 index 3e1edc50e..d2f478c12 100644 --- a/notes_with_gabriel_2 +++ b/notes_with_gabriel_2 @@ -33,8 +33,12 @@ for consti in ['TMP','BOD','TDS','FC']: water_quality_concetration_consti = eval(f'self.input{consti}') constituent = water_quality_dict[consti] + # Ordering sectors from less to more stringent + thresholds = water_quality_thresholds[constituent] + thresholds = sorted(thresholds.items(), key=lambda item: item[1], reverse=True) + sector_order = [threshold[0] for threshold in thresholds] + # Looping for every sector - sector_order = f(starting from the lowest threshold) # ['domestic','manufacturing','livestock','thermoelectric','irrigation'] for sector in sector_order: # Defining water quality thresholds From 30877f1e919343d7e5a8850767c02736c9417658 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Fri, 2 Dec 2022 12:21:17 +0100 Subject: [PATCH 07/31] continue developing water quality --- model/landCover.py | 136 +++++++++++++++++++++++++---------- model/surfaceWaterQuality.py | 27 +++++++ notes_with_gabriel_2.py | 61 ++++++++++++++++ 3 files changed, 187 insertions(+), 37 deletions(-) create mode 100644 model/surfaceWaterQuality.py create mode 100644 notes_with_gabriel_2.py diff --git a/model/landCover.py b/model/landCover.py index 990e624ac..74ea1747f 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -34,6 +34,8 @@ import virtualOS as vos from ncConverter import * +import surfaceWaterQuality as swq + class LandCover(object): def __init__(self,iniItems,nameOfSectionInIniFile,soil_and_topo_parameters,landmask,irrigationEfficiency,usingAllocSegments = False): @@ -1846,44 +1848,104 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ pcr.max(0.0, surface_water_demand_estimate - remainingIrrigationLivestock)) correctedSurfaceWaterDemandEstimate = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic surface_water_demand = correctedSurfaceWaterDemandEstimate - # - # if surface water abstraction as the first priority - if self.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination - # - available_surface_water_volume = pcr.max(0.00, routing.readAvlChannelStorage) - if self.consider_water_quality == True: - available_surface_water_volume = pcr.ifthenelse(self.inputBOD < self.thresholdBODForIrrigation, available_surface_water_volume, 0.0) - if self.usingAllocSegments: # using zone/segment at which supply network is defined - # - logger.debug("Allocation of surface water abstraction.") - # - volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ - vos.waterAbstractionAndAllocation( - water_demand_volume = surface_water_demand*routing.cellArea,\ - available_water_volume = available_surface_water_volume,\ - allocation_zones = allocSegments,\ - zone_area = self.segmentArea,\ - high_volume_treshold = None,\ - debug_water_balance = True,\ - extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), - landmask = self.landmask, - ignore_small_values = False, - prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) - - self.actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea - self.allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea - # - else: - logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") - self.actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ - surface_water_demand) # unit: m - self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m - # - self.actSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.actSurfaceWaterAbstract) - self.allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.allocSurfaceWaterAbstract) - ################################################################################################################################ - # - end of Abstraction and Allocation of SURFACE WATER + # Note the variable "surface_water_demand" is the estimate of surface water demand for all sectors (total) based on Siebert et al.; McDonald et al.; de Graaf et al. + + + if self.consider_water_quality: + + logger.info("Surface water allocation to meet demand will consider water quality.") + + + # Input + # - available_surface_water (without considering quality) + available_surface_water_without_qual = pcr.max(0.00, routing.readAvlChannelStorage) + # - list of water_quality_constituents + wq_constituent = ["sw_temperatur", "bio_o2_demand", "total_dissolved_solid", "fecal_coliform"] + # - list of water demand sectors + wd_sector = ["irrigation", "livestock", "industrial", "domestic"] + + # - the estimates of surface water demand for every sector, after the above schemes (Siebert et al.; McDonald et al.; de Graaf et al.) + sectoral_surface_water_demand = {} + sectoral_surface_water_demand["irrigation"] = correctedRemainingIrrigationLivestock * vos.getValDivZero(remainingIrrigation, remainingLivestock + remainingIrrigation) + sectoral_surface_water_demand["livestock"] = pcr.max(0.0, correctedRemainingIrrigationLivestock - sectoral_surface_water_demand["irrigation"]) + sectoral_surface_water_demand["industrial"] = correctedRemainingIndustrialDomestic * vos.getValDivZero(remainingIndustry, remainingIndustry + remainingDomestic) + sectoral_surface_water_demand["domestic"] = pcr.max(0.0, correctedRemainingIndustrialDomestic - sectoral_surface_water_demand["industrial"]) + + # - current/simulate water_quality_concetration_constituent + wq_state = {} + wq_state["sw_temperatur"] = 0.0 + wq_state["bio_o2_demand"] = self.inputBOD + wq_state["total_dissolved_solid"] = 0.0 + wq_state["fecal_coliform"] = 0.0 + # - water quality thresholds + wq_threshold = {} + wq_threshold["irrigation"] = {} + wq_threshold["irrigation"]["sw_temperatur"] = 1e20 + wq_threshold["irrigation"]["bio_o2_demand"] = 1e20 + wq_threshold["irrigation"]["total_dissolved_solid"] = 1e20 + wq_threshold["irrigation"]["fecal_coliform"] = 1e20 + wq_threshold["livestock"] = {} + wq_threshold["livestock"]["sw_temperatur"] = 1e20 + wq_threshold["livestock"]["bio_o2_demand"] = 1e20 + wq_threshold["livestock"]["total_dissolved_solid"] = 1e20 + wq_threshold["livestock"]["fecal_coliform"] = 1e20 + wq_threshold["industrial"] = {} + wq_threshold["industrial"]["sw_temperatur"] = 1e20 + wq_threshold["industrial"]["bio_o2_demand"] = 1e20 + wq_threshold["industrial"]["total_dissolved_solid"] = 1e20 + wq_threshold["industrial"]["fecal_coliform"] = 1e20 + wq_threshold["domestic"] = {} + wq_threshold["domestic"]["sw_temperatur"] = 1e20 + wq_threshold["domestic"]["bio_o2_demand"] = 1e20 + wq_threshold["domestic"]["total_dissolved_solid"] = 1e20 + wq_threshold["domestic"]["fecal_coliform"] = 1e20 + + # CONTINUE FROM HERE + + swq.surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold) + + + else: + + # + # if surface water abstraction as the first priority + if self.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination + # + # ~ available_surface_water_volume = pcr.max(0.00, routing.readAvlChannelStorage) + # ~ if self.consider_water_quality == True: + # ~ available_surface_water_volume = pcr.ifthenelse(self.inputBOD < self.thresholdBODForIrrigation, available_surface_water_volume, 0.0) + + if self.usingAllocSegments: # using zone/segment at which supply network is defined + # + logger.debug("Allocation of surface water abstraction.") + # + volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = surface_water_demand*routing.cellArea,\ + available_water_volume = available_surface_water_volume,\ + allocation_zones = allocSegments,\ + zone_area = self.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + + self.actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea + self.allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea + # + else: + logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") + self.actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ + surface_water_demand) # unit: m + self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m + # + self.actSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.actSurfaceWaterAbstract) + self.allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.allocSurfaceWaterAbstract) + ################################################################################################################################ + # - end of Abstraction and Allocation of SURFACE WATER # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py new file mode 100644 index 000000000..410ce645b --- /dev/null +++ b/model/surfaceWaterQuality.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# + +import re +import types + +import netCDF4 as nc +import pcraster as pcr + +import logging +logger = logging.getLogger(__name__) + +import virtualOS as vos +from ncConverter import * + + +def surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold): + + # CONTINUE FROM HERE + + pass + + + + + diff --git a/notes_with_gabriel_2.py b/notes_with_gabriel_2.py new file mode 100644 index 000000000..d2f478c12 --- /dev/null +++ b/notes_with_gabriel_2.py @@ -0,0 +1,61 @@ +# Principle +# - Keep proportional allocation of water withdrawn based on demand magnitude (current principle of PCR-GLOBWB 2) +# - Water allocation starts from the pixels with the best water quality + +# How many loops? +# - Water quality constituents (i.e., Temp, BOD, TDS, FC, [DO, N, P]) +# - Water quality thresholds for each constituent and for each sector + +# Input +# - available_surface_water (without considering quality) +# - water_demand_sector (e.g., irrigation, livestock, domestic and industry) -> (industry will be splitted into manufacturing and thermoelectric) +# - water_quality_concetration_constituent [Temp, BOD, TDS, FC] +# - water_quality_thresholds +water_quality_thresholds = { +'waterTemperature': ('irrigation':1000,'domestic':1000,'thermoelectric':1000,'manufacturing':1000,'livestock':1000), # Water temperature (oC) +'organic': ('irrigation':15 ,'domestic':5 ,'thermoelectric':30 ,'manufacturing':30 ,'livestock':1000), # Biochemical oxigen demand (mg/l) +'salinity': ('irrigation':450 ,'domestic':600 ,'thermoelectric':7000,'manufacturing':7000,'livestock':100000), # Total disolved solids (mg/l) +'pathogen': ('irrigation':1000,'domestic':1000,'thermoelectric':1000,'manufacturing':1000,'livestock':1000), # Fecal coliforms (cfu100/ml) +} + +water_quality_dict = {'TMP':'waterTemperature','BOD':'organic','TDS':'salinity':,'FC':'pathogen'} + +# Step 0 +available_surface_water_with_qual = available_surface_water +for sector in ['domestic','manufacturing','livestock','thermoelectric','irrigation']: + globals()['water_demand_remaining'+str(sector)] = water_demand_sector # water_demand_remaining_sector + globals()['water_demand_satisfied'+str(sector)] = 0. # water_demand_satisfied_sector + +# Looping for every constituent +for consti in ['TMP','BOD','TDS','FC']: + + # Defining datasets with water quality concentrations and constituen's name + water_quality_concetration_consti = eval(f'self.input{consti}') + constituent = water_quality_dict[consti] + + # Ordering sectors from less to more stringent + thresholds = water_quality_thresholds[constituent] + thresholds = sorted(thresholds.items(), key=lambda item: item[1], reverse=True) + sector_order = [threshold[0] for threshold in thresholds] + +# Looping for every sector + for sector in sector_order: + + # Defining water quality thresholds + threshold_consti_sector = water_quality_thresholds[constituent][sector] + + # Defining actual water available depending on constituent threshold + available_surface_water_with_qual_conti_sector = ifthenelse(water_quality_concetration_consti < threshold_consti_sector, available_surface_water_with_qual, 0.) + +# Looping for every sector to keep the proportional distribution + for sector in sector_order: + + # Water allocation based on proportional water demand distribution, zonation scheme and further considerations (groundwater, desalinated water) + water_withdrawal_sector = f(current PCR-GLOBWB2 water allocation scheme using available_surface_water_with_qual_conti_sector) + + # Calculting remaining water available + available_surface_water_with_qual = available_surface_water_with_qual - water_withdrawal_sector + + # Tracking the water demand: satisficed and remaining + water_demand_remaining_sector = water_demand_remaining_sector - water_withdrawal_sector + water_demand_satisficed_sector = water_demand_satisficed_sector + water_withdrawal_sector From 074b64249881cd2e1a0db442ae418639c0c20f6f Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Wed, 7 Dec 2022 10:25:26 +0100 Subject: [PATCH 08/31] continue water quality --- model/landCover.py | 120 ++++++++++++++++++++--------------- model/surfaceWaterQuality.py | 83 +++++++++++++++++++++++- 2 files changed, 152 insertions(+), 51 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 74ea1747f..6324590a2 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1880,32 +1880,52 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ wq_state["fecal_coliform"] = 0.0 # - water quality thresholds wq_threshold = {} - wq_threshold["irrigation"] = {} - wq_threshold["irrigation"]["sw_temperatur"] = 1e20 - wq_threshold["irrigation"]["bio_o2_demand"] = 1e20 - wq_threshold["irrigation"]["total_dissolved_solid"] = 1e20 - wq_threshold["irrigation"]["fecal_coliform"] = 1e20 - wq_threshold["livestock"] = {} - wq_threshold["livestock"]["sw_temperatur"] = 1e20 - wq_threshold["livestock"]["bio_o2_demand"] = 1e20 - wq_threshold["livestock"]["total_dissolved_solid"] = 1e20 - wq_threshold["livestock"]["fecal_coliform"] = 1e20 - wq_threshold["industrial"] = {} - wq_threshold["industrial"]["sw_temperatur"] = 1e20 - wq_threshold["industrial"]["bio_o2_demand"] = 1e20 - wq_threshold["industrial"]["total_dissolved_solid"] = 1e20 - wq_threshold["industrial"]["fecal_coliform"] = 1e20 - wq_threshold["domestic"] = {} - wq_threshold["domestic"]["sw_temperatur"] = 1e20 - wq_threshold["domestic"]["bio_o2_demand"] = 1e20 - wq_threshold["domestic"]["total_dissolved_solid"] = 1e20 - wq_threshold["domestic"]["fecal_coliform"] = 1e20 - - # CONTINUE FROM HERE - - swq.surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold) - - + wq_threshold["sw_temperatur"] = {} + wq_threshold["sw_temperatur"]["irrigation"] = 1e20 + wq_threshold["sw_temperatur"]["livestock"] = 1e20 + wq_threshold["sw_temperatur"]["industrial"] = 1e20 + wq_threshold["sw_temperatur"]["domestic"] = 1e20 + wq_threshold["bio_02_demand"] = {} + wq_threshold["bio_02_demand"]["irrigation"] = 1e20 + wq_threshold["bio_02_demand"]["livestock"] = 1e20 + wq_threshold["bio_02_demand"]["industrial"] = 1e20 + wq_threshold["bio_02_demand"]["domestic"] = 1e20 + wq_threshold["total_dissolved_solid"] = {} + wq_threshold["total_dissolved_solid"]["irrigation"] = 1e20 + wq_threshold["total_dissolved_solid"]["livestock"] = 1e20 + wq_threshold["total_dissolved_solid"]["industrial"] = 1e20 + wq_threshold["total_dissolved_solid"]["domestic"] = 1e20 + wq_threshold["fecal_coliform"] = {} + wq_threshold["fecal_coliform"]["irrigation"] = 1e20 + wq_threshold["fecal_coliform"]["livestock"] = 1e20 + wq_threshold["fecal_coliform"]["industrial"] = 1e20 + wq_threshold["fecal_coliform"]["domestic"] = 1e20 + + + totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ + swq.surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold) + + # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply + ################################################################################################################################ + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["irrigation"] + sectoral_surface_water_demand_satisfied["livestock"] + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["irrigation"] + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] + sectoral_surface_water_demand_satisfied["livestock"] + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater + # - for livestock + satisfiedLivestockDemand += sectoral_surface_water_demand_satisfied["livestock"] + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] + # - for domestic + satisfiedDomesticDemand += sectoral_surface_water_demand_satisfied["domestic"] + # - for industry + satisfiedIndustryDemand += sectoral_surface_water_demand_satisfied["industrial"] + + # CONTINUE FROM HERE! + else: # @@ -1948,30 +1968,30 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # - end of Abstraction and Allocation of SURFACE WATER - # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply - ################################################################################################################################ - # - for irrigation and livestock water demand - satisfiedIrrigationLivestockDemandFromSurfaceWater = self.allocSurfaceWaterAbstract * \ - vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedSurfaceWaterDemandEstimate) - # - for irrigation water demand, but not including livestock - satisfiedIrrigationDemandFromSurfaceWater = satisfiedIrrigationLivestockDemandFromSurfaceWater * \ - vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) - satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater - # - for non irrigation water demand: livestock, domestic and industry - satisfiedNonIrrDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract - satisfiedIrrigationDemandFromSurfaceWater) - satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater - # - for livestock - satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromSurfaceWater - \ - satisfiedIrrigationDemandFromSurfaceWater) - # - for industrial and domestic demand (excluding livestock) - satisfiedIndustrialDomesticDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract -\ - satisfiedIrrigationLivestockDemandFromSurfaceWater) - # - for domestic - satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingDomestic, \ - remainingIndustrialDomestic) - # - for industry - satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingIndustry, \ - remainingIndustrialDomestic) + # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply + ################################################################################################################################ + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromSurfaceWater = self.allocSurfaceWaterAbstract * \ + vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedSurfaceWaterDemandEstimate) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromSurfaceWater = satisfiedIrrigationLivestockDemandFromSurfaceWater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract - satisfiedIrrigationDemandFromSurfaceWater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromSurfaceWater - \ + satisfiedIrrigationDemandFromSurfaceWater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract -\ + satisfiedIrrigationLivestockDemandFromSurfaceWater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 410ce645b..8f4d2b87a 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -18,8 +18,89 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold): # CONTINUE FROM HERE + + # initial values + # - available surface water before allocation + available_surface_water_with_qual = available_surface_water_without_qual + # - remaining water demand and satisfied water demand + for sector in wd_sector: + water_demand_remaining[sector] = sectoral_surface_water_demand[sector] + water_demand_satisfied[sector] = 0.0 + + # looping for every constituent + for consti in wq_constituent: + + # water quality concentrations + water_quality_concetration_consti = wq_state[consti] + + # ordering sectors from less to more stringent + thresholds = wq_threshold[consti] + thresholds = sorted(thresholds.items(), key=lambda item: item[1], reverse=True) + sector_order = [threshold[0] for threshold in thresholds] + + # looping for every sector + for sector in sector_order: + + # defining water quality thresholds + threshold_consti_sector = wq_threshold[consti][sector] + + # defining actual water available depending on constituent threshold + available_surface_water_with_qual_conti_sector = pcr.ifthenelse(water_quality_concetration_consti < threshold_consti_sector, available_surface_water_with_qual, 0.) + + # total remaining water demand + total_water_demand = 0.0 + for sector in wd_sector: total_water_demand = total_water_demand + water_demand_remaining[sector] + + # if surface water abstraction as the first priority + if self.surfaceWaterPiority: surface_water_demand = total_water_demand + # + available_surface_water_volume = pcr.max(0.00, available_surface_water_with_qual) + + if self.usingAllocSegments: # using zone/segment at which supply network is defined + # + logger.debug("Allocation of surface water abstraction.") + # + volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = surface_water_demand*routing.cellArea,\ + available_water_volume = available_surface_water_volume,\ + allocation_zones = allocSegments,\ + zone_area = self.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + + actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea + allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea + # + else: + logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") + actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ + surface_water_demand) # unit: m + allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m + # + + # - the amount of water that is abstracted from the source (e.g. river, reservoir pixels) + actSurfaceWaterAbstract = pcr.ifthen(self.landmask, actSurfaceWaterAbstract) + # - the amount of water that is given to pixels with demand (e.g. pixels with irrigation areas) + allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, allocSurfaceWaterAbstract) + + + # calculating remaining water available + available_surface_water_with_qual = available_surface_water_with_qual - actSurfaceWaterAbstract + + # looping for every sector to distribute allocSurfaceWaterAbstract + for sector in sector_order: + current_water_withdrawal = allocSurfaceWaterAbstract * vos.getValDivZero(water_demand_remaining[sector], total_water_demand) + # - tracking the water demand: satisficed and remaining + water_demand_remaining[sector] = water_demand_remaining[sector] - current_water_withdrawal + water_demand_satisfied[sector] = water_demand_satisfied[sector] + current_water_withdrawal + - pass + return() From 04520d5f5cfbdaf67d6479b0e9f545c6fc6fe7f5 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Wed, 7 Dec 2022 11:25:51 +0100 Subject: [PATCH 09/31] continue water quality --- model/landCover.py | 2 +- model/surfaceWaterQuality.py | 8 ++++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 6324590a2..915a4637f 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1881,7 +1881,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # - water quality thresholds wq_threshold = {} wq_threshold["sw_temperatur"] = {} - wq_threshold["sw_temperatur"]["irrigation"] = 1e20 + wq_threshold["sw_temperatur"]["irrigation"] = None wq_threshold["sw_temperatur"]["livestock"] = 1e20 wq_threshold["sw_temperatur"]["industrial"] = 1e20 wq_threshold["sw_temperatur"]["domestic"] = 1e20 diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 8f4d2b87a..a081b1980 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -22,6 +22,8 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu # initial values # - available surface water before allocation available_surface_water_with_qual = available_surface_water_without_qual + # - amount of water that is abstracted from the source + totalActSurfaceWaterAbstract = 0.0 # - remaining water demand and satisfied water demand for sector in wd_sector: water_demand_remaining[sector] = sectoral_surface_water_demand[sector] @@ -88,6 +90,9 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu # - the amount of water that is given to pixels with demand (e.g. pixels with irrigation areas) allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, allocSurfaceWaterAbstract) + # tracking the total amount of water that is abstracted from the source + totalActSurfaceWaterAbstract = totalActSurfaceWaterAbstract + actSurfaceWaterAbstract + # calculating remaining water available available_surface_water_with_qual = available_surface_water_with_qual - actSurfaceWaterAbstract @@ -99,8 +104,7 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu water_demand_remaining[sector] = water_demand_remaining[sector] - current_water_withdrawal water_demand_satisfied[sector] = water_demand_satisfied[sector] + current_water_withdrawal - - return() + return totalActSurfaceWaterAbstract, water_demand_satisfied From 0a9c7202d5384b3c3c0dc48764dea51f8eb054ab Mon Sep 17 00:00:00 2001 From: gcardenas1891 <71383566+gcardenas1891@users.noreply.github.com> Date: Mon, 12 Dec 2022 17:19:02 +0100 Subject: [PATCH 10/31] Update surfaceWaterQuality.py threshold ordering updated --- model/surfaceWaterQuality.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index a081b1980..867813a5f 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -37,6 +37,7 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu # ordering sectors from less to more stringent thresholds = wq_threshold[consti] + thresholds = {k: v for k, v in thresholds.items() if v is not None} thresholds = sorted(thresholds.items(), key=lambda item: item[1], reverse=True) sector_order = [threshold[0] for threshold in thresholds] @@ -98,7 +99,7 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu available_surface_water_with_qual = available_surface_water_with_qual - actSurfaceWaterAbstract # looping for every sector to distribute allocSurfaceWaterAbstract - for sector in sector_order: + for sector in wd_sector: current_water_withdrawal = allocSurfaceWaterAbstract * vos.getValDivZero(water_demand_remaining[sector], total_water_demand) # - tracking the water demand: satisficed and remaining water_demand_remaining[sector] = water_demand_remaining[sector] - current_water_withdrawal From beef1148bba2d621ce40179761ebb00ce11d0aa6 Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Tue, 13 Dec 2022 11:20:51 +0100 Subject: [PATCH 11/31] addition of water_demand_satisfied and water_demand_remaining variables + fixing indentation issue --- config/setup_05min_rhine-meuse_water_quality_development.ini | 3 ++- model/surfaceWaterQuality.py | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/config/setup_05min_rhine-meuse_water_quality_development.ini b/config/setup_05min_rhine-meuse_water_quality_development.ini index b609ce09f..b4e9cc3a5 100644 --- a/config/setup_05min_rhine-meuse_water_quality_development.ini +++ b/config/setup_05min_rhine-meuse_water_quality_development.ini @@ -2,7 +2,8 @@ # Please set the pcrglobwb output directory (outputDir) in an absolute path. # - Please make sure that you have access to it. -outputDir = /scratch/sutan101/test_water_use_with_quality/ +# outputDir = /scratch/sutan101/test_water_use_with_quality/ +outputDir = /scratch/carde003/test_water_use_with_quality/ # Please set the clone map file (cloneMap), which defines the spatial resolution and extent of your study area. # - Please make sure that the file is stored locally in your computing machine. diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 867813a5f..d58423d00 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -25,6 +25,8 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu # - amount of water that is abstracted from the source totalActSurfaceWaterAbstract = 0.0 # - remaining water demand and satisfied water demand + water_demand_remaining = {} + water_demand_satisfied = {} for sector in wd_sector: water_demand_remaining[sector] = sectoral_surface_water_demand[sector] water_demand_satisfied[sector] = 0.0 @@ -37,7 +39,7 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu # ordering sectors from less to more stringent thresholds = wq_threshold[consti] - thresholds = {k: v for k, v in thresholds.items() if v is not None} + thresholds = {k: v for k, v in thresholds.items() if v is not None} thresholds = sorted(thresholds.items(), key=lambda item: item[1], reverse=True) sector_order = [threshold[0] for threshold in thresholds] From d6c88746d94f731d17c347362fd9233f912e58c1 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 11:37:40 +0100 Subject: [PATCH 12/31] continue surface water quality development --- model/landCover.py | 35 ++++++++++++++++++----------------- model/surfaceWaterQuality.py | 19 ++++++++++--------- 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 915a4637f..576a4eaf7 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1874,7 +1874,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # - current/simulate water_quality_concetration_constituent wq_state = {} - wq_state["sw_temperatur"] = 0.0 + wq_state["sw_temperatur"] = 25.0 wq_state["bio_o2_demand"] = self.inputBOD wq_state["total_dissolved_solid"] = 0.0 wq_state["fecal_coliform"] = 0.0 @@ -1882,28 +1882,29 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ wq_threshold = {} wq_threshold["sw_temperatur"] = {} wq_threshold["sw_temperatur"]["irrigation"] = None - wq_threshold["sw_temperatur"]["livestock"] = 1e20 - wq_threshold["sw_temperatur"]["industrial"] = 1e20 - wq_threshold["sw_temperatur"]["domestic"] = 1e20 + wq_threshold["sw_temperatur"]["livestock"] = None + wq_threshold["sw_temperatur"]["industrial"] = 30.0 + wq_threshold["sw_temperatur"]["domestic"] = None wq_threshold["bio_02_demand"] = {} - wq_threshold["bio_02_demand"]["irrigation"] = 1e20 - wq_threshold["bio_02_demand"]["livestock"] = 1e20 - wq_threshold["bio_02_demand"]["industrial"] = 1e20 - wq_threshold["bio_02_demand"]["domestic"] = 1e20 + wq_threshold["bio_02_demand"]["irrigation"] = 15.0 + wq_threshold["bio_02_demand"]["livestock"] = None + wq_threshold["bio_02_demand"]["industrial"] = 30.0 + wq_threshold["bio_02_demand"]["domestic"] = 5.0 wq_threshold["total_dissolved_solid"] = {} - wq_threshold["total_dissolved_solid"]["irrigation"] = 1e20 - wq_threshold["total_dissolved_solid"]["livestock"] = 1e20 - wq_threshold["total_dissolved_solid"]["industrial"] = 1e20 - wq_threshold["total_dissolved_solid"]["domestic"] = 1e20 + wq_threshold["total_dissolved_solid"]["irrigation"] = 450.0 + wq_threshold["total_dissolved_solid"]["livestock"] = None + wq_threshold["total_dissolved_solid"]["industrial"] = 7000. + wq_threshold["total_dissolved_solid"]["domestic"] = 600. wq_threshold["fecal_coliform"] = {} - wq_threshold["fecal_coliform"]["irrigation"] = 1e20 - wq_threshold["fecal_coliform"]["livestock"] = 1e20 - wq_threshold["fecal_coliform"]["industrial"] = 1e20 - wq_threshold["fecal_coliform"]["domestic"] = 1e20 + wq_threshold["fecal_coliform"]["irrigation"] = None + wq_threshold["fecal_coliform"]["livestock"] = None + wq_threshold["fecal_coliform"]["industrial"] = None + wq_threshold["fecal_coliform"]["domestic"] = None totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ - swq.surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold) + swq.surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, + self.surfaceWaterPiority, self.usingAllocSegments, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply ################################################################################################################################ diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index d58423d00..025ee0260 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -15,7 +15,8 @@ from ncConverter import * -def surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold): +def surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, + surfaceWaterPiority, usingAllocSegments, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): # CONTINUE FROM HERE @@ -57,11 +58,11 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu for sector in wd_sector: total_water_demand = total_water_demand + water_demand_remaining[sector] # if surface water abstraction as the first priority - if self.surfaceWaterPiority: surface_water_demand = total_water_demand + if surfaceWaterPiority: surface_water_demand = total_water_demand # available_surface_water_volume = pcr.max(0.00, available_surface_water_with_qual) - if self.usingAllocSegments: # using zone/segment at which supply network is defined + if usingAllocSegments: # using zone/segment at which supply network is defined # logger.debug("Allocation of surface water abstraction.") # @@ -70,13 +71,13 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu water_demand_volume = surface_water_demand*routing.cellArea,\ available_water_volume = available_surface_water_volume,\ allocation_zones = allocSegments,\ - zone_area = self.segmentArea,\ + zone_area = segmentArea,\ high_volume_treshold = None,\ debug_water_balance = True,\ extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), - landmask = self.landmask, + landmask = landmask, ignore_small_values = False, - prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + prioritizing_local_source = prioritizeLocalSourceToMeetWaterDemand) actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea @@ -85,13 +86,13 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ surface_water_demand) # unit: m - allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m + allocSurfaceWaterAbstract = actSurfaceWaterAbstract # unit: m # # - the amount of water that is abstracted from the source (e.g. river, reservoir pixels) - actSurfaceWaterAbstract = pcr.ifthen(self.landmask, actSurfaceWaterAbstract) + actSurfaceWaterAbstract = pcr.ifthen(landmask, actSurfaceWaterAbstract) # - the amount of water that is given to pixels with demand (e.g. pixels with irrigation areas) - allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, allocSurfaceWaterAbstract) + allocSurfaceWaterAbstract = pcr.ifthen(landmask, allocSurfaceWaterAbstract) # tracking the total amount of water that is abstracted from the source totalActSurfaceWaterAbstract = totalActSurfaceWaterAbstract + actSurfaceWaterAbstract From b298a46d70bdd9da1d3008d6024deff5d4735b84 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 11:42:06 +0100 Subject: [PATCH 13/31] continue surface water quality development --- config/setup_05min_rhine-meuse_water_quality_development.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/setup_05min_rhine-meuse_water_quality_development.ini b/config/setup_05min_rhine-meuse_water_quality_development.ini index b4e9cc3a5..1ab9e8d3a 100644 --- a/config/setup_05min_rhine-meuse_water_quality_development.ini +++ b/config/setup_05min_rhine-meuse_water_quality_development.ini @@ -2,8 +2,8 @@ # Please set the pcrglobwb output directory (outputDir) in an absolute path. # - Please make sure that you have access to it. -# outputDir = /scratch/sutan101/test_water_use_with_quality/ -outputDir = /scratch/carde003/test_water_use_with_quality/ +outputDir = /scratch/sutan101/test_water_use_with_quality/ +#~ outputDir = /scratch/carde003/test_water_use_with_quality/ # Please set the clone map file (cloneMap), which defines the spatial resolution and extent of your study area. # - Please make sure that the file is stored locally in your computing machine. From c8337891d181e7b3562af4a75827962acac5e0e2 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 11:47:06 +0100 Subject: [PATCH 14/31] continue surface water quality development --- model/landCover.py | 2 +- model/surfaceWaterQuality.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 576a4eaf7..246c9005d 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1904,7 +1904,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ swq.surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, - self.surfaceWaterPiority, self.usingAllocSegments, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) + self.surfaceWaterPiority, self.usingAllocSegments, routing.cellArea, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply ################################################################################################################################ diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 025ee0260..07d3c1d6d 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -16,7 +16,7 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, - surfaceWaterPiority, usingAllocSegments, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): + surfaceWaterPiority, usingAllocSegments, cellArea, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): # CONTINUE FROM HERE @@ -68,7 +68,7 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu # volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ vos.waterAbstractionAndAllocation( - water_demand_volume = surface_water_demand*routing.cellArea,\ + water_demand_volume = surface_water_demand*cellArea,\ available_water_volume = available_surface_water_volume,\ allocation_zones = allocSegments,\ zone_area = segmentArea,\ @@ -79,12 +79,12 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu ignore_small_values = False, prioritizing_local_source = prioritizeLocalSourceToMeetWaterDemand) - actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea - allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea + actSurfaceWaterAbstract = volActSurfaceWaterAbstract / cellArea + allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / cellArea # else: logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") - actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ + actSurfaceWaterAbstract = pcr.min(available_surface_water_volume/cellArea,\ surface_water_demand) # unit: m allocSurfaceWaterAbstract = actSurfaceWaterAbstract # unit: m # From a8a430ca5481bc902d00dfb1130642faeb782cc7 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 11:54:56 +0100 Subject: [PATCH 15/31] continue surface water quality development --- model/surfaceWaterQuality.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 07d3c1d6d..374bca426 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -16,7 +16,7 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, - surfaceWaterPiority, usingAllocSegments, cellArea, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): + surfaceWaterPriority, usingAllocSegments, cellArea, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): # CONTINUE FROM HERE @@ -57,8 +57,9 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu total_water_demand = 0.0 for sector in wd_sector: total_water_demand = total_water_demand + water_demand_remaining[sector] - # if surface water abstraction as the first priority - if surfaceWaterPiority: surface_water_demand = total_water_demand + surface_water_demand = total_water_demand + # + # TODO: Accomodate the option "surfaceWaterPriority"! # available_surface_water_volume = pcr.max(0.00, available_surface_water_with_qual) From ba3ba07165e31ab82ff499030c22900d8c8a3602 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 11:56:57 +0100 Subject: [PATCH 16/31] continue surface water quality development --- model/landCover.py | 2 +- model/surfaceWaterQuality.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 246c9005d..8dd94ceab 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1904,7 +1904,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ swq.surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, - self.surfaceWaterPiority, self.usingAllocSegments, routing.cellArea, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) + self.surfaceWaterPiority, self.usingAllocSegments, self.allocSegments, routing.cellArea, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply ################################################################################################################################ diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 374bca426..2663875fe 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -16,7 +16,7 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, - surfaceWaterPriority, usingAllocSegments, cellArea, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): + surfaceWaterPriority, usingAllocSegments, allocSegments, cellArea, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): # CONTINUE FROM HERE From 7f95fb56879cf651880135c40609d447405aa062 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 11:59:29 +0100 Subject: [PATCH 17/31] continue surface water quality development --- model/landCover.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 8dd94ceab..cb5888e38 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1885,11 +1885,11 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ wq_threshold["sw_temperatur"]["livestock"] = None wq_threshold["sw_temperatur"]["industrial"] = 30.0 wq_threshold["sw_temperatur"]["domestic"] = None - wq_threshold["bio_02_demand"] = {} - wq_threshold["bio_02_demand"]["irrigation"] = 15.0 - wq_threshold["bio_02_demand"]["livestock"] = None - wq_threshold["bio_02_demand"]["industrial"] = 30.0 - wq_threshold["bio_02_demand"]["domestic"] = 5.0 + wq_threshold["bio_o2_demand"] = {} + wq_threshold["bio_o2_demand"]["irrigation"] = 15.0 + wq_threshold["bio_o2_demand"]["livestock"] = None + wq_threshold["bio_o2_demand"]["industrial"] = 30.0 + wq_threshold["bio_o2_demand"]["domestic"] = 5.0 wq_threshold["total_dissolved_solid"] = {} wq_threshold["total_dissolved_solid"]["irrigation"] = 450.0 wq_threshold["total_dissolved_solid"]["livestock"] = None From 068b98f65b0a07ac831a0189249c095e85ba0f27 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 12:03:11 +0100 Subject: [PATCH 18/31] continue surface water quality development --- model/landCover.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/model/landCover.py b/model/landCover.py index cb5888e38..72368ef29 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1925,7 +1925,8 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # - for industry satisfiedIndustryDemand += sectoral_surface_water_demand_satisfied["industrial"] - # CONTINUE FROM HERE! + self.allocSurfaceWaterAbstract = sectoral_surface_water_demand_satisfied + self.actSurfaceWaterAbstract = totalActSurfaceWaterAbstract else: From 61f8c4ceef45289db00f9671f7832ee5d6b0199b Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 12:07:13 +0100 Subject: [PATCH 19/31] continue surface water quality development --- model/surfaceWaterQuality.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 2663875fe..0d76f7e0d 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -18,8 +18,6 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, surfaceWaterPriority, usingAllocSegments, allocSegments, cellArea, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): - # CONTINUE FROM HERE - # initial values # - available surface water before allocation available_surface_water_with_qual = available_surface_water_without_qual @@ -44,6 +42,9 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu thresholds = sorted(thresholds.items(), key=lambda item: item[1], reverse=True) sector_order = [threshold[0] for threshold in thresholds] + print(sector_order) + qu + # looping for every sector for sector in sector_order: @@ -63,6 +64,9 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu # available_surface_water_volume = pcr.max(0.00, available_surface_water_with_qual) + msg = "Allocating water that is above the threshold for " + consti + " for the sector " + sector + logger.debug(msg) + if usingAllocSegments: # using zone/segment at which supply network is defined # logger.debug("Allocation of surface water abstraction.") From 9b44fad0a363ca4bc221fa3c302ba1f0db0d915f Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 12:09:59 +0100 Subject: [PATCH 20/31] continue surface water quality development --- model/landCover.py | 26 +++++++++++++++++++++++++- model/surfaceWaterQuality.py | 2 +- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 72368ef29..72fab6462 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1899,7 +1899,31 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ wq_threshold["fecal_coliform"]["irrigation"] = None wq_threshold["fecal_coliform"]["livestock"] = None wq_threshold["fecal_coliform"]["industrial"] = None - wq_threshold["fecal_coliform"]["domestic"] = None + wq_threshold["fecal_coliform"]["domestic"] = 10.0 + + + # ~ # - water quality thresholds + # ~ wq_threshold = {} + # ~ wq_threshold["sw_temperatur"] = {} + # ~ wq_threshold["sw_temperatur"]["irrigation"] = 1000000. + # ~ wq_threshold["sw_temperatur"]["livestock"] = 1000000. + # ~ wq_threshold["sw_temperatur"]["industrial"] = 30.0 + # ~ wq_threshold["sw_temperatur"]["domestic"] = 1000000. + # ~ wq_threshold["bio_o2_demand"] = {} + # ~ wq_threshold["bio_o2_demand"]["irrigation"] = 15.0 + # ~ wq_threshold["bio_o2_demand"]["livestock"] = 1000000. + # ~ wq_threshold["bio_o2_demand"]["industrial"] = 30.0 + # ~ wq_threshold["bio_o2_demand"]["domestic"] = 5.0 + # ~ wq_threshold["total_dissolved_solid"] = {} + # ~ wq_threshold["total_dissolved_solid"]["irrigation"] = 450.0 + # ~ wq_threshold["total_dissolved_solid"]["livestock"] = 1000000. + # ~ wq_threshold["total_dissolved_solid"]["industrial"] = 7000. + # ~ wq_threshold["total_dissolved_solid"]["domestic"] = 600. + # ~ wq_threshold["fecal_coliform"] = {} + # ~ wq_threshold["fecal_coliform"]["irrigation"] = 1000000. + # ~ wq_threshold["fecal_coliform"]["livestock"] = 1000000. + # ~ wq_threshold["fecal_coliform"]["industrial"] = 1000000. + # ~ wq_threshold["fecal_coliform"]["domestic"] = 1000000. totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 0d76f7e0d..77ff47ae7 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -43,7 +43,7 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu sector_order = [threshold[0] for threshold in thresholds] print(sector_order) - qu + # ~ qu # looping for every sector for sector in sector_order: From 10ef3ffed18fabc4f0e71bfa09d3d3e0a7187b08 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 12:12:06 +0100 Subject: [PATCH 21/31] continue surface water quality development --- model/landCover.py | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 72fab6462..9e1b650ce 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1902,28 +1902,28 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ wq_threshold["fecal_coliform"]["domestic"] = 10.0 - # ~ # - water quality thresholds - # ~ wq_threshold = {} - # ~ wq_threshold["sw_temperatur"] = {} - # ~ wq_threshold["sw_temperatur"]["irrigation"] = 1000000. - # ~ wq_threshold["sw_temperatur"]["livestock"] = 1000000. - # ~ wq_threshold["sw_temperatur"]["industrial"] = 30.0 - # ~ wq_threshold["sw_temperatur"]["domestic"] = 1000000. - # ~ wq_threshold["bio_o2_demand"] = {} - # ~ wq_threshold["bio_o2_demand"]["irrigation"] = 15.0 - # ~ wq_threshold["bio_o2_demand"]["livestock"] = 1000000. - # ~ wq_threshold["bio_o2_demand"]["industrial"] = 30.0 - # ~ wq_threshold["bio_o2_demand"]["domestic"] = 5.0 - # ~ wq_threshold["total_dissolved_solid"] = {} - # ~ wq_threshold["total_dissolved_solid"]["irrigation"] = 450.0 - # ~ wq_threshold["total_dissolved_solid"]["livestock"] = 1000000. - # ~ wq_threshold["total_dissolved_solid"]["industrial"] = 7000. - # ~ wq_threshold["total_dissolved_solid"]["domestic"] = 600. - # ~ wq_threshold["fecal_coliform"] = {} - # ~ wq_threshold["fecal_coliform"]["irrigation"] = 1000000. - # ~ wq_threshold["fecal_coliform"]["livestock"] = 1000000. - # ~ wq_threshold["fecal_coliform"]["industrial"] = 1000000. - # ~ wq_threshold["fecal_coliform"]["domestic"] = 1000000. + # - water quality thresholds + wq_threshold = {} + wq_threshold["sw_temperatur"] = {} + wq_threshold["sw_temperatur"]["irrigation"] = 1000000. + wq_threshold["sw_temperatur"]["livestock"] = 1000000. + wq_threshold["sw_temperatur"]["industrial"] = 30.0 + wq_threshold["sw_temperatur"]["domestic"] = 1000000. + wq_threshold["bio_o2_demand"] = {} + wq_threshold["bio_o2_demand"]["irrigation"] = 15.0 + wq_threshold["bio_o2_demand"]["livestock"] = 1000000. + wq_threshold["bio_o2_demand"]["industrial"] = 30.0 + wq_threshold["bio_o2_demand"]["domestic"] = 5.0 + wq_threshold["total_dissolved_solid"] = {} + wq_threshold["total_dissolved_solid"]["irrigation"] = 450.0 + wq_threshold["total_dissolved_solid"]["livestock"] = 1000000. + wq_threshold["total_dissolved_solid"]["industrial"] = 7000. + wq_threshold["total_dissolved_solid"]["domestic"] = 600. + wq_threshold["fecal_coliform"] = {} + wq_threshold["fecal_coliform"]["irrigation"] = 1000000. + wq_threshold["fecal_coliform"]["livestock"] = 1000000. + wq_threshold["fecal_coliform"]["industrial"] = 1000000. + wq_threshold["fecal_coliform"]["domestic"] = 1000000. totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ From 40077df5abbf1a5ca606c5e2e264dcdcb20836b6 Mon Sep 17 00:00:00 2001 From: Edwin Sutanudjaja Date: Tue, 13 Dec 2022 12:14:55 +0100 Subject: [PATCH 22/31] continue surface water quality development --- model/landCover.py | 52 +++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 9e1b650ce..55b34724b 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1899,31 +1899,31 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ wq_threshold["fecal_coliform"]["irrigation"] = None wq_threshold["fecal_coliform"]["livestock"] = None wq_threshold["fecal_coliform"]["industrial"] = None - wq_threshold["fecal_coliform"]["domestic"] = 10.0 - - - # - water quality thresholds - wq_threshold = {} - wq_threshold["sw_temperatur"] = {} - wq_threshold["sw_temperatur"]["irrigation"] = 1000000. - wq_threshold["sw_temperatur"]["livestock"] = 1000000. - wq_threshold["sw_temperatur"]["industrial"] = 30.0 - wq_threshold["sw_temperatur"]["domestic"] = 1000000. - wq_threshold["bio_o2_demand"] = {} - wq_threshold["bio_o2_demand"]["irrigation"] = 15.0 - wq_threshold["bio_o2_demand"]["livestock"] = 1000000. - wq_threshold["bio_o2_demand"]["industrial"] = 30.0 - wq_threshold["bio_o2_demand"]["domestic"] = 5.0 - wq_threshold["total_dissolved_solid"] = {} - wq_threshold["total_dissolved_solid"]["irrigation"] = 450.0 - wq_threshold["total_dissolved_solid"]["livestock"] = 1000000. - wq_threshold["total_dissolved_solid"]["industrial"] = 7000. - wq_threshold["total_dissolved_solid"]["domestic"] = 600. - wq_threshold["fecal_coliform"] = {} - wq_threshold["fecal_coliform"]["irrigation"] = 1000000. - wq_threshold["fecal_coliform"]["livestock"] = 1000000. - wq_threshold["fecal_coliform"]["industrial"] = 1000000. - wq_threshold["fecal_coliform"]["domestic"] = 1000000. + wq_threshold["fecal_coliform"]["domestic"] = None + + + # ~ # - water quality thresholds + # ~ wq_threshold = {} + # ~ wq_threshold["sw_temperatur"] = {} + # ~ wq_threshold["sw_temperatur"]["irrigation"] = 1000000. + # ~ wq_threshold["sw_temperatur"]["livestock"] = 1000000. + # ~ wq_threshold["sw_temperatur"]["industrial"] = 30.0 + # ~ wq_threshold["sw_temperatur"]["domestic"] = 1000000. + # ~ wq_threshold["bio_o2_demand"] = {} + # ~ wq_threshold["bio_o2_demand"]["irrigation"] = 15.0 + # ~ wq_threshold["bio_o2_demand"]["livestock"] = 1000000. + # ~ wq_threshold["bio_o2_demand"]["industrial"] = 30.0 + # ~ wq_threshold["bio_o2_demand"]["domestic"] = 5.0 + # ~ wq_threshold["total_dissolved_solid"] = {} + # ~ wq_threshold["total_dissolved_solid"]["irrigation"] = 450.0 + # ~ wq_threshold["total_dissolved_solid"]["livestock"] = 1000000. + # ~ wq_threshold["total_dissolved_solid"]["industrial"] = 7000. + # ~ wq_threshold["total_dissolved_solid"]["domestic"] = 600. + # ~ wq_threshold["fecal_coliform"] = {} + # ~ wq_threshold["fecal_coliform"]["irrigation"] = 1000000. + # ~ wq_threshold["fecal_coliform"]["livestock"] = 1000000. + # ~ wq_threshold["fecal_coliform"]["industrial"] = 1000000. + # ~ wq_threshold["fecal_coliform"]["domestic"] = 1000000. totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ @@ -1949,8 +1949,8 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # - for industry satisfiedIndustryDemand += sectoral_surface_water_demand_satisfied["industrial"] - self.allocSurfaceWaterAbstract = sectoral_surface_water_demand_satisfied self.actSurfaceWaterAbstract = totalActSurfaceWaterAbstract + self.allocSurfaceWaterAbstract = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] + sectoral_surface_water_demand_satisfied["livestock"] + sectoral_surface_water_demand_satisfied["irrigation"] else: From dad87cc851eaea914024540c60b3494601a86dd3 Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Thu, 26 Jan 2023 13:08:28 +0100 Subject: [PATCH 23/31] updating ini file --- ..._rhine-meuse_water_quality_development.ini | 182 +++++++++--------- 1 file changed, 86 insertions(+), 96 deletions(-) diff --git a/config/setup_05min_rhine-meuse_water_quality_development.ini b/config/setup_05min_rhine-meuse_water_quality_development.ini index 1ab9e8d3a..17f308b2e 100644 --- a/config/setup_05min_rhine-meuse_water_quality_development.ini +++ b/config/setup_05min_rhine-meuse_water_quality_development.ini @@ -2,40 +2,38 @@ # Please set the pcrglobwb output directory (outputDir) in an absolute path. # - Please make sure that you have access to it. -outputDir = /scratch/sutan101/test_water_use_with_quality/ -#~ outputDir = /scratch/carde003/test_water_use_with_quality/ +#~ outputDir = /scratch/sutan101/test_water_use_with_quality/ +outputDir = /scratch/carde003/Mekong/1980-1990/Mekong_wquality/ + # Please set the clone map file (cloneMap), which defines the spatial resolution and extent of your study area. # - Please make sure that the file is stored locally in your computing machine. # - The file must be in the pcraster format. -cloneMap = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/global_05min/cloneMaps/RhineMeuse05min.clone.map -#~ cloneMap = /quanta1/home/hydrowld/data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta/pcrglobwb2_input/global_05min/cloneMaps/clone_global_05min.map - +#~ cloneMap = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/global_05min/cloneMaps/RhineMeuse05min.clone.map +cloneMap = /scratch/carde003/Mekong_clone/clone_M13.map # Set the input directory map in an absolute path. The input forcing and parameter directories and files will be relative to this. # - The following is an example using files from the opendap server. #~ inputDir = https://opendap.4tu.nl/thredds/dodsC/data2/pcrglobwb/version_2019_11_beta/pcrglobwb2_input/ -#~ # - The following is an example using input files stored locally in your computing machine. +# - The following is an example using input files stored locally in your computing machine. #~ inputDir = /quanta1/home/hydrowld/data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta/pcrglobwb2_input/ # - on velocity inputDir = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/ # The area/landmask of interest: # If None, area/landmask is limited for cells with ldd value. -#~ landmask = None -landmask = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/global_05min/cloneMaps/RhineMeuse05min.landmask.map - +#~ landmask = None +#~ landmask = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/global_05min/cloneMaps/RhineMeuse05min.landmask.map +landmask = /scratch/carde003/Mekong_clone/mask_M13.map # netcdf attributes for output files: institution = Department of Physical Geography, Utrecht University title = PCR-GLOBWB 2 output, with human factors (non-natural) description = PCR-GLOBWB run with human factors (non-natural) at 5 arcmin resolution - -startTime = 2000-01-01 -endTime = 2010-12-31 # Format: YYYY-MM-DD ; The model runs on daily time step. - +startTime = 1980-01-01 +endTime = 1990-12-31 # spinning up options: maxSpinUpsInYears = 0 @@ -46,7 +44,6 @@ minConvForTotlSto = 0.0 [meteoOptions] - # Set the forcing temperature and precipitation files (relative to inputDir) precipitationNC = global_30min/meteo/forcing/daily_precipitation_cru_era-interim_1979_to_2010.nc temperatureNC = global_30min/meteo/forcing/daily_temperature_cru_era-interim_1979_to_2010.nc @@ -58,93 +55,97 @@ refETPotFileNC = global_30min/meteo/forcing/daily_referencePotET_cru_era-interi [meteoDownscalingOptions] -# This section is for a 5 arcmin run, for downscaling meteorological forcing at 30 arcmin to 5 arcmin. - -downscalePrecipitation = False -downscaleTemperature = True -downscaleReferenceETPot = False - -# downscaling (based on the digital elevation model): -# The downscaling will be performed by providing the "cellIds" (meteoDownscaleIds) of lower resolution cells. -meteoDownscaleIds = global_05min/meteo/downscaling_from_30min/uniqueIds_30min.nc -highResolutionDEM = global_05min/meteo/downscaling_from_30min/gtopo05min.nc +# This section is for a 5 arcmin run, for downscaling meteorological forcing at 30 arcmin to 5 arcmin. +downscalePrecipitation = False +downscaleTemperature = True +downscaleReferenceETPot = False -# lapse rates: -temperLapseRateNC = global_05min/meteo/downscaling_from_30min/temperature_slope.nc -precipLapseRateNC = global_05min/meteo/downscaling_from_30min/precipitation_slope.nc - -# downscaling criteria (TODO: remove these): -temperatCorrelNC = global_05min/meteo/downscaling_from_30min/temperature_correl.nc -precipitCorrelNC = global_05min/meteo/downscaling_from_30min/precipitation_correl.nc - -# windows length (unit: arc-degree) for smoothing/averaging forcing data (not recommended): +# Downscaling (based on the digital elevation model): +# the downscaling will be performed by providing the "cellIds" (meteoDownscaleIds) of lower resolution cells. +meteoDownscaleIds = global_05min/meteo/downscaling_from_30min/uniqueIds_30min.nc +highResolutionDEM = global_05min/meteo/downscaling_from_30min/gtopo05min.nc + +# lapse rates: +temperLapseRateNC = global_05min/meteo/downscaling_from_30min/temperature_slope.nc +precipLapseRateNC = global_05min/meteo/downscaling_from_30min/precipitation_slope.nc + +# downscaling criteria (TODO: remove these): +temperatCorrelNC = global_05min/meteo/downscaling_from_30min/temperature_correl.nc +precipitCorrelNC = global_05min/meteo/downscaling_from_30min/precipitation_correl.nc + +# windows length (unit: arc-degree) for smoothing/averaging forcing data (not recommended): smoothingWindowsLength = 0 [landSurfaceOptions] - debugWaterBalance = True - numberOfUpperSoilLayers = 2 -# soil and parameters -# - they are used for all land cover types, unless they are are defined in certain land cover type options -# (e.g. different/various soil types for agriculture areas) +# Soil and parameters +# - they are used for all land cover types, unless they are defined in certain land cover type options +# (e.g. different/various soil types for agriculture areas) topographyNC = global_05min/landSurface/topography/topography_parameters_5_arcmin_october_2015.nc soilPropertiesNC = global_05min/landSurface/soil/soilProperties5ArcMin.nc - +# Irrigation properties includeIrrigation = True -# netcdf time series for historical expansion of irrigation areas (unit: hectares). -# Note: The resolution of this map must be consisten with the resolution of cellArea. +# - netcdf time series for historical expansion of irrigation areas (unit: hectares). +# (note: The resolution of this map must be consisten with the resolution of cellArea) historicalIrrigationArea = global_05min/waterUse/irrigation/irrigated_areas/irrigationArea05ArcMin.nc -# a pcraster map/value defining irrigation efficiency (dimensionless) - optional +# - pcraster map/value defining irrigation efficiency (dimensionless) - optional irrigationEfficiency = global_30min/waterUse/irrigation/irrigation_efficiency/efficiency.nc +# Water quality: water temperature, biochemical oxigen demand, salinity, fecal coliforms considerWaterQuality = True -inputFileBOD = /scratch/sutan101/data/dynqual/organic_monthlyAvg_1980_2019_without_time_bnds.nc -# threshold for BOD unit: mg/L -thresholdBODForIrrigation = 15. +# - input files +inputFileSwTemperature = None +inputFileBOD = /scratch/sutan101/data/dynqual/organic_monthlyAvg_1980_2019_without_time_bnds.nc +inputFileTotalDissolvedSolid = None +inputFileFecalColiform = None + +# - water constituents' threshold values +# -- Biochemical Oxigen Demand (mg/L) +thresholdBODForIrrigation = 15. +thresholdBODForLivestock = None +thresholdBODForIndustrial = 20. +thresholdBODForDomestic = 5. + +# Water demands: domestic, industrial and livestock water demand data (unit must be in m.day-1) includeDomesticWaterDemand = True includeIndustryWaterDemand = True includeLivestockWaterDemand = True -# domestic, industrial and livestock water demand data (unit must be in m.day-1) domesticWaterDemandFile = global_05min/waterUse/waterDemand/domestic/domestic_water_demand_version_april_2015.nc industryWaterDemandFile = global_05min/waterUse/waterDemand/industry/industry_water_demand_version_april_2015.nc livestockWaterDemandFile = global_05min/waterUse/waterDemand/livestock/livestock_water_demand_version_april_2015.nc - -# desalination water supply (maximum/potential/capacity) +# Desalination water supply (maximum/potential/capacity) desalinationWater = global_05min/waterUse/desalination/desalination_water_version_april_2015.nc - -# zone IDs (scale) at which allocations of groundwater and surface water (as well as desalinated water) are performed +# Pooling zones: zone IDs (scale) at which allocations of groundwater and surface water (as well as desalinated water) are performed allocationSegmentsForGroundSurfaceWater = global_05min/waterUse/abstraction_zones/abstraction_zones_60min_05min.nc - # pcraster maps defining the partitioning of groundwater - surface water source -# + # - predefined surface water - groundwater partitioning for irrigation demand (e.g. based on Siebert, Global Map of Irrigation Areas version 5) irrigationSurfaceWaterAbstractionFractionData = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_SWFRAC.nc # -- quality map irrigationSurfaceWaterAbstractionFractionDataQuality = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_QUAL.nc -# + # - threshold values defining the preference for surface water source for irrigation purpose # -- treshold to maximize surface water irrigation use (cells with irrSurfaceWaterAbstractionFraction above this will prioritize irrigation surface water use) treshold_to_maximize_irrigation_surface_water = 0.50 # -- treshold to minimize fossil water withdrawal for irrigation (cells with irrSurfaceWaterAbstractionFraction below this have no fossil withdrawal for irrigation) treshold_to_minimize_fossil_groundwater_irrigation = 0.70 -# + # - predefined surface water - groundwater partitioning for non irrigation demand (e.g. based on McDonald, 2014) maximumNonIrrigationSurfaceWaterAbstractionFractionData = global_30min/waterUse/source_partitioning/surface_water_fraction_for_non_irrigation/max_city_sw_fraction.nc [forestOptions] - name = forest debugWaterBalance = True @@ -164,6 +165,7 @@ interceptCapNC = global_05min/landSurface/landCover/naturalTall/interceptCapI coverFractionNC = global_05min/landSurface/landCover/naturalTall/coverFractionInputForest.nc landCoverMapsNC = None + # If NC file is not provided, we have to provide the following pcraster maps: fracVegCover = global_05min/landSurface/landCover/naturalTall/vegf_tall.nc minSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/minf_tall_permafrost.nc @@ -172,7 +174,7 @@ rootFraction1 = global_05min/landSurface/landCover/naturalTall/rfrac1_tall.n rootFraction2 = global_05min/landSurface/landCover/naturalTall/rfrac2_tall.nc maxRootDepth = 1.0 # Note: The maxRootDepth is not used for non irrigated land cover type. -# + # Parameters for the Arno's scheme: arnoBeta = None # If arnoBeta is defined, the soil water capacity distribution is based on this. @@ -189,7 +191,6 @@ interflowIni = global_05min/initialConditions/non-natural/1999/interflow_for [grasslandOptions] - name = grassland debugWaterBalance = True @@ -216,8 +217,8 @@ maxSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/maxf_short.n rootFraction1 = global_05min/landSurface/landCover/naturalShort/rfrac1_short.nc rootFraction2 = global_05min/landSurface/landCover/naturalShort/rfrac2_short.nc maxRootDepth = 0.5 -# Note: The maxRootDepth is not used for non irrigated land cover type. -# +# Note: The maxRootDepth is not used for non irrigated land cover type. + # Parameters for the Arno's scheme: arnoBeta = None # If arnoBeta is defined, the soil water capacity distribution is based on this. @@ -234,7 +235,6 @@ interflowIni = global_05min/initialConditions/non-natural/1999/interflow_gra [irrPaddyOptions] - name = irrPaddy debugWaterBalance = True @@ -244,7 +244,8 @@ freezingT = 0.0 degreeDayFactor = 0.0025 snowWaterHoldingCap = 0.1 refreezingCoeff = 0.05 -# + +# land cover map landCoverMapsNC = None # If NC file is not provided, we have to provide the following values: fracVegCover = global_05min/landSurface/landCover/irrPaddy/fractionPaddy.nc @@ -253,12 +254,12 @@ maxSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/maxf_paddy.nc rootFraction1 = global_30min/landSurface/landCover/irrPaddy/rfrac1_paddy.nc rootFraction2 = global_30min/landSurface/landCover/irrPaddy/rfrac2_paddy.nc maxRootDepth = 0.5 -# + # Parameters for the Arno's scheme: arnoBeta = None # If arnoBeta is defined, the soil water capacity distribution is based on this. # If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. -# + # other paramater values minTopWaterLayer = 0.05 minCropKC = 0.2 @@ -278,7 +279,6 @@ interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irr [irrNonPaddyOptions] - name = irrNonPaddy debugWaterBalance = True @@ -297,12 +297,12 @@ maxSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/maxf_nonpaddy. rootFraction1 = global_30min/landSurface/landCover/irrNonPaddy/rfrac1_nonpaddy.nc rootFraction2 = global_30min/landSurface/landCover/irrNonPaddy/rfrac2_nonpaddy.nc maxRootDepth = 1.0 -# + # Parameters for the Arno's scheme: arnoBeta = None # If arnoBeta is defined, the soil water capacity distribution is based on this. # If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. -# + # other paramater values minTopWaterLayer = 0.0 minCropKC = 0.2 @@ -322,14 +322,11 @@ interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irr - [groundwaterOptions] - debugWaterBalance = True -groundwaterPropertiesNC = global_05min/groundwater/properties/groundwaterProperties5ArcMin.nc # The file will containspecificYield (m3.m-3), kSatAquifer (m.day-1), recessionCoeff (day-1) -# +groundwaterPropertiesNC = global_05min/groundwater/properties/groundwaterProperties5ArcMin.nc # - minimum value for groundwater recession coefficient (day-1) minRecessionCoeff = 1.0e-4 @@ -337,6 +334,7 @@ minRecessionCoeff = 1.0e-4 limitFossilGroundWaterAbstraction = True estimateOfRenewableGroundwaterCapacity = 0.0 estimateOfTotalGroundwaterThickness = global_05min/groundwater/aquifer_thickness_estimate/thickness_05min.nc + # minimum and maximum total groundwater thickness minimumTotalGroundwaterThickness = 100. maximumTotalGroundwaterThickness = None @@ -347,14 +345,14 @@ pumpingCapacityNC = global_30min/waterUse/groundwater_pumping_capacity/regional_ # initial conditions: storGroundwaterIni = global_05min/initialConditions/non-natural/1999/storGroundwater_1999-12-31.nc storGroundwaterFossilIni = global_05min/initialConditions/non-natural/1999/storGroundwaterFossil_1999-12-31.nc -# + # additional initial conditions for pumping behaviors avgNonFossilGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationLong_1999-12-31.nc avgNonFossilGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationShort_1999-12-31.nc avgTotalGroundwaterAbstractionIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAbstraction_1999-12-31.nc avgTotalGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationLong_1999-12-31.nc avgTotalGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationShort_1999-12-31.nc -# + # additional initial conditions (needed only for MODFLOW run) relativeGroundwaterHeadIni = global_05min/initialConditions/non-natural/1999/relativeGroundwaterHead_1999-12-31.nc baseflowIni = global_05min/initialConditions/non-natural/1999/baseflow_1999-12-31.nc @@ -365,7 +363,6 @@ allocationSegmentsForGroundwater = global_05min/waterUse/abstraction_zones/abstr [routingOptions] - debugWaterBalance = True # drainage direction map @@ -387,7 +384,6 @@ dynamicFloodPlain = True # manning coefficient for floodplain floodplainManningsN = 0.07 - # channel gradient gradient = global_05min/routing/channel_properties/channel_gradient.nc @@ -401,63 +397,60 @@ constantChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc minimumChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc # channel properties for flooding +# - if None, it will be estimated from (bankfull) channel depth (m) and width (m) bankfullCapacity = None -# - If None, it will be estimated from (bankfull) channel depth (m) and width (m) - # files for relative elevation (above minimum dem) relativeElevationFiles = global_05min/routing/channel_properties/dzRel%04d.nc relativeElevationLevels = 0.0, 0.01, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00 - # composite crop factors for WaterBodies: cropCoefficientWaterNC = global_30min/routing/kc_surface_water/cropCoefficientForOpenWater.nc minCropWaterKC = 1.00 - # lake and reservoir parameters waterBodyInputNC = global_05min/routing/surface_water_bodies/waterBodies5ArcMin.nc onlyNaturalWaterBodies = False - # initial conditions: -waterBodyStorageIni = global_05min/initialConditions/non-natural/1999/waterBodyStorage_1999-12-31.nc -channelStorageIni = global_05min/initialConditions/non-natural/1999/channelStorage_1999-12-31.nc -readAvlChannelStorageIni = global_05min/initialConditions/non-natural/1999/readAvlChannelStorage_1999-12-31.nc -avgDischargeLongIni = global_05min/initialConditions/non-natural/1999/avgDischargeLong_1999-12-31.nc -avgDischargeShortIni = global_05min/initialConditions/non-natural/1999/avgDischargeShort_1999-12-31.nc -m2tDischargeLongIni = global_05min/initialConditions/non-natural/1999/m2tDischargeLong_1999-12-31.nc +waterBodyStorageIni = global_05min/initialConditions/non-natural/1999/waterBodyStorage_1999-12-31.nc +channelStorageIni = global_05min/initialConditions/non-natural/1999/channelStorage_1999-12-31.nc +readAvlChannelStorageIni = global_05min/initialConditions/non-natural/1999/readAvlChannelStorage_1999-12-31.nc +avgDischargeLongIni = global_05min/initialConditions/non-natural/1999/avgDischargeLong_1999-12-31.nc +avgDischargeShortIni = global_05min/initialConditions/non-natural/1999/avgDischargeShort_1999-12-31.nc +m2tDischargeLongIni = global_05min/initialConditions/non-natural/1999/m2tDischargeLong_1999-12-31.nc avgBaseflowLongIni = global_05min/initialConditions/non-natural/1999/avgBaseflowLong_1999-12-31.nc -riverbedExchangeIni = global_05min/initialConditions/non-natural/1999/riverbedExchange_1999-12-31.nc -# +riverbedExchangeIni = global_05min/initialConditions/non-natural/1999/riverbedExchange_1999-12-31.nc + # initial condition of sub-time step discharge (needed for estimating number of time steps in kinematic wave methods) -subDischargeIni = global_05min/initialConditions/non-natural/1999/subDischarge_1999-12-31.nc -# +subDischargeIni = global_05min/initialConditions/non-natural/1999/subDischarge_1999-12-31.nc + avgLakeReservoirInflowShortIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirInflowShort_1999-12-31.nc avgLakeReservoirOutflowLongIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirOutflowLong_1999-12-31.nc -# + # number of days (timesteps) that have been performed for spinning up initial conditions in the routing module (i.e. channelStorageIni, avgDischargeLongIni, avgDischargeShortIni, etc.) -timestepsToAvgDischargeIni = global_05min/initialConditions/non-natural/1999/timestepsToAvgDischarge_1999-12-31.nc +timestepsToAvgDischargeIni = global_05min/initialConditions/non-natural/1999/timestepsToAvgDischarge_1999-12-31.nc # Note that: # - maximum number of days (timesteps) to calculate long term average flow values (default: 5 years = 5 * 365 days = 1825) # - maximum number of days (timesteps) to calculate short term average values (default: 1 month = 1 * 30 days = 30) - [reportingOptions] - # output files that will be written in the disk in netcdf files: # - daily resolution outDailyTotNC = discharge,totalRunoff,gwRecharge,totalGroundwaterAbstraction,surfaceWaterStorage + # - monthly resolution outMonthTotNC = actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,totLandSurfaceActuaET,totalLandSurfacePotET,waterBodyActEvaporation,waterBodyPotEvaporation outMonthAvgNC = discharge,temperature,dynamicFracWat,surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalActiveStorageThickness,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage outMonthEndNC = storGroundwater,storGroundwaterFossil,waterBodyStorage,channelStorage,totalWaterStorageThickness,totalActiveStorageThickness + # - annual resolution outAnnuaTotNC = totalEvaporation,precipitation,gwRecharge,totalRunoff,baseflow,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,totalGroundwaterAbstraction,totalAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,runoff,actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,irrigationWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,precipitation_at_irrigation,netLqWaterToSoil_at_irrigation,evaporation_from_irrigation,transpiration_from_irrigation,referencePotET outAnnuaAvgNC = temperature,discharge,surfaceWaterStorage,waterBodyStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage,fractionWaterBodyEvaporation,fractionTotalEvaporation,fracSurfaceWaterAllocation,fracDesalinatedWaterAllocation,gwRecharge outAnnuaEndNC = surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness + # - monthly and annual maxima outMonthMaxNC = channelStorage,dynamicFracWat,floodVolume,floodDepth,surfaceWaterLevel,discharge,totalRunoff outAnnuaMaxNC = None @@ -465,6 +458,3 @@ outAnnuaMaxNC = None # netcdf format and zlib setup formatNetCDF = NETCDF4 zlib = True - - - From f8a7080d3a684b2e0372289f86aa94fca5997bb9 Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Wed, 15 Mar 2023 23:49:34 +0100 Subject: [PATCH 24/31] fixing None values in threshold --- ..._rhine-meuse_water_quality_development.ini | 6 +- model/landCover.py | 61 +++--------- model/surfaceWaterQuality.py | 97 +++++++++---------- 3 files changed, 65 insertions(+), 99 deletions(-) diff --git a/config/setup_05min_rhine-meuse_water_quality_development.ini b/config/setup_05min_rhine-meuse_water_quality_development.ini index 17f308b2e..1b2e2fc67 100644 --- a/config/setup_05min_rhine-meuse_water_quality_development.ini +++ b/config/setup_05min_rhine-meuse_water_quality_development.ini @@ -94,7 +94,7 @@ includeIrrigation = True # (note: The resolution of this map must be consisten with the resolution of cellArea) historicalIrrigationArea = global_05min/waterUse/irrigation/irrigated_areas/irrigationArea05ArcMin.nc -# - pcraster map/value defining irrigation efficiency (dimensionless) - optional +# - pcraster map/value defining irrigation efficiency (dimensionless) - optional irrigationEfficiency = global_30min/waterUse/irrigation/irrigation_efficiency/efficiency.nc # Water quality: water temperature, biochemical oxigen demand, salinity, fecal coliforms @@ -438,8 +438,8 @@ timestepsToAvgDischargeIni = global_05min/initialConditions/non-natural/1999/ [reportingOptions] # output files that will be written in the disk in netcdf files: -# - daily resolution -outDailyTotNC = discharge,totalRunoff,gwRecharge,totalGroundwaterAbstraction,surfaceWaterStorage +# - daily resolution (discharge,totalRunoff,gwRecharge,totalGroundwaterAbstraction,surfaceWaterStorage) +outDailyTotNC = None # - monthly resolution outMonthTotNC = actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,totLandSurfaceActuaET,totalLandSurfacePotET,waterBodyActEvaporation,waterBodyPotEvaporation diff --git a/model/landCover.py b/model/landCover.py index 55b34724b..b3ca44fab 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1851,33 +1851,31 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # Note the variable "surface_water_demand" is the estimate of surface water demand for all sectors (total) based on Siebert et al.; McDonald et al.; de Graaf et al. - if self.consider_water_quality: - logger.info("Surface water allocation to meet demand will consider water quality.") - # Input # - available_surface_water (without considering quality) - available_surface_water_without_qual = pcr.max(0.00, routing.readAvlChannelStorage) + available_surface_water = pcr.max(0.00, routing.readAvlChannelStorage) # - list of water_quality_constituents wq_constituent = ["sw_temperatur", "bio_o2_demand", "total_dissolved_solid", "fecal_coliform"] # - list of water demand sectors - wd_sector = ["irrigation", "livestock", "industrial", "domestic"] - + wd_sector = ["irrigation", "livestock", "industrial", "domestic"] + # - the estimates of surface water demand for every sector, after the above schemes (Siebert et al.; McDonald et al.; de Graaf et al.) sectoral_surface_water_demand = {} - sectoral_surface_water_demand["irrigation"] = correctedRemainingIrrigationLivestock * vos.getValDivZero(remainingIrrigation, remainingLivestock + remainingIrrigation) + sectoral_surface_water_demand["irrigation"] = correctedRemainingIrrigationLivestock * vos.getValDivZero(remainingIrrigation, remainingLivestock + remainingIrrigation) sectoral_surface_water_demand["livestock"] = pcr.max(0.0, correctedRemainingIrrigationLivestock - sectoral_surface_water_demand["irrigation"]) sectoral_surface_water_demand["industrial"] = correctedRemainingIndustrialDomestic * vos.getValDivZero(remainingIndustry, remainingIndustry + remainingDomestic) sectoral_surface_water_demand["domestic"] = pcr.max(0.0, correctedRemainingIndustrialDomestic - sectoral_surface_water_demand["industrial"]) - + # - current/simulate water_quality_concetration_constituent wq_state = {} wq_state["sw_temperatur"] = 25.0 wq_state["bio_o2_demand"] = self.inputBOD wq_state["total_dissolved_solid"] = 0.0 wq_state["fecal_coliform"] = 0.0 + # - water quality thresholds wq_threshold = {} wq_threshold["sw_temperatur"] = {} @@ -1900,36 +1898,11 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ wq_threshold["fecal_coliform"]["livestock"] = None wq_threshold["fecal_coliform"]["industrial"] = None wq_threshold["fecal_coliform"]["domestic"] = None - - - # ~ # - water quality thresholds - # ~ wq_threshold = {} - # ~ wq_threshold["sw_temperatur"] = {} - # ~ wq_threshold["sw_temperatur"]["irrigation"] = 1000000. - # ~ wq_threshold["sw_temperatur"]["livestock"] = 1000000. - # ~ wq_threshold["sw_temperatur"]["industrial"] = 30.0 - # ~ wq_threshold["sw_temperatur"]["domestic"] = 1000000. - # ~ wq_threshold["bio_o2_demand"] = {} - # ~ wq_threshold["bio_o2_demand"]["irrigation"] = 15.0 - # ~ wq_threshold["bio_o2_demand"]["livestock"] = 1000000. - # ~ wq_threshold["bio_o2_demand"]["industrial"] = 30.0 - # ~ wq_threshold["bio_o2_demand"]["domestic"] = 5.0 - # ~ wq_threshold["total_dissolved_solid"] = {} - # ~ wq_threshold["total_dissolved_solid"]["irrigation"] = 450.0 - # ~ wq_threshold["total_dissolved_solid"]["livestock"] = 1000000. - # ~ wq_threshold["total_dissolved_solid"]["industrial"] = 7000. - # ~ wq_threshold["total_dissolved_solid"]["domestic"] = 600. - # ~ wq_threshold["fecal_coliform"] = {} - # ~ wq_threshold["fecal_coliform"]["irrigation"] = 1000000. - # ~ wq_threshold["fecal_coliform"]["livestock"] = 1000000. - # ~ wq_threshold["fecal_coliform"]["industrial"] = 1000000. - # ~ wq_threshold["fecal_coliform"]["domestic"] = 1000000. - totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ - swq.surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, + swq.surface_water_allocation_based_on_quality(available_surface_water, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, self.surfaceWaterPiority, self.usingAllocSegments, self.allocSegments, routing.cellArea, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) - + # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply ################################################################################################################################ # - for irrigation and livestock water demand @@ -1951,19 +1924,13 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ self.actSurfaceWaterAbstract = totalActSurfaceWaterAbstract self.allocSurfaceWaterAbstract = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] + sectoral_surface_water_demand_satisfied["livestock"] + sectoral_surface_water_demand_satisfied["irrigation"] - + else: - - # # if surface water abstraction as the first priority - if self.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination - # - # ~ available_surface_water_volume = pcr.max(0.00, routing.readAvlChannelStorage) - # ~ if self.consider_water_quality == True: - # ~ available_surface_water_volume = pcr.ifthenelse(self.inputBOD < self.thresholdBODForIrrigation, available_surface_water_volume, 0.0) + if self.surfaceWaterPiority: + surface_water_demand = self.totalGrossDemandAfterDesalination if self.usingAllocSegments: # using zone/segment at which supply network is defined - # logger.debug("Allocation of surface water abstraction.") # volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ @@ -1978,7 +1945,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ landmask = self.landmask, ignore_small_values = False, prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) - + self.actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea self.allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea # @@ -1992,8 +1959,8 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ self.allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.allocSurfaceWaterAbstract) ################################################################################################################################ # - end of Abstraction and Allocation of SURFACE WATER - - + + # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply ################################################################################################################################ # - for irrigation and livestock water demand diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 77ff47ae7..17e0bb0fb 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -12,69 +12,74 @@ logger = logging.getLogger(__name__) import virtualOS as vos -from ncConverter import * +#from ncConverter import * - -def surface_water_allocation_based_on_quality(available_surface_water_without_qual, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, +def surface_water_allocation_based_on_quality(available_surface_water, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, surfaceWaterPriority, usingAllocSegments, allocSegments, cellArea, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): + ''' + Where: + available_surface_water: maximum value between 0 and routed flow in channel + sectoral_surface_water_demand: surface water demand estimations for every sector (irrigation, domestic, industrial, livestock) + wd_sector: water demand sectors' names in PCR-GLOBWB (irrigation, domestic, industrial, livestock) + wq_constituent: surface water quality constituents' names from DynQual (water temperature, BOD, salinity/TDS, fecal coliforms) + wq_state: surface water quality constituents' concentrations from DynQual (water temperature, BOD, salinity/TDS, fecal coliforms) + wq_treshold: surface water quality concentration thresholds per constituent and sector + ''' # initial values - # - available surface water before allocation - available_surface_water_with_qual = available_surface_water_without_qual - # - amount of water that is abstracted from the source + # - amount of water that is abstracted from the source: initializing dataset totalActSurfaceWaterAbstract = 0.0 - # - remaining water demand and satisfied water demand + + # - remaining water demand and satisfied water demand: initializing dictionaries water_demand_remaining = {} water_demand_satisfied = {} for sector in wd_sector: water_demand_remaining[sector] = sectoral_surface_water_demand[sector] water_demand_satisfied[sector] = 0.0 - + + # - replacing None values in wq_threshold dictionary with unreachable value (1e20) + for consti in wq_threshold.keys(): + wq_threshold[consti] = {k:v if v is not None else 1e20 for k,v in wq_threshold[consti].items()} + # looping for every constituent for consti in wq_constituent: + sectors = wd_sector.copy() # water quality concentrations water_quality_concetration_consti = wq_state[consti] - - # ordering sectors from less to more stringent - thresholds = wq_threshold[consti] - thresholds = {k: v for k, v in thresholds.items() if v is not None} - thresholds = sorted(thresholds.items(), key=lambda item: item[1], reverse=True) - sector_order = [threshold[0] for threshold in thresholds] - print(sector_order) - # ~ qu + # ordering sectors from more stringent to less stringent + thresholds = wq_threshold[consti] + thresholds = dict(sorted(thresholds.items(), key=lambda item: item[1], reverse=False)) + sectors_ordered = list(thresholds.keys()) # looping for every sector - for sector in sector_order: + for sector_order in sectors_ordered: # defining water quality thresholds - threshold_consti_sector = wq_threshold[consti][sector] + threshold_consti_sector = wq_threshold[consti][sector_order] # defining actual water available depending on constituent threshold - available_surface_water_with_qual_conti_sector = pcr.ifthenelse(water_quality_concetration_consti < threshold_consti_sector, available_surface_water_with_qual, 0.) + available_surface_water_consti_sector = pcr.ifthenelse(water_quality_concetration_consti < threshold_consti_sector, available_surface_water, 0.) # total remaining water demand total_water_demand = 0.0 - for sector in wd_sector: total_water_demand = total_water_demand + water_demand_remaining[sector] - + for sector in wd_sector: + total_water_demand = total_water_demand + water_demand_remaining[sector] surface_water_demand = total_water_demand - # - # TODO: Accomodate the option "surfaceWaterPriority"! - # - available_surface_water_volume = pcr.max(0.00, available_surface_water_with_qual) - msg = "Allocating water that is above the threshold for " + consti + " for the sector " + sector + # water allocation scheme + # [ TODO ] accomodate the option "surfaceWaterPriority"!!!! + msg = "Allocating water that is above the threshold for " + consti + " for the sector " + sector_order logger.debug(msg) if usingAllocSegments: # using zone/segment at which supply network is defined - # logger.debug("Allocation of surface water abstraction.") - # + volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ vos.waterAbstractionAndAllocation( water_demand_volume = surface_water_demand*cellArea,\ - available_water_volume = available_surface_water_volume,\ + available_water_volume = available_surface_water_consti_sector,\ allocation_zones = allocSegments,\ zone_area = segmentArea,\ high_volume_treshold = None,\ @@ -83,39 +88,33 @@ def surface_water_allocation_based_on_quality(available_surface_water_without_qu landmask = landmask, ignore_small_values = False, prioritizing_local_source = prioritizeLocalSourceToMeetWaterDemand) - + actSurfaceWaterAbstract = volActSurfaceWaterAbstract / cellArea allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / cellArea - # - else: + + else: logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") - actSurfaceWaterAbstract = pcr.min(available_surface_water_volume/cellArea,\ - surface_water_demand) # unit: m - allocSurfaceWaterAbstract = actSurfaceWaterAbstract # unit: m - # + actSurfaceWaterAbstract = pcr.min(available_surface_water_volume/cellArea, surface_water_demand) # unit: m + allocSurfaceWaterAbstract = actSurfaceWaterAbstract # unit: m - # - the amount of water that is abstracted from the source (e.g. river, reservoir pixels) + # - the amount of water that is abstracted from the source (e.g. river, reservoir pixels): outgoing water actSurfaceWaterAbstract = pcr.ifthen(landmask, actSurfaceWaterAbstract) - # - the amount of water that is given to pixels with demand (e.g. pixels with irrigation areas) + # - the amount of water that is given to pixels with demand (e.g. pixels with irrigation areas): incoming water allocSurfaceWaterAbstract = pcr.ifthen(landmask, allocSurfaceWaterAbstract) # tracking the total amount of water that is abstracted from the source totalActSurfaceWaterAbstract = totalActSurfaceWaterAbstract + actSurfaceWaterAbstract - # calculating remaining water available - available_surface_water_with_qual = available_surface_water_with_qual - actSurfaceWaterAbstract + available_surface_water = available_surface_water - actSurfaceWaterAbstract # looping for every sector to distribute allocSurfaceWaterAbstract - for sector in wd_sector: - current_water_withdrawal = allocSurfaceWaterAbstract * vos.getValDivZero(water_demand_remaining[sector], total_water_demand) + for sector in sectors: + current_water_withdrawal_sector = allocSurfaceWaterAbstract * vos.getValDivZero(water_demand_remaining[sector], total_water_demand) + # - tracking the water demand: satisficed and remaining - water_demand_remaining[sector] = water_demand_remaining[sector] - current_water_withdrawal - water_demand_satisfied[sector] = water_demand_satisfied[sector] + current_water_withdrawal + water_demand_remaining[sector] = water_demand_remaining[sector] - current_water_withdrawal_sector + water_demand_satisfied[sector] = water_demand_satisfied[sector] + current_water_withdrawal_sector + sectors.remove(sector_order) return totalActSurfaceWaterAbstract, water_demand_satisfied - - - - - From 3b1ee0b5893a84fdb656fd57c168312d4d397895 Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Mon, 20 Mar 2023 17:42:20 +0100 Subject: [PATCH 25/31] seting up all water quality constituents --- model/landCover.py | 79 ++++++++++++++++++------------------ model/landSurface.py | 62 +++++++++++++++++++--------- model/surfaceWaterQuality.py | 5 ++- 3 files changed, 85 insertions(+), 61 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index b3ca44fab..d5fa14e7e 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -931,21 +931,20 @@ def getICsLC(self,iniItems,iniConditions = None): vars(self)[var] = iniConditions[str(var)] vars(self)[var] = pcr.ifthen(self.landmask,vars(self)[var]) - def updateLC(self,meteo,groundwater,routing,\ - capRiseFrac,\ - nonIrrGrossDemandDict,swAbstractionFractionDict,\ - currTimeStep,\ - allocSegments,\ - desalinationWaterUse,\ - groundwater_pumping_region_ids,\ + def updateLC(self,meteo,groundwater,routing, + capRiseFrac, + nonIrrGrossDemandDict,swAbstractionFractionDict, + currTimeStep, + allocSegments, + desalinationWaterUse, + groundwater_pumping_region_ids, regionalAnnualGroundwaterAbstractionLimit, - consider_water_quality = False, - inputBOD = 0.0,\ - thresholdBODForIrrigation = 0.0): + wq_state, wq_threshold, consider_water_quality=False): + # assiging water quality input information self.consider_water_quality = consider_water_quality - self.inputBOD = inputBOD - self.thresholdBODForIrrigation = thresholdBODForIrrigation + self.wq_state = wq_state + self.wq_threshold = wq_threshold # get land cover parameters at the first day of the year or the first day of the simulation if self.noAnnualChangesInLandCoverParameter == False and\ @@ -1858,7 +1857,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # - available_surface_water (without considering quality) available_surface_water = pcr.max(0.00, routing.readAvlChannelStorage) # - list of water_quality_constituents - wq_constituent = ["sw_temperatur", "bio_o2_demand", "total_dissolved_solid", "fecal_coliform"] + wq_constituent = ["sw_temperature", "bio_o2_demand", "tot_dis_solid", "fecal_coliform"] # - list of water demand sectors wd_sector = ["irrigation", "livestock", "industrial", "domestic"] @@ -1870,37 +1869,37 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ sectoral_surface_water_demand["domestic"] = pcr.max(0.0, correctedRemainingIndustrialDomestic - sectoral_surface_water_demand["industrial"]) # - current/simulate water_quality_concetration_constituent - wq_state = {} - wq_state["sw_temperatur"] = 25.0 - wq_state["bio_o2_demand"] = self.inputBOD - wq_state["total_dissolved_solid"] = 0.0 - wq_state["fecal_coliform"] = 0.0 +# wq_state = {} +# wq_state["sw_temperatur"] = 25.0 +# wq_state["bio_o2_demand"] = self.inputBOD +# wq_state["total_dissolved_solid"] = 0.0 +# wq_state["fecal_coliform"] = 0.0 # - water quality thresholds - wq_threshold = {} - wq_threshold["sw_temperatur"] = {} - wq_threshold["sw_temperatur"]["irrigation"] = None - wq_threshold["sw_temperatur"]["livestock"] = None - wq_threshold["sw_temperatur"]["industrial"] = 30.0 - wq_threshold["sw_temperatur"]["domestic"] = None - wq_threshold["bio_o2_demand"] = {} - wq_threshold["bio_o2_demand"]["irrigation"] = 15.0 - wq_threshold["bio_o2_demand"]["livestock"] = None - wq_threshold["bio_o2_demand"]["industrial"] = 30.0 - wq_threshold["bio_o2_demand"]["domestic"] = 5.0 - wq_threshold["total_dissolved_solid"] = {} - wq_threshold["total_dissolved_solid"]["irrigation"] = 450.0 - wq_threshold["total_dissolved_solid"]["livestock"] = None - wq_threshold["total_dissolved_solid"]["industrial"] = 7000. - wq_threshold["total_dissolved_solid"]["domestic"] = 600. - wq_threshold["fecal_coliform"] = {} - wq_threshold["fecal_coliform"]["irrigation"] = None - wq_threshold["fecal_coliform"]["livestock"] = None - wq_threshold["fecal_coliform"]["industrial"] = None - wq_threshold["fecal_coliform"]["domestic"] = None +# wq_threshold = {} +# wq_threshold["sw_temperatur"] = {} +# wq_threshold["sw_temperatur"]["irrigation"] = 1e-20 # None +# wq_threshold["sw_temperatur"]["livestock"] = 1e-20 # None +# wq_threshold["sw_temperatur"]["industrial"] = 1e-20 # 30.0 +# wq_threshold["sw_temperatur"]["domestic"] = 1e-20 # None +# wq_threshold["bio_o2_demand"] = {} +# wq_threshold["bio_o2_demand"]["irrigation"] = 1e-20 # 15.0 +# wq_threshold["bio_o2_demand"]["livestock"] = 1e-20 # None +# wq_threshold["bio_o2_demand"]["industrial"] = 1e-20 # 30.0 +# wq_threshold["bio_o2_demand"]["domestic"] = 1e-20 # 5.0 +# wq_threshold["total_dissolved_solid"] = {} +# wq_threshold["total_dissolved_solid"]["irrigation"] = 1e-20 # 450.0 +# wq_threshold["total_dissolved_solid"]["livestock"] = 1e-20 # None +# wq_threshold["total_dissolved_solid"]["industrial"] = 1e-20 # 7000. +# wq_threshold["total_dissolved_solid"]["domestic"] = 1e-20 # 600. +# wq_threshold["fecal_coliform"] = {} +# wq_threshold["fecal_coliform"]["irrigation"] = 1e-20 # None +# wq_threshold["fecal_coliform"]["livestock"] = 1e-20 # None +# wq_threshold["fecal_coliform"]["industrial"] = 1e-20 # None +# wq_threshold["fecal_coliform"]["domestic"] = 1e-20 # None totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ - swq.surface_water_allocation_based_on_quality(available_surface_water, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, + swq.surface_water_allocation_based_on_quality(available_surface_water, wq_constituent, wd_sector, sectoral_surface_water_demand, self.wq_state, self.wq_threshold, self.surfaceWaterPiority, self.usingAllocSegments, self.allocSegments, routing.cellArea, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply diff --git a/model/landSurface.py b/model/landSurface.py index a8fcf1983..38e92db08 100644 --- a/model/landSurface.py +++ b/model/landSurface.py @@ -379,17 +379,46 @@ def __init__(self,iniItems,landmask,initialState=None): self.landCoverObj[coverType].irrTypeFracOverIrr = vos.getValDivZero(self.landCoverObj[coverType].fracVegCover,\ totalIrrAreaFrac, vos.smallNumber) - - # option to consider water quality + ############################################################################################################################################# + # Surface water quality option: water allocation based on + # + # Evaluating if surface water quality will be considered for water allocation self.consider_water_quality = False if ("considerWaterQuality" in list(iniItems.landSurfaceOptions.keys()) and iniItems.landSurfaceOptions["considerWaterQuality"] == "True"): logger.info('Water use in this model run considers water quality.') self.consider_water_quality = True - # - input files and values (e.g. thresholds) that are related to water quality - self.inputFileBOD = iniItems.landSurfaceOptions["inputFileBOD"] - self.thresholdBODForIrrigation = float(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) - - + + # - input files with surface water quality concentrations + self.inputFileSWT = iniItems.landSurfaceOptions["inputFileSWTemperature"] + self.inputFileBOD = iniItems.landSurfaceOptions["inputFileBiochemOxigenDemand"] + self.inputFileTDS = iniItems.landSurfaceOptions["inputFileTotalDissolvedSolid"] + self.inputFileFC = iniItems.landSurfaceOptions["inputFileFecalColiform"] + + # - threshold values of water quality constituents + # self.thresholdBODForIrrigation = float(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) + self.wq_threshold = {} + self.wq_threshold["sw_temperature"] = {} + self.wq_threshold["sw_temperature"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) + self.wq_threshold["sw_temperature"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) + self.wq_threshold["sw_temperature"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) + self.wq_threshold["sw_temperature"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) + self.wq_threshold["bio_o2_demand"] = {} + self.wq_threshold["bio_o2_demand"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) + self.wq_threshold["bio_o2_demand"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdBODForLivestock"]) + self.wq_threshold["bio_o2_demand"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdBODForDomestic"]) + self.wq_threshold["bio_o2_demand"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdBODForIndustrial"]) + self.wq_threshold["tot_dis_solid"] = {} + self.wq_threshold["tot_dis_solid"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) + self.wq_threshold["tot_dis_solid"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) + self.wq_threshold["tot_dis_solid"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) + self.wq_threshold["tot_dis_solid"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) + self.wq_threshold["fecal_coliform"] = {} + self.wq_threshold["fecal_coliform"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) + self.wq_threshold["fecal_coliform"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) + self.wq_threshold["fecal_coliform"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) + self.wq_threshold["fecal_coliform"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) + # + ############################################################################################################################################# # get the initial conditions (for every land cover type) self.getInitialConditions(iniItems, initialState) @@ -1244,15 +1273,12 @@ def scaleDynamicIrrigation(self,yearInInteger): def update(self,meteo,groundwater,routing,currTimeStep): # updating any information related to water quality - if self.consider_water_quality == True: - - # - read BOD for every time step - self.inputBOD = vos.netcdf2PCRobjClone(ncFile = self.inputFileBOD,\ - varName = "automatic" , \ - dateInput = currTimeStep.fulldate,\ - useDoy = "daily", - cloneMapFileName = self.cloneMap) - + if self.consider_water_quality: + self.wq_state = {} + self.wq_state["sw_temperature"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileSWT, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) + self.wq_state["bio_o2_demand"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileBOD, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) + self.wq_state["tot_dis_solid"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileTDS, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) + self.wq_state["fecal_coliform"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileFC , varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) # updating regional groundwater abstraction limit (at the begining of the year or at the beginning of simulation) if groundwater.limitRegionalAnnualGroundwaterAbstraction: @@ -1457,9 +1483,7 @@ def update(self,meteo,groundwater,routing,currTimeStep): self.allocSegments,\ self.desalinationWaterUse,\ self.groundwater_pumping_region_ids,self.regionalAnnualGroundwaterAbstractionLimit,\ - self.consider_water_quality, - self.inputBOD, - self.thresholdBODForIrrigation) + self.wq_state, self.wq_threshold, self.consider_water_quality) # TODO: Please organize how we will deal with water quality problems (e.g. self.inputBOD) # first, we set all aggregated values/variables to zero: diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 17e0bb0fb..2cba25728 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -39,6 +39,7 @@ def surface_water_allocation_based_on_quality(available_surface_water, wq_consti # - replacing None values in wq_threshold dictionary with unreachable value (1e20) for consti in wq_threshold.keys(): + print(consti) wq_threshold[consti] = {k:v if v is not None else 1e20 for k,v in wq_threshold[consti].items()} # looping for every constituent @@ -60,7 +61,7 @@ def surface_water_allocation_based_on_quality(available_surface_water, wq_consti threshold_consti_sector = wq_threshold[consti][sector_order] # defining actual water available depending on constituent threshold - available_surface_water_consti_sector = pcr.ifthenelse(water_quality_concetration_consti < threshold_consti_sector, available_surface_water, 0.) + available_surface_water_consti_sector = pcr.ifthenelse((water_quality_concetration_consti < threshold_consti_sector) | (pcr.pcrnot(pcr.defined(water_quality_concetration_consti))), available_surface_water, 0.) # total remaining water demand total_water_demand = 0.0 @@ -117,4 +118,4 @@ def surface_water_allocation_based_on_quality(available_surface_water, wq_consti water_demand_satisfied[sector] = water_demand_satisfied[sector] + current_water_withdrawal_sector sectors.remove(sector_order) - return totalActSurfaceWaterAbstract, water_demand_satisfied + return totalActSurfaceWaterAbstract, water_demand_satisfied \ No newline at end of file From bb05de99de8fc2dbc9ff0fcc59461d8d08a48519 Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Tue, 21 Mar 2023 10:59:30 +0100 Subject: [PATCH 26/31] run consider_water_quality False --- model/landCover.py | 8 ++++---- model/landSurface.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index d5fa14e7e..91a9c3e02 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1849,13 +1849,13 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ surface_water_demand = correctedSurfaceWaterDemandEstimate # Note the variable "surface_water_demand" is the estimate of surface water demand for all sectors (total) based on Siebert et al.; McDonald et al.; de Graaf et al. - + # available_surface_water (without considering quality) + available_surface_water = pcr.max(0.00, routing.readAvlChannelStorage) + if self.consider_water_quality: logger.info("Surface water allocation to meet demand will consider water quality.") # Input - # - available_surface_water (without considering quality) - available_surface_water = pcr.max(0.00, routing.readAvlChannelStorage) # - list of water_quality_constituents wq_constituent = ["sw_temperature", "bio_o2_demand", "tot_dis_solid", "fecal_coliform"] # - list of water demand sectors @@ -1935,7 +1935,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ vos.waterAbstractionAndAllocation( water_demand_volume = surface_water_demand*routing.cellArea,\ - available_water_volume = available_surface_water_volume,\ + available_water_volume = available_surface_water,\ allocation_zones = allocSegments,\ zone_area = self.segmentArea,\ high_volume_treshold = None,\ diff --git a/model/landSurface.py b/model/landSurface.py index 38e92db08..e5f5ace4b 100644 --- a/model/landSurface.py +++ b/model/landSurface.py @@ -384,6 +384,7 @@ def __init__(self,iniItems,landmask,initialState=None): # # Evaluating if surface water quality will be considered for water allocation self.consider_water_quality = False + self.wq_threshold = {} if ("considerWaterQuality" in list(iniItems.landSurfaceOptions.keys()) and iniItems.landSurfaceOptions["considerWaterQuality"] == "True"): logger.info('Water use in this model run considers water quality.') self.consider_water_quality = True @@ -396,7 +397,6 @@ def __init__(self,iniItems,landmask,initialState=None): # - threshold values of water quality constituents # self.thresholdBODForIrrigation = float(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) - self.wq_threshold = {} self.wq_threshold["sw_temperature"] = {} self.wq_threshold["sw_temperature"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) self.wq_threshold["sw_temperature"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) @@ -1273,8 +1273,8 @@ def scaleDynamicIrrigation(self,yearInInteger): def update(self,meteo,groundwater,routing,currTimeStep): # updating any information related to water quality + self.wq_state = {} if self.consider_water_quality: - self.wq_state = {} self.wq_state["sw_temperature"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileSWT, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) self.wq_state["bio_o2_demand"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileBOD, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) self.wq_state["tot_dis_solid"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileTDS, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) From 70e8f4da7610d382f6ab3cf0b155b52942b6e70c Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Thu, 23 Mar 2023 09:42:08 +0100 Subject: [PATCH 27/31] update --- model/landCover.py | 9 ++--- model/landSurface.py | 64 ++++++++++++++++++++++++------------ model/surfaceWaterQuality.py | 40 ++++++++++++---------- 3 files changed, 70 insertions(+), 43 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index 91a9c3e02..e97cef8e6 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1856,10 +1856,11 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ logger.info("Surface water allocation to meet demand will consider water quality.") # Input - # - list of water_quality_constituents - wq_constituent = ["sw_temperature", "bio_o2_demand", "tot_dis_solid", "fecal_coliform"] # - list of water demand sectors - wd_sector = ["irrigation", "livestock", "industrial", "domestic"] + # wd_sector = ["irrigation", "domestic", "industrial", "livestock"] + # - list of water_quality_constituents + # wq_constituent = ["sw_temperature", "bio_o2_demand", "tot_dis_solid", "fecal_coliform"] + wq_constituent = list(wq_thresholds[wd_sector[0]].keys()) # - the estimates of surface water demand for every sector, after the above schemes (Siebert et al.; McDonald et al.; de Graaf et al.) sectoral_surface_water_demand = {} @@ -1899,7 +1900,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # wq_threshold["fecal_coliform"]["domestic"] = 1e-20 # None totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ - swq.surface_water_allocation_based_on_quality(available_surface_water, wq_constituent, wd_sector, sectoral_surface_water_demand, self.wq_state, self.wq_threshold, + swq.surface_water_allocation_based_on_quality(available_surface_water, sectoral_surface_water_demand, self.wq_state, self.wq_threshold, self.surfaceWaterPiority, self.usingAllocSegments, self.allocSegments, routing.cellArea, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply diff --git a/model/landSurface.py b/model/landSurface.py index e5f5ace4b..6a0bd3bf0 100644 --- a/model/landSurface.py +++ b/model/landSurface.py @@ -380,7 +380,7 @@ def __init__(self,iniItems,landmask,initialState=None): totalIrrAreaFrac, vos.smallNumber) ############################################################################################################################################# - # Surface water quality option: water allocation based on + # Surface water quality option: Water allocation based on sectoral water quality requirements # # Evaluating if surface water quality will be considered for water allocation self.consider_water_quality = False @@ -397,26 +397,48 @@ def __init__(self,iniItems,landmask,initialState=None): # - threshold values of water quality constituents # self.thresholdBODForIrrigation = float(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) - self.wq_threshold["sw_temperature"] = {} - self.wq_threshold["sw_temperature"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) - self.wq_threshold["sw_temperature"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) - self.wq_threshold["sw_temperature"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) - self.wq_threshold["sw_temperature"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) - self.wq_threshold["bio_o2_demand"] = {} - self.wq_threshold["bio_o2_demand"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) - self.wq_threshold["bio_o2_demand"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdBODForLivestock"]) - self.wq_threshold["bio_o2_demand"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdBODForDomestic"]) - self.wq_threshold["bio_o2_demand"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdBODForIndustrial"]) - self.wq_threshold["tot_dis_solid"] = {} - self.wq_threshold["tot_dis_solid"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) - self.wq_threshold["tot_dis_solid"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) - self.wq_threshold["tot_dis_solid"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) - self.wq_threshold["tot_dis_solid"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) - self.wq_threshold["fecal_coliform"] = {} - self.wq_threshold["fecal_coliform"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) - self.wq_threshold["fecal_coliform"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) - self.wq_threshold["fecal_coliform"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) - self.wq_threshold["fecal_coliform"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) +# self.wq_threshold["sw_temperature"] = {} +# self.wq_threshold["sw_temperature"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) +# self.wq_threshold["sw_temperature"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdSWTForLivestock"]) +# self.wq_threshold["sw_temperature"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdSWTForDomestic"]) +# self.wq_threshold["sw_temperature"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIndustrial"]) +# self.wq_threshold["bio_o2_demand"] = {} +# self.wq_threshold["bio_o2_demand"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) +# self.wq_threshold["bio_o2_demand"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdBODForLivestock"]) +# self.wq_threshold["bio_o2_demand"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdBODForDomestic"]) +# self.wq_threshold["bio_o2_demand"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdBODForIndustrial"]) +# self.wq_threshold["tot_dis_solid"] = {} +# self.wq_threshold["tot_dis_solid"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) +# self.wq_threshold["tot_dis_solid"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdTDSForLivestock"]) +# self.wq_threshold["tot_dis_solid"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdTDSForDomestic"]) +# self.wq_threshold["tot_dis_solid"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIndustrial"]) +# self.wq_threshold["fecal_coliform"] = {} +# self.wq_threshold["fecal_coliform"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) +# self.wq_threshold["fecal_coliform"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdFCForLivestock"]) +# self.wq_threshold["fecal_coliform"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdFCForDomestic"]) +# self.wq_threshold["fecal_coliform"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdFCForIndustrial"]) + + self.wq_threshold["irrigation"] = {} + self.wq_threshold["irrigation"]["sw_temperature"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) + self.wq_threshold["irrigation"]["bio_o2_demand"] = eval(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) + self.wq_threshold["irrigation"]["tot_dis_solid"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) + self.wq_threshold["irrigation"]["fecal_coliform"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) + self.wq_threshold["livestock"] = {} + self.wq_threshold["livestock"]["sw_temperature"] = eval(iniItems.landSurfaceOptions["thresholdSWTForLivestock"]) + self.wq_threshold["livestock"]["bio_o2_demand"] = eval(iniItems.landSurfaceOptions["thresholdBODForLivestock"]) + self.wq_threshold["livestock"]["tot_dis_solid"] = eval(iniItems.landSurfaceOptions["thresholdTDSForLivestock"]) + self.wq_threshold["livestock"]["fecal_coliform"] = eval(iniItems.landSurfaceOptions["thresholdFCForLivestock"]) + self.wq_threshold["domestic"] = {} + self.wq_threshold["domestic"]["sw_temperature"] = eval(iniItems.landSurfaceOptions["thresholdSWTForDomestic"]) + self.wq_threshold["domestic"]["bio_o2_demand"] = eval(iniItems.landSurfaceOptions["thresholdBODForDomestic"]) + self.wq_threshold["domestic"]["tot_dis_solid"] = eval(iniItems.landSurfaceOptions["thresholdTDSForDomestic"]) + self.wq_threshold["domestic"]["fecal_coliform"] = eval(iniItems.landSurfaceOptions["thresholdFCForDomestic"]) + self.wq_threshold["industrial"] = {} + self.wq_threshold["industrial"]["sw_temperature"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIndustrial"]) + self.wq_threshold["industrial"]["bio_o2_demand"] = eval(iniItems.landSurfaceOptions["thresholdBODForIndustrial"]) + self.wq_threshold["industrial"]["tot_dis_solid"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIndustrial"]) + self.wq_threshold["industrial"]["fecal_coliform"] = eval(iniItems.landSurfaceOptions["thresholdFCForIndustrial"]) + # # ############################################################################################################################################# diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index 2cba25728..cdf6d4497 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -14,33 +14,37 @@ import virtualOS as vos #from ncConverter import * -def surface_water_allocation_based_on_quality(available_surface_water, wq_constituent, wd_sector, sectoral_surface_water_demand, wq_state, wq_threshold, - surfaceWaterPriority, usingAllocSegments, allocSegments, cellArea, segmentArea, landmask, prioritizeLocalSourceToMeetWaterDemand, currTimeStep): +def surface_water_allocation_based_on_quality(availableSurfaceWater, sectoralSurfaceWaterDemand, waterQualityStates, waterQualityThresholds, + surfaceWaterPriority, usingAllocSegments, allocSegments, cellArea, segmentArea, landmask, + prioritizeLocalSourceToMeetWaterDemand, currTimeStep): ''' Where: - available_surface_water: maximum value between 0 and routed flow in channel - sectoral_surface_water_demand: surface water demand estimations for every sector (irrigation, domestic, industrial, livestock) - wd_sector: water demand sectors' names in PCR-GLOBWB (irrigation, domestic, industrial, livestock) - wq_constituent: surface water quality constituents' names from DynQual (water temperature, BOD, salinity/TDS, fecal coliforms) - wq_state: surface water quality constituents' concentrations from DynQual (water temperature, BOD, salinity/TDS, fecal coliforms) - wq_treshold: surface water quality concentration thresholds per constituent and sector + availableSurfaceWater: maximum value between 0 and routed flow in channel + sectoralSurfaceWaterDemand: surface water demand estimations for every sector (irrigation, domestic, industrial, livestock) + waterQualityStates: surface water quality constituents' concentrations from DynQual (water temperature, BOD, salinity/TDS, fecal coliforms) + waterQualityThresholds: surface water quality concentration thresholds per constituent and sector ''' # initial values + # - list of water demand sectors + waterDemandSectors = list(waterQualityThresholds.keys()) # i.e., irrigation, domestic, industrial, livestock + + # - list of water quality constituents + waterQualityConstituents = list(waterQualityThresholds[waterDemandSectors[0]].keys()) # i.e., water temperature, BOD, salinity/TDS, fecal coliforms + # - amount of water that is abstracted from the source: initializing dataset - totalActSurfaceWaterAbstract = 0.0 + totalActualSurfaceWaterAbstract = 0.0 # - remaining water demand and satisfied water demand: initializing dictionaries - water_demand_remaining = {} - water_demand_satisfied = {} - for sector in wd_sector: - water_demand_remaining[sector] = sectoral_surface_water_demand[sector] - water_demand_satisfied[sector] = 0.0 + waterDemandRemaining = {} + waterDemandSatisfied = {} + for sector in waterDemandSectors: + waterDemandRemaining[sector] = sectoralSurfaceWaterDemand[sector] + waterDemandSatisfied[sector] = 0.0 - # - replacing None values in wq_threshold dictionary with unreachable value (1e20) - for consti in wq_threshold.keys(): - print(consti) - wq_threshold[consti] = {k:v if v is not None else 1e20 for k,v in wq_threshold[consti].items()} + # - replacing None values in waterQualityThresholds dictionary with unreachable threshold (1e20) + for sector in waterDemandSectors: + waterQualityThresholds[sector] = {k:v if v is not None else 1e20 for k,v in wq_threshold[sector].items()} # looping for every constituent for consti in wq_constituent: From ba214bb0f1641d95190a39775ae4063721ea7b86 Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Wed, 29 Mar 2023 13:54:11 +0200 Subject: [PATCH 28/31] update --- model/landCover.py | 173 +++++++++++++++-------------------- model/landSurface.py | 8 +- model/surfaceWaterQuality.py | 147 +++++++++++++++-------------- 3 files changed, 152 insertions(+), 176 deletions(-) diff --git a/model/landCover.py b/model/landCover.py index e97cef8e6..0e01b3ba4 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -1743,103 +1743,108 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # the following value will be reduced by available/accesible water self.totalPotentialGrossDemand = self.totalPotentialMaximumGrossDemand + + ################################################################################################################################### # Abstraction and Allocation of DESALINATED WATER - # ################################################################################################################## - # - desalination water to satisfy water demand + ################################################################################################################################### + # Desalination water abstraction and allocation.................................................................................... if self.usingAllocSegments: # using zone/segments at which networks are defined (as defined in the landSurface options) - # logger.debug("Allocation of supply from desalination water.") - # + volDesalinationAbstraction, volDesalinationAllocation = \ vos.waterAbstractionAndAllocation( - water_demand_volume = self.totalPotentialGrossDemand*routing.cellArea,\ - available_water_volume = pcr.max(0.00, desalinationWaterUse*routing.cellArea),\ - allocation_zones = allocSegments,\ - zone_area = self.segmentArea,\ - high_volume_treshold = None,\ - debug_water_balance = True,\ - extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), - landmask = self.landmask, - ignore_small_values = False, - prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) - # + water_demand_volume = self.totalPotentialGrossDemand*routing.cellArea, + available_water_volume = pcr.max(0.00, desalinationWaterUse*routing.cellArea), + allocation_zones = allocSegments, + zone_area = self.segmentArea, + high_volume_treshold = None, + debug_water_balance = True, + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + self.desalinationAbstraction = volDesalinationAbstraction / routing.cellArea self.desalinationAllocation = volDesalinationAllocation / routing.cellArea - # - else: - # + + else: logger.debug("Supply from desalination water is only for satisfying local demand (no network).") + self.desalinationAbstraction = pcr.min(desalinationWaterUse, self.totalPotentialGrossDemand) self.desalinationAllocation = self.desalinationAbstraction - # + self.desalinationAbstraction = pcr.ifthen(self.landmask, self.desalinationAbstraction) self.desalinationAllocation = pcr.ifthen(self.landmask, self.desalinationAllocation) - # ################################################################################################################## - # - end of Abstraction and Allocation of DESALINATED WATER - - # water demand that have been satisfied (unit: m/day) - after desalination - ################################################################################################################################ - # - for irrigation (excluding livestock) + # Satisfied water demand (unit: m/day) - after desalination........................................................................ + # - irrigation (excluding livestock) satisfiedIrrigationDemand = vos.getValDivZero(self.irrGrossDemand, self.totalPotentialGrossDemand) * self.desalinationAllocation - # - for domestic, industry and livestock + # - non-irrigation: domestic, industry and livestock satisfiedNonIrrDemand = pcr.max(0.00, self.desalinationAllocation - satisfiedIrrigationDemand) - # - for domestic + # - domestic satisfiedDomesticDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['domestic'], self.totalPotentialMaximumNonIrrGrossDemand) - # - for industry + # - industry satisfiedIndustryDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['industry'], self.totalPotentialMaximumNonIrrGrossDemand) - # - for livestock + # - livestock satisfiedLivestockDemand = pcr.max(0.0, satisfiedNonIrrDemand - satisfiedDomesticDemand - satisfiedIndustryDemand) - - - # total remaining gross demand (m/day) after desalination - ################################################################################################################################ + # - water abstraction by source + self.waterAbstractionSource = {} + self.waterAbstractionSource['desalination'] = {} + self.waterAbstractionSource['desalination']['irrigation'] = satisfiedNonIrrDemand + self.waterAbstractionSource['desalination']['domestic'] = satisfiedDomesticDemand + self.waterAbstractionSource['desalination']['industrial'] = satisfiedIndustryDemand + self.waterAbstractionSource['desalination']['livestock'] = satisfiedLivestockDemand + + # Total remaining GROSS demand (m/day) - after desalination........................................................................ self.totalGrossDemandAfterDesalination = pcr.max(0.0, self.totalPotentialGrossDemand - self.desalinationAllocation) # the remaining water demand per sector - # - for domestic + # - domestic remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) - # - for industry + # - industry remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) - # - for livestock + # - livestock remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) - # - for irrigation (excluding livestock) - remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) # - total for livestock and irrigation remainingIrrigationLivestock = remainingIrrigation + remainingLivestock # - total for industrial and domestic (excluding livestock) - remainingIndustrialDomestic = pcr.max(0.0, self.totalGrossDemandAfterDesalination - remainingIrrigationLivestock) + remainingIndustrialDomestic = pcr.max(0.0, self.totalGrossDemandAfterDesalination - remainingIrrigationLivestock) + ################################################################################################################################### # Abstraction and Allocation of SURFACE WATER - ############################################################################################################################## - # calculate the estimate of surface water demand (considering by swAbstractionFractionDict) - # - for industrial and domestic + ################################################################################################################################### + # Estimates of surface water demand (considering by swAbstractionFractionDict) .................................................... + # - industrial and domestic swAbstractionFraction_industrial_domestic = pcr.min(swAbstractionFractionDict['max_for_non_irrigation'],\ swAbstractionFractionDict['estimate']) if swAbstractionFractionDict['non_irrigation'] is not None: swAbstractionFraction_industrial_domestic = swAbstractionFractionDict['non_irrigation'] - + surface_water_demand_estimate = swAbstractionFraction_industrial_domestic * remainingIndustrialDomestic - # - for irrigation and livestock + # - irrigation and livestock surface_water_irrigation_demand_estimate = swAbstractionFractionDict['irrigation'] * remainingIrrigationLivestock - # - surface water source as priority if groundwater irrigation fraction is relatively low + + # - surface water source as priority if groundwater irrigation fraction is relatively low surface_water_irrigation_demand_estimate = \ pcr.ifthenelse(swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_maximize_irrigation_surface_water'],\ remainingIrrigationLivestock, surface_water_irrigation_demand_estimate) + # - update estimate of surface water demand withdrawal (unit: m/day) surface_water_demand_estimate += surface_water_irrigation_demand_estimate # - prioritize surface water use in non productive aquifers that have limited groundwater supply surface_water_demand_estimate = pcr.ifthenelse(groundwater.productive_aquifer, surface_water_demand_estimate,\ pcr.max(0.0, remainingIrrigationLivestock - \ pcr.min(groundwater.avgAllocationShort, groundwater.avgAllocation))) - # - maximize/optimize surface water use in areas with the overestimation of groundwater supply + # - maximize/optimize surface water use in areas with the overestimation of groundwater supply surface_water_demand_estimate += pcr.max(0.0, pcr.max(groundwater.avgAllocationShort, groundwater.avgAllocation) -\ (1.0 - swAbstractionFractionDict['irrigation']) * totalIrrigationLivestockDemand -\ (1.0 - swAbstractionFraction_industrial_domestic) * (self.totalPotentialMaximumGrossDemand - totalIrrigationLivestockDemand)) - # - # total demand (unit: m/day) that should be allocated from surface water + + # total demand (unit: m/day) that should be allocated from surface water # (corrected/limited by swAbstractionFractionDict and limited by the remaining demand) surface_water_demand_estimate = pcr.min(self.totalGrossDemandAfterDesalination, surface_water_demand_estimate) correctedRemainingIrrigationLivestock = pcr.min(surface_water_demand_estimate, remainingIrrigationLivestock) @@ -1847,77 +1852,41 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ pcr.max(0.0, surface_water_demand_estimate - remainingIrrigationLivestock)) correctedSurfaceWaterDemandEstimate = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic surface_water_demand = correctedSurfaceWaterDemandEstimate - # Note the variable "surface_water_demand" is the estimate of surface water demand for all sectors (total) based on Siebert et al.; McDonald et al.; de Graaf et al. # available_surface_water (without considering quality) available_surface_water = pcr.max(0.00, routing.readAvlChannelStorage) - + + # Water quality evaluation ......................................................................................... if self.consider_water_quality: logger.info("Surface water allocation to meet demand will consider water quality.") - # Input - # - list of water demand sectors - # wd_sector = ["irrigation", "domestic", "industrial", "livestock"] - # - list of water_quality_constituents - # wq_constituent = ["sw_temperature", "bio_o2_demand", "tot_dis_solid", "fecal_coliform"] - wq_constituent = list(wq_thresholds[wd_sector[0]].keys()) - - # - the estimates of surface water demand for every sector, after the above schemes (Siebert et al.; McDonald et al.; de Graaf et al.) + # Estimates of surface water demand for every sector, after the above schemes (Siebert et al.; McDonald et al.; de Graaf et al.) sectoral_surface_water_demand = {} sectoral_surface_water_demand["irrigation"] = correctedRemainingIrrigationLivestock * vos.getValDivZero(remainingIrrigation, remainingLivestock + remainingIrrigation) sectoral_surface_water_demand["livestock"] = pcr.max(0.0, correctedRemainingIrrigationLivestock - sectoral_surface_water_demand["irrigation"]) sectoral_surface_water_demand["industrial"] = correctedRemainingIndustrialDomestic * vos.getValDivZero(remainingIndustry, remainingIndustry + remainingDomestic) sectoral_surface_water_demand["domestic"] = pcr.max(0.0, correctedRemainingIndustrialDomestic - sectoral_surface_water_demand["industrial"]) - # - current/simulate water_quality_concetration_constituent -# wq_state = {} -# wq_state["sw_temperatur"] = 25.0 -# wq_state["bio_o2_demand"] = self.inputBOD -# wq_state["total_dissolved_solid"] = 0.0 -# wq_state["fecal_coliform"] = 0.0 - - # - water quality thresholds -# wq_threshold = {} -# wq_threshold["sw_temperatur"] = {} -# wq_threshold["sw_temperatur"]["irrigation"] = 1e-20 # None -# wq_threshold["sw_temperatur"]["livestock"] = 1e-20 # None -# wq_threshold["sw_temperatur"]["industrial"] = 1e-20 # 30.0 -# wq_threshold["sw_temperatur"]["domestic"] = 1e-20 # None -# wq_threshold["bio_o2_demand"] = {} -# wq_threshold["bio_o2_demand"]["irrigation"] = 1e-20 # 15.0 -# wq_threshold["bio_o2_demand"]["livestock"] = 1e-20 # None -# wq_threshold["bio_o2_demand"]["industrial"] = 1e-20 # 30.0 -# wq_threshold["bio_o2_demand"]["domestic"] = 1e-20 # 5.0 -# wq_threshold["total_dissolved_solid"] = {} -# wq_threshold["total_dissolved_solid"]["irrigation"] = 1e-20 # 450.0 -# wq_threshold["total_dissolved_solid"]["livestock"] = 1e-20 # None -# wq_threshold["total_dissolved_solid"]["industrial"] = 1e-20 # 7000. -# wq_threshold["total_dissolved_solid"]["domestic"] = 1e-20 # 600. -# wq_threshold["fecal_coliform"] = {} -# wq_threshold["fecal_coliform"]["irrigation"] = 1e-20 # None -# wq_threshold["fecal_coliform"]["livestock"] = 1e-20 # None -# wq_threshold["fecal_coliform"]["industrial"] = 1e-20 # None -# wq_threshold["fecal_coliform"]["domestic"] = 1e-20 # None - + # Water quality evaluation and surface water allocation totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ - swq.surface_water_allocation_based_on_quality(available_surface_water, sectoral_surface_water_demand, self.wq_state, self.wq_threshold, - self.surfaceWaterPiority, self.usingAllocSegments, self.allocSegments, routing.cellArea, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) + swq.surface_water_allocation_based_on_quality(available_surface_water, sectoral_surface_water_demand, + self.wq_state, self.wq_threshold, self.surfaceWaterPiority, self.usingAllocSegments, self.allocSegments, + routing.cellArea, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) - # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply - ################################################################################################################################ - # - for irrigation and livestock water demand + # Tracking water demand that have been satisfied (unit: m/day) + # - for irrigation and livestock water demand satisfiedIrrigationLivestockDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["irrigation"] + sectoral_surface_water_demand_satisfied["livestock"] - # - for irrigation water demand, but not including livestock + # - for irrigation water demand, but not including livestock satisfiedIrrigationDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["irrigation"] satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater - # - for non irrigation water demand: livestock, domestic and industry + # - for non irrigation water demand: livestock, domestic and industry satisfiedNonIrrDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] + sectoral_surface_water_demand_satisfied["livestock"] satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater - # - for livestock + # - for livestock satisfiedLivestockDemand += sectoral_surface_water_demand_satisfied["livestock"] # - for industrial and domestic demand (excluding livestock) satisfiedIndustrialDomesticDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] - # - for domestic + # - for domestic satisfiedDomesticDemand += sectoral_surface_water_demand_satisfied["domestic"] # - for industry satisfiedIndustryDemand += sectoral_surface_water_demand_satisfied["industrial"] @@ -1925,8 +1894,11 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ self.actSurfaceWaterAbstract = totalActSurfaceWaterAbstract self.allocSurfaceWaterAbstract = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] + sectoral_surface_water_demand_satisfied["livestock"] + sectoral_surface_water_demand_satisfied["irrigation"] + # End of Water quality evaluation .................................................................................. + # + # Conventional water allocation scheme ............................................................................. else: - # if surface water abstraction as the first priority + # if surface water abstraction as the first priority (False by default) if self.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination @@ -1953,7 +1925,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") self.actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ surface_water_demand) # unit: m - self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m + self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m # self.actSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.actSurfaceWaterAbstract) self.allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.allocSurfaceWaterAbstract) @@ -1984,8 +1956,7 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ remainingIndustrialDomestic) # - for industry satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingIndustry, \ - remainingIndustrialDomestic) - + remainingIndustrialDomestic) ###################################################################################################################### diff --git a/model/landSurface.py b/model/landSurface.py index 6a0bd3bf0..958d537af 100644 --- a/model/landSurface.py +++ b/model/landSurface.py @@ -396,7 +396,6 @@ def __init__(self,iniItems,landmask,initialState=None): self.inputFileFC = iniItems.landSurfaceOptions["inputFileFecalColiform"] # - threshold values of water quality constituents - # self.thresholdBODForIrrigation = float(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) # self.wq_threshold["sw_temperature"] = {} # self.wq_threshold["sw_temperature"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) # self.wq_threshold["sw_temperature"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdSWTForLivestock"]) @@ -1297,10 +1296,17 @@ def update(self,meteo,groundwater,routing,currTimeStep): # updating any information related to water quality self.wq_state = {} if self.consider_water_quality: + # converting .nc files to .map per timestep self.wq_state["sw_temperature"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileSWT, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) self.wq_state["bio_o2_demand"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileBOD, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) self.wq_state["tot_dis_solid"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileTDS, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) self.wq_state["fecal_coliform"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileFC , varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) + + # filling nans with zero values + self.wq_state["sw_temperature"] = pcr.cover(self.wq_state["sw_temperature"],0.0) + self.wq_state["bio_o2_demand"] = pcr.cover(self.wq_state["bio_o2_demand"], 0.0) + self.wq_state["tot_dis_solid"] = pcr.cover(self.wq_state["tot_dis_solid"], 0.0) + self.wq_state["fecal_coliform"] = pcr.cover(self.wq_state["fecal_coliform"],0.0) # updating regional groundwater abstraction limit (at the begining of the year or at the beginning of simulation) if groundwater.limitRegionalAnnualGroundwaterAbstraction: diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py index cdf6d4497..6bedefb0f 100644 --- a/model/surfaceWaterQuality.py +++ b/model/surfaceWaterQuality.py @@ -12,7 +12,6 @@ logger = logging.getLogger(__name__) import virtualOS as vos -#from ncConverter import * def surface_water_allocation_based_on_quality(availableSurfaceWater, sectoralSurfaceWaterDemand, waterQualityStates, waterQualityThresholds, surfaceWaterPriority, usingAllocSegments, allocSegments, cellArea, segmentArea, landmask, @@ -28,6 +27,7 @@ def surface_water_allocation_based_on_quality(availableSurfaceWater, sectoralSur # initial values # - list of water demand sectors waterDemandSectors = list(waterQualityThresholds.keys()) # i.e., irrigation, domestic, industrial, livestock + waterDemandSectorsPriority = ['domestic','industrial','livestock','irrigation'] # - list of water quality constituents waterQualityConstituents = list(waterQualityThresholds[waterDemandSectors[0]].keys()) # i.e., water temperature, BOD, salinity/TDS, fecal coliforms @@ -42,84 +42,83 @@ def surface_water_allocation_based_on_quality(availableSurfaceWater, sectoralSur waterDemandRemaining[sector] = sectoralSurfaceWaterDemand[sector] waterDemandSatisfied[sector] = 0.0 - # - replacing None values in waterQualityThresholds dictionary with unreachable threshold (1e20) + # - replacing None values in waterQualityThresholds dictionary with unreachable threshold (1e100) for sector in waterDemandSectors: - waterQualityThresholds[sector] = {k:v if v is not None else 1e20 for k,v in wq_threshold[sector].items()} + waterQualityThresholds[sector] = {k:v if v is not None else 1e100 for k,v in waterQualityThresholds[sector].items()} - # looping for every constituent - for consti in wq_constituent: - sectors = wd_sector.copy() + # looping for every sector + for sectorPriority in waterDemandSectorsPriority: + # sectoral surface water demend + surfaceWaterDemand = sectoralSurfaceWaterDemand[sectorPriority] # water quality concentrations - water_quality_concetration_consti = wq_state[consti] + waterQualityConcentrationSWT = waterQualityStates['sw_temperature'] + waterQualityConcentrationBOD = waterQualityStates['bio_o2_demand'] + waterQualityConcentrationTDS = waterQualityStates['tot_dis_solid'] + waterQualityConcentrationFC = waterQualityStates['fecal_coliform'] - # ordering sectors from more stringent to less stringent - thresholds = wq_threshold[consti] - thresholds = dict(sorted(thresholds.items(), key=lambda item: item[1], reverse=False)) - sectors_ordered = list(thresholds.keys()) + # water quality thresholds + waterQualityThresholdsSWT = waterQualityThresholds[sectorPriority]['sw_temperature'] + waterQualityThresholdsBOD = waterQualityThresholds[sectorPriority]['bio_o2_demand'] + waterQualityThresholdsTDS = waterQualityThresholds[sectorPriority]['tot_dis_solid'] + waterQualityThresholdsFC = waterQualityThresholds[sectorPriority]['fecal_coliform'] - # looping for every sector - for sector_order in sectors_ordered: - - # defining water quality thresholds - threshold_consti_sector = wq_threshold[consti][sector_order] - - # defining actual water available depending on constituent threshold - available_surface_water_consti_sector = pcr.ifthenelse((water_quality_concetration_consti < threshold_consti_sector) | (pcr.pcrnot(pcr.defined(water_quality_concetration_consti))), available_surface_water, 0.) - - # total remaining water demand - total_water_demand = 0.0 - for sector in wd_sector: - total_water_demand = total_water_demand + water_demand_remaining[sector] - surface_water_demand = total_water_demand - - # water allocation scheme - # [ TODO ] accomodate the option "surfaceWaterPriority"!!!! - msg = "Allocating water that is above the threshold for " + consti + " for the sector " + sector_order - logger.debug(msg) - - if usingAllocSegments: # using zone/segment at which supply network is defined - logger.debug("Allocation of surface water abstraction.") - - volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ - vos.waterAbstractionAndAllocation( - water_demand_volume = surface_water_demand*cellArea,\ - available_water_volume = available_surface_water_consti_sector,\ - allocation_zones = allocSegments,\ - zone_area = segmentArea,\ - high_volume_treshold = None,\ - debug_water_balance = True,\ - extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), - landmask = landmask, - ignore_small_values = False, - prioritizing_local_source = prioritizeLocalSourceToMeetWaterDemand) - - actSurfaceWaterAbstract = volActSurfaceWaterAbstract / cellArea - allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / cellArea - - else: - logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") - actSurfaceWaterAbstract = pcr.min(available_surface_water_volume/cellArea, surface_water_demand) # unit: m - allocSurfaceWaterAbstract = actSurfaceWaterAbstract # unit: m - - # - the amount of water that is abstracted from the source (e.g. river, reservoir pixels): outgoing water - actSurfaceWaterAbstract = pcr.ifthen(landmask, actSurfaceWaterAbstract) - # - the amount of water that is given to pixels with demand (e.g. pixels with irrigation areas): incoming water - allocSurfaceWaterAbstract = pcr.ifthen(landmask, allocSurfaceWaterAbstract) - - # tracking the total amount of water that is abstracted from the source - totalActSurfaceWaterAbstract = totalActSurfaceWaterAbstract + actSurfaceWaterAbstract + # actual water available depending on its quality (threshold) + availableSurfaceWaterWithQuality = pcr.ifthenelse((waterQualityConcentrationSWT < waterQualityThresholdsSWT) & + (waterQualityConcentrationBOD < waterQualityThresholdsBOD) & + (waterQualityConcentrationTDS < waterQualityThresholdsTDS) & + (waterQualityConcentrationFC < waterQualityThresholdsFC), + availableSurfaceWater, 0.0) + + # total remaining water demand + #totalSurfaceWaterDemand = 0.0 + #for sector in waterDemandSectors: + # totalSurfaceWaterDemand = totalSurfaceWaterDemand + waterDemandRemaining[sector] + + # water allocation: water abtracted and allocated per pixel + msg = "Allocating water that is above water quality thresholds for the sector " + sectorPriority + logger.debug(msg) + + if usingAllocSegments: # using zone/segment at which supply network is defined + logger.debug("Allocation of surface water abstraction.") - # calculating remaining water available - available_surface_water = available_surface_water - actSurfaceWaterAbstract + volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ + vos.waterAbstractionAndAllocation( \ + water_demand_volume = surfaceWaterDemand*cellArea, + available_water_volume = availableSurfaceWaterWithQuality, + allocation_zones = allocSegments, + zone_area = segmentArea, + high_volume_treshold = None, + debug_water_balance = True, + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = landmask, + ignore_small_values = False, + prioritizing_local_source = prioritizeLocalSourceToMeetWaterDemand) - # looping for every sector to distribute allocSurfaceWaterAbstract - for sector in sectors: - current_water_withdrawal_sector = allocSurfaceWaterAbstract * vos.getValDivZero(water_demand_remaining[sector], total_water_demand) - - # - tracking the water demand: satisficed and remaining - water_demand_remaining[sector] = water_demand_remaining[sector] - current_water_withdrawal_sector - water_demand_satisfied[sector] = water_demand_satisfied[sector] + current_water_withdrawal_sector - sectors.remove(sector_order) - - return totalActSurfaceWaterAbstract, water_demand_satisfied \ No newline at end of file + actSurfaceWaterAbstract = volActSurfaceWaterAbstract / cellArea + allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / cellArea + + else: + logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") + actSurfaceWaterAbstract = pcr.min(availableSurfaceWaterWithQuality/cellArea, surfaceWaterDemand) # unit: m + allocSurfaceWaterAbstract = actSurfaceWaterAbstract # unit: m + + # masking values + # - outgoing water: amount of water abstracted from the source (e.g. river, reservoir pixels) + actSurfaceWaterAbstract = pcr.ifthen(landmask, actSurfaceWaterAbstract) + + # - incoming water: amount of water allocated to pixels with demand (e.g. pixels with irrigation areas) + allocSurfaceWaterAbstract = pcr.ifthen(landmask, allocSurfaceWaterAbstract) + + # water balance on the fly + # - tracking the total amount of water that is abstracted from the source + totalActualSurfaceWaterAbstract = totalActualSurfaceWaterAbstract + actSurfaceWaterAbstract + + # - calculating remaining water available + availableSurfaceWater = availableSurfaceWater - actSurfaceWaterAbstract + + # - tracking the water demand: satisficed and remaining + waterDemandRemaining[sectorPriority] = waterDemandRemaining[sectorPriority] - allocSurfaceWaterAbstract + waterDemandSatisfied[sectorPriority] = waterDemandSatisfied[sectorPriority] + allocSurfaceWaterAbstract + + return totalActualSurfaceWaterAbstract, waterDemandSatisfied \ No newline at end of file From 1d6c52727b36fd4d209a5814fbd9b7eb2cd688b3 Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Mon, 22 May 2023 16:14:03 +0200 Subject: [PATCH 29/31] Starting new branch --- config/setup_05min_no_water_quality.ini | 467 +++++++++++++++++++++++ config/setup_05min_water_quality.ini | 485 ++++++++++++++++++++++++ model/waterUse.py | 0 notes_with_gabriel.txt | 43 --- notes_with_gabriel_2 | 61 --- notes_with_gabriel_2.py | 61 --- 6 files changed, 952 insertions(+), 165 deletions(-) create mode 100644 config/setup_05min_no_water_quality.ini create mode 100644 config/setup_05min_water_quality.ini create mode 100644 model/waterUse.py delete mode 100644 notes_with_gabriel.txt delete mode 100644 notes_with_gabriel_2 delete mode 100644 notes_with_gabriel_2.py diff --git a/config/setup_05min_no_water_quality.ini b/config/setup_05min_no_water_quality.ini new file mode 100644 index 000000000..c3ad75e77 --- /dev/null +++ b/config/setup_05min_no_water_quality.ini @@ -0,0 +1,467 @@ +[globalOptions] +# Please set the pcrglobwb output directory (outputDir) in an absolute path. +# - Please make sure that you have access to it. +#~ outputDir = /scratch/carde003/Mekong/1980-2010/Mekong_original/ +#~ outputDir = /scratch/carde003/M13/1980-2010/Mekong_original +outputDir = /scratch/carde003/ChaoPhraya/1980-2010/orig/ + +# Please set the clone map file (cloneMap), which defines the spatial resolution and extent of your study area. +# - Please make sure that the file is stored locally in your computing machine. +# - The file must be in the pcraster format. +#~ cloneMap = /scratch/carde003/Mekong/mekong.map +#~ cloneMap = /scratch/carde003/M13/clone_M13.map +cloneMap = /scratch/carde003/ChaoPhraya/clone_chaophraya.map + +# Set the input directory map in an absolute path. The input forcing and parameter directories and files will be relative to this. +# - The following is an example using files from the opendap server. +#~ inputDir = https://opendap.4tu.nl/thredds/dodsC/data2/pcrglobwb/version_2019_11_beta/pcrglobwb2_input/ +# - The following is an example using input files stored locally in your computing machine. +#~ inputDir = /quanta1/home/hydrowld/data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta/pcrglobwb2_input/ +# - on velocity +inputDir = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/ + +# The area/landmask of interest: +# If None, area/landmask is limited for cells with ldd value. +#~ landmask = None +#~ landmask = /scratch/carde003/Mekong/mekong.map +#~ landmask = /scratch/carde003/M13/mask_M13.map +landmask = /scratch/carde003/ChaoPhraya/clone_chaophraya.map + +# netcdf attributes for output files: +institution = Department of Physical Geography, Utrecht University +title = PCR-GLOBWB 2 output, with human factors (non-natural) +description = PCR-GLOBWB run with human factors (non-natural) at 5 arcmin resolution + +# Format: YYYY-MM-DD ; The model runs on daily time step. +startTime = 1980-01-01 +endTime = 2010-12-31 + +# spinning up options: +maxSpinUpsInYears = 0 +minConvForSoilSto = 0.0 +minConvForGwatSto = 0.0 +minConvForChanSto = 0.0 +minConvForTotlSto = 0.0 + + +[meteoOptions] +# Set the forcing temperature and precipitation files (relative to inputDir) +precipitationNC = global_30min/meteo/forcing/daily_precipitation_cru_era-interim_1979_to_2010.nc +temperatureNC = global_30min/meteo/forcing/daily_temperature_cru_era-interim_1979_to_2010.nc + +# Method to calculate referencePotETP (reference potential evaporation+transpiration) +# options are "Hamon" and "Input" ; If "Input", the netcdf input file must be given: +referenceETPotMethod = Input +refETPotFileNC = global_30min/meteo/forcing/daily_referencePotET_cru_era-interim_1979_to_2010.nc + + +[meteoDownscalingOptions] +# This section is for a 5 arcmin run, for downscaling meteorological forcing at 30 arcmin to 5 arcmin. +downscalePrecipitation = False +downscaleTemperature = True +downscaleReferenceETPot = False + +# Downscaling (based on the digital elevation model): +# the downscaling will be performed by providing the "cellIds" (meteoDownscaleIds) of lower resolution cells. +meteoDownscaleIds = global_05min/meteo/downscaling_from_30min/uniqueIds_30min.nc +highResolutionDEM = global_05min/meteo/downscaling_from_30min/gtopo05min.nc + +# lapse rates: +temperLapseRateNC = global_05min/meteo/downscaling_from_30min/temperature_slope.nc +precipLapseRateNC = global_05min/meteo/downscaling_from_30min/precipitation_slope.nc + +# downscaling criteria (TODO: remove these): +temperatCorrelNC = global_05min/meteo/downscaling_from_30min/temperature_correl.nc +precipitCorrelNC = global_05min/meteo/downscaling_from_30min/precipitation_correl.nc + +# windows length (unit: arc-degree) for smoothing/averaging forcing data (not recommended): +smoothingWindowsLength = 0 + + +[landSurfaceOptions] +debugWaterBalance = True +numberOfUpperSoilLayers = 2 + +# Soil and parameters +# - they are used for all land cover types, unless they are defined in certain land cover type options +# (e.g. different/various soil types for agriculture areas) +topographyNC = global_05min/landSurface/topography/topography_parameters_5_arcmin_october_2015.nc +soilPropertiesNC = global_05min/landSurface/soil/soilProperties5ArcMin.nc + +# Irrigation properties +includeIrrigation = True + +# - netcdf time series for historical expansion of irrigation areas (unit: hectares). +# (note: The resolution of this map must be consisten with the resolution of cellArea) +historicalIrrigationArea = global_05min/waterUse/irrigation/irrigated_areas/irrigationArea05ArcMin.nc + +# - pcraster map/value defining irrigation efficiency (dimensionless) - optional +irrigationEfficiency = global_30min/waterUse/irrigation/irrigation_efficiency/efficiency.nc + +# Water quality: water temperature, biochemical oxigen demand, salinity, fecal coliforms +considerWaterQuality = False + +# - input files +inputFileSwTemperature = None +inputFileBOD = /scratch/sutan101/data/dynqual/organic_monthlyAvg_1980_2019_without_time_bnds.nc +inputFileTotalDissolvedSolid = None +inputFileFecalColiform = None + +# - water constituents' threshold values +# -- Biochemical Oxigen Demand (mg/L) +thresholdBODForIrrigation = 15. +thresholdBODForLivestock = None +thresholdBODForIndustrial = 20. +thresholdBODForDomestic = 5. + +# Water demands: domestic, industrial and livestock water demand data (unit must be in m.day-1) +includeDomesticWaterDemand = True +includeIndustryWaterDemand = True +includeLivestockWaterDemand = True + +domesticWaterDemandFile = global_05min/waterUse/waterDemand/domestic/domestic_water_demand_version_april_2015.nc +industryWaterDemandFile = global_05min/waterUse/waterDemand/industry/industry_water_demand_version_april_2015.nc +livestockWaterDemandFile = global_05min/waterUse/waterDemand/livestock/livestock_water_demand_version_april_2015.nc + +# Desalination water supply (maximum/potential/capacity) +desalinationWater = global_05min/waterUse/desalination/desalination_water_version_april_2015.nc + +# Pooling zones: zone IDs (scale) at which allocations of groundwater and surface water (as well as desalinated water) are performed +allocationSegmentsForGroundSurfaceWater = global_05min/waterUse/abstraction_zones/abstraction_zones_60min_05min.nc + +# pcraster maps defining the partitioning of groundwater - surface water source + +# - predefined surface water - groundwater partitioning for irrigation demand (e.g. based on Siebert, Global Map of Irrigation Areas version 5) +irrigationSurfaceWaterAbstractionFractionData = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_SWFRAC.nc +# -- quality map +irrigationSurfaceWaterAbstractionFractionDataQuality = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_QUAL.nc + +# - threshold values defining the preference for surface water source for irrigation purpose +# -- treshold to maximize surface water irrigation use (cells with irrSurfaceWaterAbstractionFraction above this will prioritize irrigation surface water use) +treshold_to_maximize_irrigation_surface_water = 0.50 +# -- treshold to minimize fossil water withdrawal for irrigation (cells with irrSurfaceWaterAbstractionFraction below this have no fossil withdrawal for irrigation) +treshold_to_minimize_fossil_groundwater_irrigation = 0.70 + +# - predefined surface water - groundwater partitioning for non irrigation demand (e.g. based on McDonald, 2014) +maximumNonIrrigationSurfaceWaterAbstractionFractionData = global_30min/waterUse/source_partitioning/surface_water_fraction_for_non_irrigation/max_city_sw_fraction.nc + + +[forestOptions] +name = forest +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 + +cropCoefficientNC = global_05min/landSurface/landCover/naturalTall/cropCoefficientForest.nc +interceptCapNC = global_05min/landSurface/landCover/naturalTall/interceptCapInputForest.nc +coverFractionNC = global_05min/landSurface/landCover/naturalTall/coverFractionInputForest.nc + +landCoverMapsNC = None + +# If NC file is not provided, we have to provide the following pcraster maps: +fracVegCover = global_05min/landSurface/landCover/naturalTall/vegf_tall.nc +minSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/minf_tall_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/maxf_tall.nc +rootFraction1 = global_05min/landSurface/landCover/naturalTall/rfrac1_tall.nc +rootFraction2 = global_05min/landSurface/landCover/naturalTall/rfrac2_tall.nc +maxRootDepth = 1.0 +# Note: The maxRootDepth is not used for non irrigated land cover type. + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_forest_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_forest_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_forest_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_forest_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_forest_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_forest_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_forest_1999-12-31.nc + + +[grasslandOptions] +name = grassland +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 + +cropCoefficientNC = global_05min/landSurface/landCover/naturalShort/cropCoefficientGrassland.nc +interceptCapNC = global_05min/landSurface/landCover/naturalShort/interceptCapInputGrassland.nc +coverFractionNC = global_05min/landSurface/landCover/naturalShort/coverFractionInputGrassland.nc + +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/naturalShort/vegf_short.nc +minSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/minf_short_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/maxf_short.nc +rootFraction1 = global_05min/landSurface/landCover/naturalShort/rfrac1_short.nc +rootFraction2 = global_05min/landSurface/landCover/naturalShort/rfrac2_short.nc +maxRootDepth = 0.5 +# Note: The maxRootDepth is not used for non irrigated land cover type. + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_grassland_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_grassland_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_grassland_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_grassland_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_grassland_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_grassland_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_grassland_1999-12-31.nc + + +[irrPaddyOptions] +name = irrPaddy +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# land cover map +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/irrPaddy/fractionPaddy.nc +minSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/minf_paddy_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/maxf_paddy.nc +rootFraction1 = global_30min/landSurface/landCover/irrPaddy/rfrac1_paddy.nc +rootFraction2 = global_30min/landSurface/landCover/irrPaddy/rfrac2_paddy.nc +maxRootDepth = 0.5 + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# other paramater values +minTopWaterLayer = 0.05 +minCropKC = 0.2 +cropDeplFactor = 0.2 +minInterceptCap = 0.0002 + +cropCoefficientNC = global_30min/landSurface/landCover/irrPaddy/Global_CropCoefficientKc-IrrPaddy_30min.nc + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrPaddy_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrPaddy_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrPaddy_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrPaddy_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrPaddy_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrPaddy_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrPaddy_1999-12-31.nc + + +[irrNonPaddyOptions] +name = irrNonPaddy +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 +# +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/irrNonPaddy/fractionNonPaddy.nc +minSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/minf_nonpaddy_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/maxf_nonpaddy.nc +rootFraction1 = global_30min/landSurface/landCover/irrNonPaddy/rfrac1_nonpaddy.nc +rootFraction2 = global_30min/landSurface/landCover/irrNonPaddy/rfrac2_nonpaddy.nc +maxRootDepth = 1.0 + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 +cropDeplFactor = 0.5 +minInterceptCap = 0.0002 + +cropCoefficientNC = global_30min/landSurface/landCover/irrNonPaddy/Global_CropCoefficientKc-IrrNonPaddy_30min.nc + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrNonPaddy_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrNonPaddy_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrNonPaddy_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrNonPaddy_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrNonPaddy_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrNonPaddy_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrNonPaddy_1999-12-31.nc + + + +[groundwaterOptions] +debugWaterBalance = True + +# The file will containspecificYield (m3.m-3), kSatAquifer (m.day-1), recessionCoeff (day-1) +groundwaterPropertiesNC = global_05min/groundwater/properties/groundwaterProperties5ArcMin.nc +# - minimum value for groundwater recession coefficient (day-1) +minRecessionCoeff = 1.0e-4 + +# some options for constraining groundwater abstraction +limitFossilGroundWaterAbstraction = True +estimateOfRenewableGroundwaterCapacity = 0.0 +estimateOfTotalGroundwaterThickness = global_05min/groundwater/aquifer_thickness_estimate/thickness_05min.nc + +# minimum and maximum total groundwater thickness +minimumTotalGroundwaterThickness = 100. +maximumTotalGroundwaterThickness = None + +# annual pumping capacity for each region (unit: billion cubic meter per year), should be given in a netcdf file +pumpingCapacityNC = global_30min/waterUse/groundwater_pumping_capacity/regional_abstraction_limit.nc + +# initial conditions: +storGroundwaterIni = global_05min/initialConditions/non-natural/1999/storGroundwater_1999-12-31.nc +storGroundwaterFossilIni = global_05min/initialConditions/non-natural/1999/storGroundwaterFossil_1999-12-31.nc + +# additional initial conditions for pumping behaviors +avgNonFossilGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationLong_1999-12-31.nc +avgNonFossilGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationShort_1999-12-31.nc +avgTotalGroundwaterAbstractionIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAbstraction_1999-12-31.nc +avgTotalGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationLong_1999-12-31.nc +avgTotalGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationShort_1999-12-31.nc + +# additional initial conditions (needed only for MODFLOW run) +relativeGroundwaterHeadIni = global_05min/initialConditions/non-natural/1999/relativeGroundwaterHead_1999-12-31.nc +baseflowIni = global_05min/initialConditions/non-natural/1999/baseflow_1999-12-31.nc + +# zonal IDs (scale) at which zonal allocation of groundwater is performed +allocationSegmentsForGroundwater = global_05min/waterUse/abstraction_zones/abstraction_zones_30min_05min.nc + + + +[routingOptions] +debugWaterBalance = True + +# drainage direction map +lddMap = global_05min/routing/ldd_and_cell_area/lddsound_05min.nc + +# cell area (unit: m2) +cellAreaMap = global_05min/routing/ldd_and_cell_area/cellsize05min.correct.nc + +# routing method: +routingMethod = accuTravelTime +#~ routingMethod = kinematicWave + +# manning coefficient +manningsN = 0.04 + +# Option for flood plain simulation +dynamicFloodPlain = True + +# manning coefficient for floodplain +floodplainManningsN = 0.07 + +# channel gradient +gradient = global_05min/routing/channel_properties/channel_gradient.nc + +# constant channel depth +constantChannelDepth = global_05min/routing/channel_properties/bankfull_depth.nc + +# constant channel width (optional) +constantChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc + +# minimum channel width (optional) +minimumChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc + +# channel properties for flooding +# - if None, it will be estimated from (bankfull) channel depth (m) and width (m) +bankfullCapacity = None + +# files for relative elevation (above minimum dem) +relativeElevationFiles = global_05min/routing/channel_properties/dzRel%04d.nc +relativeElevationLevels = 0.0, 0.01, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00 + +# composite crop factors for WaterBodies: +cropCoefficientWaterNC = global_30min/routing/kc_surface_water/cropCoefficientForOpenWater.nc +minCropWaterKC = 1.00 + +# lake and reservoir parameters +waterBodyInputNC = global_05min/routing/surface_water_bodies/waterBodies5ArcMin.nc +onlyNaturalWaterBodies = False + +# initial conditions: +waterBodyStorageIni = global_05min/initialConditions/non-natural/1999/waterBodyStorage_1999-12-31.nc +channelStorageIni = global_05min/initialConditions/non-natural/1999/channelStorage_1999-12-31.nc +readAvlChannelStorageIni = global_05min/initialConditions/non-natural/1999/readAvlChannelStorage_1999-12-31.nc +avgDischargeLongIni = global_05min/initialConditions/non-natural/1999/avgDischargeLong_1999-12-31.nc +avgDischargeShortIni = global_05min/initialConditions/non-natural/1999/avgDischargeShort_1999-12-31.nc +m2tDischargeLongIni = global_05min/initialConditions/non-natural/1999/m2tDischargeLong_1999-12-31.nc +avgBaseflowLongIni = global_05min/initialConditions/non-natural/1999/avgBaseflowLong_1999-12-31.nc +riverbedExchangeIni = global_05min/initialConditions/non-natural/1999/riverbedExchange_1999-12-31.nc + +# initial condition of sub-time step discharge (needed for estimating number of time steps in kinematic wave methods) +subDischargeIni = global_05min/initialConditions/non-natural/1999/subDischarge_1999-12-31.nc + +avgLakeReservoirInflowShortIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirInflowShort_1999-12-31.nc +avgLakeReservoirOutflowLongIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirOutflowLong_1999-12-31.nc + +# number of days (timesteps) that have been performed for spinning up initial conditions in the routing module (i.e. channelStorageIni, avgDischargeLongIni, avgDischargeShortIni, etc.) +timestepsToAvgDischargeIni = global_05min/initialConditions/non-natural/1999/timestepsToAvgDischarge_1999-12-31.nc +# Note that: +# - maximum number of days (timesteps) to calculate long term average flow values (default: 5 years = 5 * 365 days = 1825) +# - maximum number of days (timesteps) to calculate short term average values (default: 1 month = 1 * 30 days = 30) + + + +[reportingOptions] +# output files that will be written in the disk in netcdf files: +# - daily resolution (discharge,totalRunoff,gwRecharge,totalGroundwaterAbstraction,surfaceWaterStorage) +outDailyTotNC = None + +# - monthly resolution +# (outMonthTotNC: actualET,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,totLandSurfaceActuaET,totalLandSurfacePotET,waterBodyActEvaporation,waterBodyPotEvaporation) +# (outMonthAvgNC: temperature,dynamicFracWat,surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalActiveStorageThickness,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage) +# (outMonthEndNC: storGroundwater,storGroundwaterFossil,waterBodyStorage,channelStorage,totalWaterStorageThickness,totalActiveStorageThickness) +outMonthTotNC = irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction +outMonthAvgNC = discharge +outMonthEndNC = None + +# - annual resolution +# (outAnnuaTotNC: totalEvaporation,precipitation,gwRecharge,totalRunoff,baseflow,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,totalGroundwaterAbstraction,totalAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,runoff,actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,irrigationWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,precipitation_at_irrigation,netLqWaterToSoil_at_irrigation,evaporation_from_irrigation,transpiration_from_irrigation,referencePotET) +# (outAnnuaAvgNC: temperature,discharge,surfaceWaterStorage,waterBodyStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage,fractionWaterBodyEvaporation,fractionTotalEvaporation,fracSurfaceWaterAllocation,fracDesalinatedWaterAllocation,gwRecharge) +# (outAnnuaEndNC: surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness) +outAnnuaTotNC = None +outAnnuaAvgNC = None +outAnnuaEndNC = None + +# - monthly and annual maxima (channelStorage,dynamicFracWat,floodVolume,floodDepth,surfaceWaterLevel,discharge,totalRunoff) +outMonthMaxNC = None +outAnnuaMaxNC = None + +# netcdf format and zlib setup +formatNetCDF = NETCDF4 +zlib = True diff --git a/config/setup_05min_water_quality.ini b/config/setup_05min_water_quality.ini new file mode 100644 index 000000000..e48054b12 --- /dev/null +++ b/config/setup_05min_water_quality.ini @@ -0,0 +1,485 @@ +[globalOptions] +# Please set the pcrglobwb output directory (outputDir) in an absolute path. +# - Please make sure that you have access to it. +#~ outputDir = /scratch/carde003/SWQ/M13/1980-2010/None +#~ outputDir = /scratch/carde003/SWQ/Mekong/1980-2010/Mekong_wquality/ +outputDir = /scratch/carde003/SWQ/ChaoPhraya/1980-2010/none/ + +# Please set the clone map file (cloneMap), which defines the spatial resolution and extent of your study area. +# - Please make sure that the file is stored locally in your computing machine. +# - The file must be in the pcraster format. +#~ cloneMap = /scratch/carde003/SWQ/M13/mask_M13.map +#~ cloneMap = /scratch/carde003/SWQ/Mekong/mask_mekong.map +cloneMap = /scratch/carde003/SWQ/ChaoPhraya/mask_chaophraya.map + +# Set the input directory map in an absolute path. The input forcing and parameter directories and files will be relative to this. +# - The following is an example using files from the opendap server. +#~ inputDir = https://opendap.4tu.nl/thredds/dodsC/data2/pcrglobwb/version_2019_11_beta/pcrglobwb2_input/ +# - The following is an example using input files stored locally in your computing machine. +#~ inputDir = /quanta1/home/hydrowld/data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta/pcrglobwb2_input/ +# - on velocity +inputDir = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/ + +# The area/landmask of interest: +# If None, area/landmask is limited for cells with ldd value. +#~ landmask = None +#~ landmask = /scratch/carde003/SWQ/M13/mask_M13.map +#~ landmask = /scratch/carde003/SWQ/Mekong/mask_mekong.map +landmask = /scratch/carde003/SWQ/ChaoPhraya/mask_chaophraya.map + +# netcdf attributes for output files: +institution = Department of Physical Geography, Utrecht University +title = PCR-GLOBWB 2 output, with human factors (non-natural) +description = PCR-GLOBWB run with human factors (non-natural) at 5 arcmin resolution + +# Format: YYYY-MM-DD ; The model runs on daily time step. +startTime = 1980-01-01 +endTime = 2010-12-31 + +# spinning up options: +maxSpinUpsInYears = 5 +minConvForSoilSto = 0.0 +minConvForGwatSto = 0.0 +minConvForChanSto = 0.0 +minConvForTotlSto = 0.0 + + +[meteoOptions] +# Set the forcing temperature and precipitation files (relative to inputDir) +precipitationNC = global_30min/meteo/forcing/daily_precipitation_cru_era-interim_1979_to_2010.nc +temperatureNC = global_30min/meteo/forcing/daily_temperature_cru_era-interim_1979_to_2010.nc + +# Method to calculate referencePotETP (reference potential evaporation+transpiration) +# options are "Hamon" and "Input"; if "Input", the netcdf input file must be given: +referenceETPotMethod = Input +refETPotFileNC = global_30min/meteo/forcing/daily_referencePotET_cru_era-interim_1979_to_2010.nc + + +[meteoDownscalingOptions] +# This section is for a 5 arcmin run, for downscaling meteorological forcing at 30 arcmin to 5 arcmin. +downscalePrecipitation = False +downscaleTemperature = True +downscaleReferenceETPot = False + +# Downscaling (based on the digital elevation model): +# the downscaling will be performed by providing the "cellIds" (meteoDownscaleIds) of lower resolution cells. +meteoDownscaleIds = global_05min/meteo/downscaling_from_30min/uniqueIds_30min.nc +highResolutionDEM = global_05min/meteo/downscaling_from_30min/gtopo05min.nc + +# lapse rates: +temperLapseRateNC = global_05min/meteo/downscaling_from_30min/temperature_slope.nc +precipLapseRateNC = global_05min/meteo/downscaling_from_30min/precipitation_slope.nc + +# downscaling criteria (TODO: remove these): +temperatCorrelNC = global_05min/meteo/downscaling_from_30min/temperature_correl.nc +precipitCorrelNC = global_05min/meteo/downscaling_from_30min/precipitation_correl.nc + +# windows length (unit: arc-degree) for smoothing/averaging forcing data (not recommended): +smoothingWindowsLength = 0 + + +[landSurfaceOptions] +debugWaterBalance = True +numberOfUpperSoilLayers = 2 + +# Soil and parameters +# - they are used for all land cover types, unless they are defined in certain land cover type options +# (e.g. different/various soil types for agriculture areas) +topographyNC = global_05min/landSurface/topography/topography_parameters_5_arcmin_october_2015.nc +soilPropertiesNC = global_05min/landSurface/soil/soilProperties5ArcMin.nc + +# Irrigation properties +includeIrrigation = True + +# - netcdf time series for historical expansion of irrigation areas (unit: hectares). +# (note: The resolution of this map must be consisten with the resolution of cellArea) +historicalIrrigationArea = global_05min/waterUse/irrigation/irrigated_areas/irrigationArea05ArcMin.nc + +# - pcraster map/value defining irrigation efficiency (dimensionless) - optional +irrigationEfficiency = global_30min/waterUse/irrigation/irrigation_efficiency/efficiency.nc + +# Water quality: water temperature, biochemical oxigen demand, salinity, fecal coliforms +considerWaterQuality = True + +# - input files +inputFileSWTemperature = /scratch/carde003/Datasets/DynQual/waterTemperature_monthlyAvg_1980_2019.nc +inputFileBiochemOxigenDemand = /scratch/carde003/Datasets/DynQual/organic_monthlyAvg_1980_2019.nc +inputFileTotalDissolvedSolid = /scratch/carde003/Datasets/DynQual/salinity_monthlyAvg_1980_2019.nc +inputFileFecalColiform = /scratch/carde003/Datasets/DynQual/pathogen_monthlyAvg_1980_2019.nc + +# - water constituents' threshold values +# -- Surface Water Temperature (oC) +thresholdSWTForIrrigation = None +thresholdSWTForLivestock = None +thresholdSWTForDomestic = None +thresholdSWTForIndustrial = None + +# -- Biochemical Oxigen Demand (mg/L) +thresholdBODForIrrigation = None +thresholdBODForLivestock = None +thresholdBODForDomestic = None +thresholdBODForIndustrial = None + +# -- Total Dissolved Solid (mg/L) +thresholdTDSForIrrigation = None +thresholdTDSForLivestock = None +thresholdTDSForDomestic = None +thresholdTDSForIndustrial = None + +# -- Fecal Coliforms (mg/L) +thresholdFCForIrrigation = None +thresholdFCForLivestock = None +thresholdFCForDomestic = None +thresholdFCForIndustrial = None + +# Water demands: domestic, industrial and livestock water demand data (unit must be in m.day-1) +includeDomesticWaterDemand = True +includeIndustryWaterDemand = True +includeLivestockWaterDemand = True + +domesticWaterDemandFile = global_05min/waterUse/waterDemand/domestic/domestic_water_demand_version_april_2015.nc +industryWaterDemandFile = global_05min/waterUse/waterDemand/industry/industry_water_demand_version_april_2015.nc +livestockWaterDemandFile = global_05min/waterUse/waterDemand/livestock/livestock_water_demand_version_april_2015.nc + +# Desalination water supply (maximum/potential/capacity) +desalinationWater = global_05min/waterUse/desalination/desalination_water_version_april_2015.nc + +# Pooling zones: zone IDs (scale) at which allocations of groundwater and surface water (as well as desalinated water) are performed +allocationSegmentsForGroundSurfaceWater = global_05min/waterUse/abstraction_zones/abstraction_zones_60min_05min.nc + +# pcraster maps defining the partitioning of groundwater - surface water source + +# - predefined surface water - groundwater partitioning for irrigation demand (e.g. based on Siebert, Global Map of Irrigation Areas version 5) +irrigationSurfaceWaterAbstractionFractionData = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_SWFRAC.nc +# -- quality map +irrigationSurfaceWaterAbstractionFractionDataQuality = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_QUAL.nc + +# - threshold values defining the preference for surface water source for irrigation purpose +# -- treshold to maximize surface water irrigation use (cells with irrSurfaceWaterAbstractionFraction above this will prioritize irrigation surface water use) +treshold_to_maximize_irrigation_surface_water = 0.50 +# -- treshold to minimize fossil water withdrawal for irrigation (cells with irrSurfaceWaterAbstractionFraction below this have no fossil withdrawal for irrigation) +treshold_to_minimize_fossil_groundwater_irrigation = 0.70 + +# - predefined surface water - groundwater partitioning for non irrigation demand (e.g. based on McDonald, 2014) +maximumNonIrrigationSurfaceWaterAbstractionFractionData = global_30min/waterUse/source_partitioning/surface_water_fraction_for_non_irrigation/max_city_sw_fraction.nc + + +[forestOptions] +name = forest +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 + +cropCoefficientNC = global_05min/landSurface/landCover/naturalTall/cropCoefficientForest.nc +interceptCapNC = global_05min/landSurface/landCover/naturalTall/interceptCapInputForest.nc +coverFractionNC = global_05min/landSurface/landCover/naturalTall/coverFractionInputForest.nc + +landCoverMapsNC = None + +# If NC file is not provided, we have to provide the following pcraster maps: +fracVegCover = global_05min/landSurface/landCover/naturalTall/vegf_tall.nc +minSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/minf_tall_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/maxf_tall.nc +rootFraction1 = global_05min/landSurface/landCover/naturalTall/rfrac1_tall.nc +rootFraction2 = global_05min/landSurface/landCover/naturalTall/rfrac2_tall.nc +maxRootDepth = 1.0 +# Note: The maxRootDepth is not used for non irrigated land cover type. + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_forest_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_forest_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_forest_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_forest_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_forest_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_forest_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_forest_1999-12-31.nc + + +[grasslandOptions] +name = grassland +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 + +cropCoefficientNC = global_05min/landSurface/landCover/naturalShort/cropCoefficientGrassland.nc +interceptCapNC = global_05min/landSurface/landCover/naturalShort/interceptCapInputGrassland.nc +coverFractionNC = global_05min/landSurface/landCover/naturalShort/coverFractionInputGrassland.nc + +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/naturalShort/vegf_short.nc +minSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/minf_short_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/maxf_short.nc +rootFraction1 = global_05min/landSurface/landCover/naturalShort/rfrac1_short.nc +rootFraction2 = global_05min/landSurface/landCover/naturalShort/rfrac2_short.nc +maxRootDepth = 0.5 +# Note: The maxRootDepth is not used for non irrigated land cover type. + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_grassland_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_grassland_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_grassland_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_grassland_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_grassland_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_grassland_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_grassland_1999-12-31.nc + + +[irrPaddyOptions] +name = irrPaddy +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# land cover map +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/irrPaddy/fractionPaddy.nc +minSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/minf_paddy_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/maxf_paddy.nc +rootFraction1 = global_30min/landSurface/landCover/irrPaddy/rfrac1_paddy.nc +rootFraction2 = global_30min/landSurface/landCover/irrPaddy/rfrac2_paddy.nc +maxRootDepth = 0.5 + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# other paramater values +minTopWaterLayer = 0.05 +minCropKC = 0.2 +cropDeplFactor = 0.2 +minInterceptCap = 0.0002 + +cropCoefficientNC = global_30min/landSurface/landCover/irrPaddy/Global_CropCoefficientKc-IrrPaddy_30min.nc + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrPaddy_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrPaddy_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrPaddy_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrPaddy_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrPaddy_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrPaddy_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrPaddy_1999-12-31.nc + + +[irrNonPaddyOptions] +name = irrNonPaddy +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 +# +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/irrNonPaddy/fractionNonPaddy.nc +minSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/minf_nonpaddy_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/maxf_nonpaddy.nc +rootFraction1 = global_30min/landSurface/landCover/irrNonPaddy/rfrac1_nonpaddy.nc +rootFraction2 = global_30min/landSurface/landCover/irrNonPaddy/rfrac2_nonpaddy.nc +maxRootDepth = 1.0 + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 +cropDeplFactor = 0.5 +minInterceptCap = 0.0002 + +cropCoefficientNC = global_30min/landSurface/landCover/irrNonPaddy/Global_CropCoefficientKc-IrrNonPaddy_30min.nc + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrNonPaddy_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrNonPaddy_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrNonPaddy_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrNonPaddy_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrNonPaddy_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrNonPaddy_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrNonPaddy_1999-12-31.nc + + + +[groundwaterOptions] +debugWaterBalance = True + +# The file will containspecificYield (m3.m-3), kSatAquifer (m.day-1), recessionCoeff (day-1) +groundwaterPropertiesNC = global_05min/groundwater/properties/groundwaterProperties5ArcMin.nc +# - minimum value for groundwater recession coefficient (day-1) +minRecessionCoeff = 1.0e-4 + +# some options for constraining groundwater abstraction +limitFossilGroundWaterAbstraction = True +estimateOfRenewableGroundwaterCapacity = 0.0 +estimateOfTotalGroundwaterThickness = global_05min/groundwater/aquifer_thickness_estimate/thickness_05min.nc + +# minimum and maximum total groundwater thickness +minimumTotalGroundwaterThickness = 100. +maximumTotalGroundwaterThickness = None + +# annual pumping capacity for each region (unit: billion cubic meter per year), should be given in a netcdf file +pumpingCapacityNC = global_30min/waterUse/groundwater_pumping_capacity/regional_abstraction_limit.nc + +# initial conditions: +storGroundwaterIni = global_05min/initialConditions/non-natural/1999/storGroundwater_1999-12-31.nc +storGroundwaterFossilIni = global_05min/initialConditions/non-natural/1999/storGroundwaterFossil_1999-12-31.nc + +# additional initial conditions for pumping behaviors +avgNonFossilGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationLong_1999-12-31.nc +avgNonFossilGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationShort_1999-12-31.nc +avgTotalGroundwaterAbstractionIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAbstraction_1999-12-31.nc +avgTotalGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationLong_1999-12-31.nc +avgTotalGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationShort_1999-12-31.nc + +# additional initial conditions (needed only for MODFLOW run) +relativeGroundwaterHeadIni = global_05min/initialConditions/non-natural/1999/relativeGroundwaterHead_1999-12-31.nc +baseflowIni = global_05min/initialConditions/non-natural/1999/baseflow_1999-12-31.nc + +# zonal IDs (scale) at which zonal allocation of groundwater is performed +allocationSegmentsForGroundwater = global_05min/waterUse/abstraction_zones/abstraction_zones_30min_05min.nc + + + +[routingOptions] +debugWaterBalance = True + +# drainage direction map +lddMap = global_05min/routing/ldd_and_cell_area/lddsound_05min.nc + +# cell area (unit: m2) +cellAreaMap = global_05min/routing/ldd_and_cell_area/cellsize05min.correct.nc + +# routing method: +routingMethod = accuTravelTime +#~ routingMethod = kinematicWave + +# manning coefficient +manningsN = 0.04 + +# Option for flood plain simulation +dynamicFloodPlain = True + +# manning coefficient for floodplain +floodplainManningsN = 0.07 + +# channel gradient +gradient = global_05min/routing/channel_properties/channel_gradient.nc + +# constant channel depth +constantChannelDepth = global_05min/routing/channel_properties/bankfull_depth.nc + +# constant channel width (optional) +constantChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc + +# minimum channel width (optional) +minimumChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc + +# channel properties for flooding +# - if None, it will be estimated from (bankfull) channel depth (m) and width (m) +bankfullCapacity = None + +# files for relative elevation (above minimum dem) +relativeElevationFiles = global_05min/routing/channel_properties/dzRel%04d.nc +relativeElevationLevels = 0.0, 0.01, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00 + +# composite crop factors for WaterBodies: +cropCoefficientWaterNC = global_30min/routing/kc_surface_water/cropCoefficientForOpenWater.nc +minCropWaterKC = 1.00 + +# lake and reservoir parameters +waterBodyInputNC = global_05min/routing/surface_water_bodies/waterBodies5ArcMin.nc +onlyNaturalWaterBodies = False + +# initial conditions: +waterBodyStorageIni = global_05min/initialConditions/non-natural/1999/waterBodyStorage_1999-12-31.nc +channelStorageIni = global_05min/initialConditions/non-natural/1999/channelStorage_1999-12-31.nc +readAvlChannelStorageIni = global_05min/initialConditions/non-natural/1999/readAvlChannelStorage_1999-12-31.nc +avgDischargeLongIni = global_05min/initialConditions/non-natural/1999/avgDischargeLong_1999-12-31.nc +avgDischargeShortIni = global_05min/initialConditions/non-natural/1999/avgDischargeShort_1999-12-31.nc +m2tDischargeLongIni = global_05min/initialConditions/non-natural/1999/m2tDischargeLong_1999-12-31.nc +avgBaseflowLongIni = global_05min/initialConditions/non-natural/1999/avgBaseflowLong_1999-12-31.nc +riverbedExchangeIni = global_05min/initialConditions/non-natural/1999/riverbedExchange_1999-12-31.nc + +# initial condition of sub-time step discharge (needed for estimating number of time steps in kinematic wave methods) +subDischargeIni = global_05min/initialConditions/non-natural/1999/subDischarge_1999-12-31.nc + +avgLakeReservoirInflowShortIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirInflowShort_1999-12-31.nc +avgLakeReservoirOutflowLongIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirOutflowLong_1999-12-31.nc + +# number of days (timesteps) that have been performed for spinning up initial conditions in the routing module (i.e. channelStorageIni, avgDischargeLongIni, avgDischargeShortIni, etc.) +timestepsToAvgDischargeIni = global_05min/initialConditions/non-natural/1999/timestepsToAvgDischarge_1999-12-31.nc +# Note that: +# - maximum number of days (timesteps) to calculate long term average flow values (default: 5 years = 5 * 365 days = 1825) +# - maximum number of days (timesteps) to calculate short term average values (default: 1 month = 1 * 30 days = 30) + + + +[reportingOptions] +# output files that will be written in the disk in netcdf files: +# - daily resolution (discharge,totalRunoff,gwRecharge,totalGroundwaterAbstraction,surfaceWaterStorage) +outDailyTotNC = None + +# - monthly resolution +# (outMonthTotNC: actualET,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,totLandSurfaceActuaET,totalLandSurfacePotET,waterBodyActEvaporation,waterBodyPotEvaporation) +# (outMonthAvgNC: temperature,dynamicFracWat,surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalActiveStorageThickness,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage) +# (outMonthEndNC: storGroundwater,storGroundwaterFossil,waterBodyStorage,channelStorage,totalWaterStorageThickness,totalActiveStorageThickness) +outMonthTotNC = irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction +outMonthAvgNC = discharge +outMonthEndNC = None + +# - annual resolution +# (outAnnuaTotNC: totalEvaporation,precipitation,gwRecharge,totalRunoff,baseflow,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,totalGroundwaterAbstraction,totalAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,runoff,actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,irrigationWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,precipitation_at_irrigation,netLqWaterToSoil_at_irrigation,evaporation_from_irrigation,transpiration_from_irrigation,referencePotET) +# (outAnnuaAvgNC: temperature,discharge,surfaceWaterStorage,waterBodyStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage,fractionWaterBodyEvaporation,fractionTotalEvaporation,fracSurfaceWaterAllocation,fracDesalinatedWaterAllocation,gwRecharge) +# (outAnnuaEndNC: surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness) +outAnnuaTotNC = None +outAnnuaAvgNC = None +outAnnuaEndNC = None + +# - monthly and annual maxima (channelStorage,dynamicFracWat,floodVolume,floodDepth,surfaceWaterLevel,discharge,totalRunoff) +outMonthMaxNC = None +outAnnuaMaxNC = None + +# netcdf format and zlib setup +formatNetCDF = NETCDF4 +zlib = True diff --git a/model/waterUse.py b/model/waterUse.py new file mode 100644 index 000000000..e69de29bb diff --git a/notes_with_gabriel.txt b/notes_with_gabriel.txt deleted file mode 100644 index e7197d4a4..000000000 --- a/notes_with_gabriel.txt +++ /dev/null @@ -1,43 +0,0 @@ -principle: -- keep proportional allocation based on demand magnitude (this is the principle of PCR-GLOBWB 2) -- we start allocating from the pixels that have the best quality () - -how many loops? -- water quality constituents, e.g. TOD, FC, etc. -- water quality thresholds for every constituent and for every sector - -input: -- available_surface_water_without_qual (without considering quality) -- water demand for every sector, e.g. irrigation, livestock, domestic and industry -- note, industry will be splitted into manufacturing and thermo-electric -- water quality concetration for every constituent -- water quality thresholds - - -step 0: - -available_surface_water_with_qual = available_surface_water_without_qual -water_use_irrigation = 0.0 - -for every consti (TOD, FC, etc) - -for threshold_consti_sector - -available_surface_water_with_qual_for_this_conti_sector = available_surface_water_with_qual -available_surface_water_with_qual_for_this_conti_sector = ifthenelse(wq_consti < threshold_consti_sector, available_surface_water_with_qual_for_this_conti_sector, 0.0) - -- start allocating available_surface_water_with_qual_for_this_conti_sector for every sector (proportional based on demand magnitudes); this will consider zones and etc -- this will be called water_use_each_sector (irrigation, livestock, industry and domestic) -- we should track this for every sector --- water_use_irrigation = water_use_irrigation + water_use_irrigation - -for every sector - -available_surface_water_with_qual = available_surface_water_with_qual - water_use_each_sector - -end loop for every sector - -end loop for threshold_consti_sector - -end loop for every_sector - diff --git a/notes_with_gabriel_2 b/notes_with_gabriel_2 deleted file mode 100644 index d2f478c12..000000000 --- a/notes_with_gabriel_2 +++ /dev/null @@ -1,61 +0,0 @@ -# Principle -# - Keep proportional allocation of water withdrawn based on demand magnitude (current principle of PCR-GLOBWB 2) -# - Water allocation starts from the pixels with the best water quality - -# How many loops? -# - Water quality constituents (i.e., Temp, BOD, TDS, FC, [DO, N, P]) -# - Water quality thresholds for each constituent and for each sector - -# Input -# - available_surface_water (without considering quality) -# - water_demand_sector (e.g., irrigation, livestock, domestic and industry) -> (industry will be splitted into manufacturing and thermoelectric) -# - water_quality_concetration_constituent [Temp, BOD, TDS, FC] -# - water_quality_thresholds -water_quality_thresholds = { -'waterTemperature': ('irrigation':1000,'domestic':1000,'thermoelectric':1000,'manufacturing':1000,'livestock':1000), # Water temperature (oC) -'organic': ('irrigation':15 ,'domestic':5 ,'thermoelectric':30 ,'manufacturing':30 ,'livestock':1000), # Biochemical oxigen demand (mg/l) -'salinity': ('irrigation':450 ,'domestic':600 ,'thermoelectric':7000,'manufacturing':7000,'livestock':100000), # Total disolved solids (mg/l) -'pathogen': ('irrigation':1000,'domestic':1000,'thermoelectric':1000,'manufacturing':1000,'livestock':1000), # Fecal coliforms (cfu100/ml) -} - -water_quality_dict = {'TMP':'waterTemperature','BOD':'organic','TDS':'salinity':,'FC':'pathogen'} - -# Step 0 -available_surface_water_with_qual = available_surface_water -for sector in ['domestic','manufacturing','livestock','thermoelectric','irrigation']: - globals()['water_demand_remaining'+str(sector)] = water_demand_sector # water_demand_remaining_sector - globals()['water_demand_satisfied'+str(sector)] = 0. # water_demand_satisfied_sector - -# Looping for every constituent -for consti in ['TMP','BOD','TDS','FC']: - - # Defining datasets with water quality concentrations and constituen's name - water_quality_concetration_consti = eval(f'self.input{consti}') - constituent = water_quality_dict[consti] - - # Ordering sectors from less to more stringent - thresholds = water_quality_thresholds[constituent] - thresholds = sorted(thresholds.items(), key=lambda item: item[1], reverse=True) - sector_order = [threshold[0] for threshold in thresholds] - -# Looping for every sector - for sector in sector_order: - - # Defining water quality thresholds - threshold_consti_sector = water_quality_thresholds[constituent][sector] - - # Defining actual water available depending on constituent threshold - available_surface_water_with_qual_conti_sector = ifthenelse(water_quality_concetration_consti < threshold_consti_sector, available_surface_water_with_qual, 0.) - -# Looping for every sector to keep the proportional distribution - for sector in sector_order: - - # Water allocation based on proportional water demand distribution, zonation scheme and further considerations (groundwater, desalinated water) - water_withdrawal_sector = f(current PCR-GLOBWB2 water allocation scheme using available_surface_water_with_qual_conti_sector) - - # Calculting remaining water available - available_surface_water_with_qual = available_surface_water_with_qual - water_withdrawal_sector - - # Tracking the water demand: satisficed and remaining - water_demand_remaining_sector = water_demand_remaining_sector - water_withdrawal_sector - water_demand_satisficed_sector = water_demand_satisficed_sector + water_withdrawal_sector diff --git a/notes_with_gabriel_2.py b/notes_with_gabriel_2.py deleted file mode 100644 index d2f478c12..000000000 --- a/notes_with_gabriel_2.py +++ /dev/null @@ -1,61 +0,0 @@ -# Principle -# - Keep proportional allocation of water withdrawn based on demand magnitude (current principle of PCR-GLOBWB 2) -# - Water allocation starts from the pixels with the best water quality - -# How many loops? -# - Water quality constituents (i.e., Temp, BOD, TDS, FC, [DO, N, P]) -# - Water quality thresholds for each constituent and for each sector - -# Input -# - available_surface_water (without considering quality) -# - water_demand_sector (e.g., irrigation, livestock, domestic and industry) -> (industry will be splitted into manufacturing and thermoelectric) -# - water_quality_concetration_constituent [Temp, BOD, TDS, FC] -# - water_quality_thresholds -water_quality_thresholds = { -'waterTemperature': ('irrigation':1000,'domestic':1000,'thermoelectric':1000,'manufacturing':1000,'livestock':1000), # Water temperature (oC) -'organic': ('irrigation':15 ,'domestic':5 ,'thermoelectric':30 ,'manufacturing':30 ,'livestock':1000), # Biochemical oxigen demand (mg/l) -'salinity': ('irrigation':450 ,'domestic':600 ,'thermoelectric':7000,'manufacturing':7000,'livestock':100000), # Total disolved solids (mg/l) -'pathogen': ('irrigation':1000,'domestic':1000,'thermoelectric':1000,'manufacturing':1000,'livestock':1000), # Fecal coliforms (cfu100/ml) -} - -water_quality_dict = {'TMP':'waterTemperature','BOD':'organic','TDS':'salinity':,'FC':'pathogen'} - -# Step 0 -available_surface_water_with_qual = available_surface_water -for sector in ['domestic','manufacturing','livestock','thermoelectric','irrigation']: - globals()['water_demand_remaining'+str(sector)] = water_demand_sector # water_demand_remaining_sector - globals()['water_demand_satisfied'+str(sector)] = 0. # water_demand_satisfied_sector - -# Looping for every constituent -for consti in ['TMP','BOD','TDS','FC']: - - # Defining datasets with water quality concentrations and constituen's name - water_quality_concetration_consti = eval(f'self.input{consti}') - constituent = water_quality_dict[consti] - - # Ordering sectors from less to more stringent - thresholds = water_quality_thresholds[constituent] - thresholds = sorted(thresholds.items(), key=lambda item: item[1], reverse=True) - sector_order = [threshold[0] for threshold in thresholds] - -# Looping for every sector - for sector in sector_order: - - # Defining water quality thresholds - threshold_consti_sector = water_quality_thresholds[constituent][sector] - - # Defining actual water available depending on constituent threshold - available_surface_water_with_qual_conti_sector = ifthenelse(water_quality_concetration_consti < threshold_consti_sector, available_surface_water_with_qual, 0.) - -# Looping for every sector to keep the proportional distribution - for sector in sector_order: - - # Water allocation based on proportional water demand distribution, zonation scheme and further considerations (groundwater, desalinated water) - water_withdrawal_sector = f(current PCR-GLOBWB2 water allocation scheme using available_surface_water_with_qual_conti_sector) - - # Calculting remaining water available - available_surface_water_with_qual = available_surface_water_with_qual - water_withdrawal_sector - - # Tracking the water demand: satisficed and remaining - water_demand_remaining_sector = water_demand_remaining_sector - water_withdrawal_sector - water_demand_satisficed_sector = water_demand_satisficed_sector + water_withdrawal_sector From c0e6c1d6f1a4ce0a212316afa35a31fc86deca56 Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Thu, 28 Sep 2023 10:08:50 +0200 Subject: [PATCH 30/31] this commit is only for backup that we may not need it --- config/setup_05min_no_water_quality.ini | 467 ------- config/setup_30min.ini | 2 +- model/bmi.py | 468 ------- model/bmiPcrglobwb.py | 405 ------ model/deterministic_runner_merging_ulysses.py | 402 ------ ...rministic_runner_merging_with_arguments.py | 457 ------ ...terministic_runner_parallel_for_ulysses.py | 558 -------- model/deterministic_runner_with_arguments.py | 629 --------- ..._runner_for_monthly_modflow_and_merging.py | 284 ---- ...for_monthly_modflow_and_merging_ulysses.py | 306 ---- ..._glue_with_parallel_and_modflow_options.py | 411 ------ ...arallel_and_modflow_options_for_jessica.py | 436 ------ model/landCover.py | 330 ++--- model/landSurface.py | 548 ++++---- ...rge_netcdf_30arcsec_europe_example_bash.sh | 18 - model/merge_netcdf_6_arcmin_example_bash.sh | 9 - model/merge_netcdf_6_arcmin_ulysses.py | 504 ------- model/merge_netcdf_aqueduct_2021.sh | 14 - model/merge_netcdf_europe.py | 552 -------- model/merge_netcdf_general.py | 573 -------- model/merge_pcraster_maps.py | 324 ----- ...rge_pcraster_maps_6_arcmin_example_bash.sh | 9 - model/merge_pcraster_maps_6_arcmin_ulysses.py | 354 ----- model/merge_pcraster_maps_general.py | 353 ----- model/surfaceWaterQuality.py | 124 -- model/waterUse.py | 1241 +++++++++++++++++ 26 files changed, 1644 insertions(+), 8134 deletions(-) delete mode 100644 config/setup_05min_no_water_quality.ini delete mode 100644 model/bmi.py delete mode 100644 model/bmiPcrglobwb.py delete mode 100644 model/deterministic_runner_merging_ulysses.py delete mode 100644 model/deterministic_runner_merging_with_arguments.py delete mode 100755 model/deterministic_runner_parallel_for_ulysses.py delete mode 100644 model/deterministic_runner_with_arguments.py delete mode 100644 model/etc/deterministic_runner_for_monthly_modflow_and_merging.py delete mode 100644 model/etc/deterministic_runner_for_monthly_modflow_and_merging_ulysses.py delete mode 100644 model/etc/deterministic_runner_glue_with_parallel_and_modflow_options.py delete mode 100644 model/etc/deterministic_runner_glue_with_parallel_and_modflow_options_for_jessica.py delete mode 100644 model/merge_netcdf_30arcsec_europe_example_bash.sh delete mode 100644 model/merge_netcdf_6_arcmin_example_bash.sh delete mode 100644 model/merge_netcdf_6_arcmin_ulysses.py delete mode 100644 model/merge_netcdf_aqueduct_2021.sh delete mode 100644 model/merge_netcdf_europe.py delete mode 100644 model/merge_netcdf_general.py delete mode 100644 model/merge_pcraster_maps.py delete mode 100644 model/merge_pcraster_maps_6_arcmin_example_bash.sh delete mode 100644 model/merge_pcraster_maps_6_arcmin_ulysses.py delete mode 100644 model/merge_pcraster_maps_general.py delete mode 100644 model/surfaceWaterQuality.py diff --git a/config/setup_05min_no_water_quality.ini b/config/setup_05min_no_water_quality.ini deleted file mode 100644 index c3ad75e77..000000000 --- a/config/setup_05min_no_water_quality.ini +++ /dev/null @@ -1,467 +0,0 @@ -[globalOptions] -# Please set the pcrglobwb output directory (outputDir) in an absolute path. -# - Please make sure that you have access to it. -#~ outputDir = /scratch/carde003/Mekong/1980-2010/Mekong_original/ -#~ outputDir = /scratch/carde003/M13/1980-2010/Mekong_original -outputDir = /scratch/carde003/ChaoPhraya/1980-2010/orig/ - -# Please set the clone map file (cloneMap), which defines the spatial resolution and extent of your study area. -# - Please make sure that the file is stored locally in your computing machine. -# - The file must be in the pcraster format. -#~ cloneMap = /scratch/carde003/Mekong/mekong.map -#~ cloneMap = /scratch/carde003/M13/clone_M13.map -cloneMap = /scratch/carde003/ChaoPhraya/clone_chaophraya.map - -# Set the input directory map in an absolute path. The input forcing and parameter directories and files will be relative to this. -# - The following is an example using files from the opendap server. -#~ inputDir = https://opendap.4tu.nl/thredds/dodsC/data2/pcrglobwb/version_2019_11_beta/pcrglobwb2_input/ -# - The following is an example using input files stored locally in your computing machine. -#~ inputDir = /quanta1/home/hydrowld/data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta/pcrglobwb2_input/ -# - on velocity -inputDir = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/ - -# The area/landmask of interest: -# If None, area/landmask is limited for cells with ldd value. -#~ landmask = None -#~ landmask = /scratch/carde003/Mekong/mekong.map -#~ landmask = /scratch/carde003/M13/mask_M13.map -landmask = /scratch/carde003/ChaoPhraya/clone_chaophraya.map - -# netcdf attributes for output files: -institution = Department of Physical Geography, Utrecht University -title = PCR-GLOBWB 2 output, with human factors (non-natural) -description = PCR-GLOBWB run with human factors (non-natural) at 5 arcmin resolution - -# Format: YYYY-MM-DD ; The model runs on daily time step. -startTime = 1980-01-01 -endTime = 2010-12-31 - -# spinning up options: -maxSpinUpsInYears = 0 -minConvForSoilSto = 0.0 -minConvForGwatSto = 0.0 -minConvForChanSto = 0.0 -minConvForTotlSto = 0.0 - - -[meteoOptions] -# Set the forcing temperature and precipitation files (relative to inputDir) -precipitationNC = global_30min/meteo/forcing/daily_precipitation_cru_era-interim_1979_to_2010.nc -temperatureNC = global_30min/meteo/forcing/daily_temperature_cru_era-interim_1979_to_2010.nc - -# Method to calculate referencePotETP (reference potential evaporation+transpiration) -# options are "Hamon" and "Input" ; If "Input", the netcdf input file must be given: -referenceETPotMethod = Input -refETPotFileNC = global_30min/meteo/forcing/daily_referencePotET_cru_era-interim_1979_to_2010.nc - - -[meteoDownscalingOptions] -# This section is for a 5 arcmin run, for downscaling meteorological forcing at 30 arcmin to 5 arcmin. -downscalePrecipitation = False -downscaleTemperature = True -downscaleReferenceETPot = False - -# Downscaling (based on the digital elevation model): -# the downscaling will be performed by providing the "cellIds" (meteoDownscaleIds) of lower resolution cells. -meteoDownscaleIds = global_05min/meteo/downscaling_from_30min/uniqueIds_30min.nc -highResolutionDEM = global_05min/meteo/downscaling_from_30min/gtopo05min.nc - -# lapse rates: -temperLapseRateNC = global_05min/meteo/downscaling_from_30min/temperature_slope.nc -precipLapseRateNC = global_05min/meteo/downscaling_from_30min/precipitation_slope.nc - -# downscaling criteria (TODO: remove these): -temperatCorrelNC = global_05min/meteo/downscaling_from_30min/temperature_correl.nc -precipitCorrelNC = global_05min/meteo/downscaling_from_30min/precipitation_correl.nc - -# windows length (unit: arc-degree) for smoothing/averaging forcing data (not recommended): -smoothingWindowsLength = 0 - - -[landSurfaceOptions] -debugWaterBalance = True -numberOfUpperSoilLayers = 2 - -# Soil and parameters -# - they are used for all land cover types, unless they are defined in certain land cover type options -# (e.g. different/various soil types for agriculture areas) -topographyNC = global_05min/landSurface/topography/topography_parameters_5_arcmin_october_2015.nc -soilPropertiesNC = global_05min/landSurface/soil/soilProperties5ArcMin.nc - -# Irrigation properties -includeIrrigation = True - -# - netcdf time series for historical expansion of irrigation areas (unit: hectares). -# (note: The resolution of this map must be consisten with the resolution of cellArea) -historicalIrrigationArea = global_05min/waterUse/irrigation/irrigated_areas/irrigationArea05ArcMin.nc - -# - pcraster map/value defining irrigation efficiency (dimensionless) - optional -irrigationEfficiency = global_30min/waterUse/irrigation/irrigation_efficiency/efficiency.nc - -# Water quality: water temperature, biochemical oxigen demand, salinity, fecal coliforms -considerWaterQuality = False - -# - input files -inputFileSwTemperature = None -inputFileBOD = /scratch/sutan101/data/dynqual/organic_monthlyAvg_1980_2019_without_time_bnds.nc -inputFileTotalDissolvedSolid = None -inputFileFecalColiform = None - -# - water constituents' threshold values -# -- Biochemical Oxigen Demand (mg/L) -thresholdBODForIrrigation = 15. -thresholdBODForLivestock = None -thresholdBODForIndustrial = 20. -thresholdBODForDomestic = 5. - -# Water demands: domestic, industrial and livestock water demand data (unit must be in m.day-1) -includeDomesticWaterDemand = True -includeIndustryWaterDemand = True -includeLivestockWaterDemand = True - -domesticWaterDemandFile = global_05min/waterUse/waterDemand/domestic/domestic_water_demand_version_april_2015.nc -industryWaterDemandFile = global_05min/waterUse/waterDemand/industry/industry_water_demand_version_april_2015.nc -livestockWaterDemandFile = global_05min/waterUse/waterDemand/livestock/livestock_water_demand_version_april_2015.nc - -# Desalination water supply (maximum/potential/capacity) -desalinationWater = global_05min/waterUse/desalination/desalination_water_version_april_2015.nc - -# Pooling zones: zone IDs (scale) at which allocations of groundwater and surface water (as well as desalinated water) are performed -allocationSegmentsForGroundSurfaceWater = global_05min/waterUse/abstraction_zones/abstraction_zones_60min_05min.nc - -# pcraster maps defining the partitioning of groundwater - surface water source - -# - predefined surface water - groundwater partitioning for irrigation demand (e.g. based on Siebert, Global Map of Irrigation Areas version 5) -irrigationSurfaceWaterAbstractionFractionData = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_SWFRAC.nc -# -- quality map -irrigationSurfaceWaterAbstractionFractionDataQuality = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_QUAL.nc - -# - threshold values defining the preference for surface water source for irrigation purpose -# -- treshold to maximize surface water irrigation use (cells with irrSurfaceWaterAbstractionFraction above this will prioritize irrigation surface water use) -treshold_to_maximize_irrigation_surface_water = 0.50 -# -- treshold to minimize fossil water withdrawal for irrigation (cells with irrSurfaceWaterAbstractionFraction below this have no fossil withdrawal for irrigation) -treshold_to_minimize_fossil_groundwater_irrigation = 0.70 - -# - predefined surface water - groundwater partitioning for non irrigation demand (e.g. based on McDonald, 2014) -maximumNonIrrigationSurfaceWaterAbstractionFractionData = global_30min/waterUse/source_partitioning/surface_water_fraction_for_non_irrigation/max_city_sw_fraction.nc - - -[forestOptions] -name = forest -debugWaterBalance = True - -# snow module properties -snowModuleType = Simple -freezingT = 0.0 -degreeDayFactor = 0.0025 -snowWaterHoldingCap = 0.1 -refreezingCoeff = 0.05 - -# other paramater values -minTopWaterLayer = 0.0 -minCropKC = 0.2 - -cropCoefficientNC = global_05min/landSurface/landCover/naturalTall/cropCoefficientForest.nc -interceptCapNC = global_05min/landSurface/landCover/naturalTall/interceptCapInputForest.nc -coverFractionNC = global_05min/landSurface/landCover/naturalTall/coverFractionInputForest.nc - -landCoverMapsNC = None - -# If NC file is not provided, we have to provide the following pcraster maps: -fracVegCover = global_05min/landSurface/landCover/naturalTall/vegf_tall.nc -minSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/minf_tall_permafrost.nc -maxSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/maxf_tall.nc -rootFraction1 = global_05min/landSurface/landCover/naturalTall/rfrac1_tall.nc -rootFraction2 = global_05min/landSurface/landCover/naturalTall/rfrac2_tall.nc -maxRootDepth = 1.0 -# Note: The maxRootDepth is not used for non irrigated land cover type. - -# Parameters for the Arno's scheme: -arnoBeta = None -# If arnoBeta is defined, the soil water capacity distribution is based on this. -# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. - -# initial conditions: -interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_forest_1999-12-31.nc -snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_forest_1999-12-31.nc -snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_forest_1999-12-31.nc -topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_forest_1999-12-31.nc -storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_forest_1999-12-31.nc -storLowIni = global_05min/initialConditions/non-natural/1999/storLow_forest_1999-12-31.nc -interflowIni = global_05min/initialConditions/non-natural/1999/interflow_forest_1999-12-31.nc - - -[grasslandOptions] -name = grassland -debugWaterBalance = True - -# snow module properties -snowModuleType = Simple -freezingT = 0.0 -degreeDayFactor = 0.0025 -snowWaterHoldingCap = 0.1 -refreezingCoeff = 0.05 - -# other paramater values -minTopWaterLayer = 0.0 -minCropKC = 0.2 - -cropCoefficientNC = global_05min/landSurface/landCover/naturalShort/cropCoefficientGrassland.nc -interceptCapNC = global_05min/landSurface/landCover/naturalShort/interceptCapInputGrassland.nc -coverFractionNC = global_05min/landSurface/landCover/naturalShort/coverFractionInputGrassland.nc - -landCoverMapsNC = None -# If NC file is not provided, we have to provide the following values: -fracVegCover = global_05min/landSurface/landCover/naturalShort/vegf_short.nc -minSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/minf_short_permafrost.nc -maxSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/maxf_short.nc -rootFraction1 = global_05min/landSurface/landCover/naturalShort/rfrac1_short.nc -rootFraction2 = global_05min/landSurface/landCover/naturalShort/rfrac2_short.nc -maxRootDepth = 0.5 -# Note: The maxRootDepth is not used for non irrigated land cover type. - -# Parameters for the Arno's scheme: -arnoBeta = None -# If arnoBeta is defined, the soil water capacity distribution is based on this. -# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. - -# initial conditions: -interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_grassland_1999-12-31.nc -snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_grassland_1999-12-31.nc -snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_grassland_1999-12-31.nc -topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_grassland_1999-12-31.nc -storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_grassland_1999-12-31.nc -storLowIni = global_05min/initialConditions/non-natural/1999/storLow_grassland_1999-12-31.nc -interflowIni = global_05min/initialConditions/non-natural/1999/interflow_grassland_1999-12-31.nc - - -[irrPaddyOptions] -name = irrPaddy -debugWaterBalance = True - -# snow module properties -snowModuleType = Simple -freezingT = 0.0 -degreeDayFactor = 0.0025 -snowWaterHoldingCap = 0.1 -refreezingCoeff = 0.05 - -# land cover map -landCoverMapsNC = None -# If NC file is not provided, we have to provide the following values: -fracVegCover = global_05min/landSurface/landCover/irrPaddy/fractionPaddy.nc -minSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/minf_paddy_permafrost.nc -maxSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/maxf_paddy.nc -rootFraction1 = global_30min/landSurface/landCover/irrPaddy/rfrac1_paddy.nc -rootFraction2 = global_30min/landSurface/landCover/irrPaddy/rfrac2_paddy.nc -maxRootDepth = 0.5 - -# Parameters for the Arno's scheme: -arnoBeta = None -# If arnoBeta is defined, the soil water capacity distribution is based on this. -# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. - -# other paramater values -minTopWaterLayer = 0.05 -minCropKC = 0.2 -cropDeplFactor = 0.2 -minInterceptCap = 0.0002 - -cropCoefficientNC = global_30min/landSurface/landCover/irrPaddy/Global_CropCoefficientKc-IrrPaddy_30min.nc - -# initial conditions: -interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrPaddy_1999-12-31.nc -snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrPaddy_1999-12-31.nc -snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrPaddy_1999-12-31.nc -topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrPaddy_1999-12-31.nc -storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrPaddy_1999-12-31.nc -storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrPaddy_1999-12-31.nc -interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrPaddy_1999-12-31.nc - - -[irrNonPaddyOptions] -name = irrNonPaddy -debugWaterBalance = True - -# snow module properties -snowModuleType = Simple -freezingT = 0.0 -degreeDayFactor = 0.0025 -snowWaterHoldingCap = 0.1 -refreezingCoeff = 0.05 -# -landCoverMapsNC = None -# If NC file is not provided, we have to provide the following values: -fracVegCover = global_05min/landSurface/landCover/irrNonPaddy/fractionNonPaddy.nc -minSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/minf_nonpaddy_permafrost.nc -maxSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/maxf_nonpaddy.nc -rootFraction1 = global_30min/landSurface/landCover/irrNonPaddy/rfrac1_nonpaddy.nc -rootFraction2 = global_30min/landSurface/landCover/irrNonPaddy/rfrac2_nonpaddy.nc -maxRootDepth = 1.0 - -# Parameters for the Arno's scheme: -arnoBeta = None -# If arnoBeta is defined, the soil water capacity distribution is based on this. -# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. - -# other paramater values -minTopWaterLayer = 0.0 -minCropKC = 0.2 -cropDeplFactor = 0.5 -minInterceptCap = 0.0002 - -cropCoefficientNC = global_30min/landSurface/landCover/irrNonPaddy/Global_CropCoefficientKc-IrrNonPaddy_30min.nc - -# initial conditions: -interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrNonPaddy_1999-12-31.nc -snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrNonPaddy_1999-12-31.nc -snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrNonPaddy_1999-12-31.nc -topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrNonPaddy_1999-12-31.nc -storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrNonPaddy_1999-12-31.nc -storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrNonPaddy_1999-12-31.nc -interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrNonPaddy_1999-12-31.nc - - - -[groundwaterOptions] -debugWaterBalance = True - -# The file will containspecificYield (m3.m-3), kSatAquifer (m.day-1), recessionCoeff (day-1) -groundwaterPropertiesNC = global_05min/groundwater/properties/groundwaterProperties5ArcMin.nc -# - minimum value for groundwater recession coefficient (day-1) -minRecessionCoeff = 1.0e-4 - -# some options for constraining groundwater abstraction -limitFossilGroundWaterAbstraction = True -estimateOfRenewableGroundwaterCapacity = 0.0 -estimateOfTotalGroundwaterThickness = global_05min/groundwater/aquifer_thickness_estimate/thickness_05min.nc - -# minimum and maximum total groundwater thickness -minimumTotalGroundwaterThickness = 100. -maximumTotalGroundwaterThickness = None - -# annual pumping capacity for each region (unit: billion cubic meter per year), should be given in a netcdf file -pumpingCapacityNC = global_30min/waterUse/groundwater_pumping_capacity/regional_abstraction_limit.nc - -# initial conditions: -storGroundwaterIni = global_05min/initialConditions/non-natural/1999/storGroundwater_1999-12-31.nc -storGroundwaterFossilIni = global_05min/initialConditions/non-natural/1999/storGroundwaterFossil_1999-12-31.nc - -# additional initial conditions for pumping behaviors -avgNonFossilGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationLong_1999-12-31.nc -avgNonFossilGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationShort_1999-12-31.nc -avgTotalGroundwaterAbstractionIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAbstraction_1999-12-31.nc -avgTotalGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationLong_1999-12-31.nc -avgTotalGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationShort_1999-12-31.nc - -# additional initial conditions (needed only for MODFLOW run) -relativeGroundwaterHeadIni = global_05min/initialConditions/non-natural/1999/relativeGroundwaterHead_1999-12-31.nc -baseflowIni = global_05min/initialConditions/non-natural/1999/baseflow_1999-12-31.nc - -# zonal IDs (scale) at which zonal allocation of groundwater is performed -allocationSegmentsForGroundwater = global_05min/waterUse/abstraction_zones/abstraction_zones_30min_05min.nc - - - -[routingOptions] -debugWaterBalance = True - -# drainage direction map -lddMap = global_05min/routing/ldd_and_cell_area/lddsound_05min.nc - -# cell area (unit: m2) -cellAreaMap = global_05min/routing/ldd_and_cell_area/cellsize05min.correct.nc - -# routing method: -routingMethod = accuTravelTime -#~ routingMethod = kinematicWave - -# manning coefficient -manningsN = 0.04 - -# Option for flood plain simulation -dynamicFloodPlain = True - -# manning coefficient for floodplain -floodplainManningsN = 0.07 - -# channel gradient -gradient = global_05min/routing/channel_properties/channel_gradient.nc - -# constant channel depth -constantChannelDepth = global_05min/routing/channel_properties/bankfull_depth.nc - -# constant channel width (optional) -constantChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc - -# minimum channel width (optional) -minimumChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc - -# channel properties for flooding -# - if None, it will be estimated from (bankfull) channel depth (m) and width (m) -bankfullCapacity = None - -# files for relative elevation (above minimum dem) -relativeElevationFiles = global_05min/routing/channel_properties/dzRel%04d.nc -relativeElevationLevels = 0.0, 0.01, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00 - -# composite crop factors for WaterBodies: -cropCoefficientWaterNC = global_30min/routing/kc_surface_water/cropCoefficientForOpenWater.nc -minCropWaterKC = 1.00 - -# lake and reservoir parameters -waterBodyInputNC = global_05min/routing/surface_water_bodies/waterBodies5ArcMin.nc -onlyNaturalWaterBodies = False - -# initial conditions: -waterBodyStorageIni = global_05min/initialConditions/non-natural/1999/waterBodyStorage_1999-12-31.nc -channelStorageIni = global_05min/initialConditions/non-natural/1999/channelStorage_1999-12-31.nc -readAvlChannelStorageIni = global_05min/initialConditions/non-natural/1999/readAvlChannelStorage_1999-12-31.nc -avgDischargeLongIni = global_05min/initialConditions/non-natural/1999/avgDischargeLong_1999-12-31.nc -avgDischargeShortIni = global_05min/initialConditions/non-natural/1999/avgDischargeShort_1999-12-31.nc -m2tDischargeLongIni = global_05min/initialConditions/non-natural/1999/m2tDischargeLong_1999-12-31.nc -avgBaseflowLongIni = global_05min/initialConditions/non-natural/1999/avgBaseflowLong_1999-12-31.nc -riverbedExchangeIni = global_05min/initialConditions/non-natural/1999/riverbedExchange_1999-12-31.nc - -# initial condition of sub-time step discharge (needed for estimating number of time steps in kinematic wave methods) -subDischargeIni = global_05min/initialConditions/non-natural/1999/subDischarge_1999-12-31.nc - -avgLakeReservoirInflowShortIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirInflowShort_1999-12-31.nc -avgLakeReservoirOutflowLongIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirOutflowLong_1999-12-31.nc - -# number of days (timesteps) that have been performed for spinning up initial conditions in the routing module (i.e. channelStorageIni, avgDischargeLongIni, avgDischargeShortIni, etc.) -timestepsToAvgDischargeIni = global_05min/initialConditions/non-natural/1999/timestepsToAvgDischarge_1999-12-31.nc -# Note that: -# - maximum number of days (timesteps) to calculate long term average flow values (default: 5 years = 5 * 365 days = 1825) -# - maximum number of days (timesteps) to calculate short term average values (default: 1 month = 1 * 30 days = 30) - - - -[reportingOptions] -# output files that will be written in the disk in netcdf files: -# - daily resolution (discharge,totalRunoff,gwRecharge,totalGroundwaterAbstraction,surfaceWaterStorage) -outDailyTotNC = None - -# - monthly resolution -# (outMonthTotNC: actualET,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,totLandSurfaceActuaET,totalLandSurfacePotET,waterBodyActEvaporation,waterBodyPotEvaporation) -# (outMonthAvgNC: temperature,dynamicFracWat,surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalActiveStorageThickness,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage) -# (outMonthEndNC: storGroundwater,storGroundwaterFossil,waterBodyStorage,channelStorage,totalWaterStorageThickness,totalActiveStorageThickness) -outMonthTotNC = irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction -outMonthAvgNC = discharge -outMonthEndNC = None - -# - annual resolution -# (outAnnuaTotNC: totalEvaporation,precipitation,gwRecharge,totalRunoff,baseflow,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,totalGroundwaterAbstraction,totalAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,runoff,actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,irrigationWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,precipitation_at_irrigation,netLqWaterToSoil_at_irrigation,evaporation_from_irrigation,transpiration_from_irrigation,referencePotET) -# (outAnnuaAvgNC: temperature,discharge,surfaceWaterStorage,waterBodyStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage,fractionWaterBodyEvaporation,fractionTotalEvaporation,fracSurfaceWaterAllocation,fracDesalinatedWaterAllocation,gwRecharge) -# (outAnnuaEndNC: surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness) -outAnnuaTotNC = None -outAnnuaAvgNC = None -outAnnuaEndNC = None - -# - monthly and annual maxima (channelStorage,dynamicFracWat,floodVolume,floodDepth,surfaceWaterLevel,discharge,totalRunoff) -outMonthMaxNC = None -outAnnuaMaxNC = None - -# netcdf format and zlib setup -formatNetCDF = NETCDF4 -zlib = True diff --git a/config/setup_30min.ini b/config/setup_30min.ini index e687465d5..a5178501c 100644 --- a/config/setup_30min.ini +++ b/config/setup_30min.ini @@ -366,7 +366,7 @@ timestepsToAvgDischargeIni = global_30min/initialConditions/non-natural/cons # - daily resolution outDailyTotNC = discharge,totalRunoff,gwRecharge,totalGroundwaterAbstraction,surfaceWaterStorage # - monthly resolution -outMonthTotNC = actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,totLandSurfaceActuaET,totalLandSurfacePotET,waterBodyActEvaporation,waterBodyPotEvaporation +outMonthTotNC = actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,irrWaterConsumption,nonIrrReturnFlow,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,totLandSurfaceActuaET,totalLandSurfacePotET,waterBodyActEvaporation,waterBodyPotEvaporation outMonthAvgNC = discharge,temperature,dynamicFracWat,surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalActiveStorageThickness,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage outMonthEndNC = storGroundwater,storGroundwaterFossil,waterBodyStorage,channelStorage,totalWaterStorageThickness,totalActiveStorageThickness # - annual resolution diff --git a/model/bmi.py b/model/bmi.py deleted file mode 100644 index b8157714f..000000000 --- a/model/bmi.py +++ /dev/null @@ -1,468 +0,0 @@ -#! /usr/bin/env python - -""" -To create this file the original version of the CSDMS BMI Python Language Binding -from https://github.com/csdms/bmi-python/blob/master/bmi/bmi.py was extended with a number of extra functions -so that it can be used in OpenDA. All extra functions are contained in the class EBmi. - -Also added additional comments. Where the original version of the CSDMS BMI Python Language Binding was ambiguous, the -information from http://csdms.colorado.edu/wiki/BMI_Description and common sense were used to fill in most of the gaps. -""" - -from abc import ABCMeta, abstractmethod - -import six - - -class BmiGridType(object): - """ - Enumeration with grid types. - """ - - UNKNOWN = 0 - UNIFORM = 1 - RECTILINEAR = 2 - STRUCTURED = 3 - UNSTRUCTURED = 4 - - -@six.add_metaclass(ABCMeta) -class Bmi(object): - """ - Interface (abstract base class) for a model that implements the CSDMS BMI (Basic Model Interface). - """ - - """ - Model Control Functions - """ - - @abstractmethod - def initialize(self, filename): - """ - Initialize the model. - - Input parameters: - File filename: path and name of the configuration file for the model. - """ - raise NotImplementedError - - @abstractmethod - def update(self): - """ - Update the model to the next time step. - """ - raise NotImplementedError - - @abstractmethod - def update_until(self, time): - """ - Update the model until the given time. - - Input parameters: - double time: time in the units and epoch returned by the function get_time_units. - """ - raise NotImplementedError - - @abstractmethod - def update_frac(self, time_frac): - """ - ??? - - Input parameters: - double time_frac: ??? - """ - raise NotImplementedError - - @abstractmethod - def finalize(self): - """ - Finalize the model. - """ - raise NotImplementedError - - """ - Model Information Functions - """ - - @abstractmethod - def get_component_name(self): - """ - Return value: - String: identifier of the model. - """ - raise NotImplementedError - - @abstractmethod - def get_input_var_names(self): - """ - Return value: - List of String objects: identifiers of all input variables of the model. - """ - raise NotImplementedError - - @abstractmethod - def get_output_var_names(self): - """ - Return value: - List of String objects: identifiers of all output variables of the model. - """ - raise NotImplementedError - - """ - Variable Information Functions - """ - - @abstractmethod - def get_var_type(self, long_var_name): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - String: data type of the values of the given variable, e.g. Numpy datatype string. - """ - raise NotImplementedError - - @abstractmethod - def get_var_units(self, long_var_name): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - String: unit of the values of the given variable. Return a string formatted using the UDUNITS standard from Unidata. - """ - raise NotImplementedError - - @abstractmethod - def get_var_rank(self, long_var_name): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - Integer: number of dimensions of the given variable. - """ - raise NotImplementedError - - @abstractmethod - def get_var_size(self, long_var_name): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - Integer: total number of values contained in the given variable, e.g. gridCellCount. - """ - raise NotImplementedError - - @abstractmethod - def get_var_nbytes(self, long_var_name): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - ???: ??? - """ - raise NotImplementedError - - @abstractmethod - def get_start_time(self): - """ - Return value: - double: start time of the model in the units and epoch returned by the function get_time_units. - """ - raise NotImplementedError - - @abstractmethod - def get_current_time(self): - """ - Return value: - double: current time of the model in the units and epoch returned by the function get_time_units. - """ - raise NotImplementedError - - @abstractmethod - def get_end_time(self): - """ - Return value: - double: end time of the model in the units and epoch returned by the function get_time_units. - """ - raise NotImplementedError - - @abstractmethod - def get_time_step(self): - """ - Return value: - double: duration of one time step of the model in the units returned by the function get_time_units. - """ - raise NotImplementedError - - @abstractmethod - def get_time_units(self): - """ - Return value: - String: unit and epoch of time in the model. Return a string formatted using the UDUNITS standard from Unidata. - """ - raise NotImplementedError - - """ - Variable Getter and Setter Functions - """ - - @abstractmethod - def get_value(self, long_var_name): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - Numpy array of values in the data type returned by the function get_var_type: all values of the given variable. - For a 2D grid these values must be in row major order, starting with the bottom row. - """ - raise NotImplementedError - - @abstractmethod - def get_value_at_indices(self, long_var_name, inds): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - List of Lists of integers inds: each nested List contains one index for each dimension of the given variable, - i.e. each nested List indicates one element in the multi-dimensional variable array, - e.g. [[0, 0, 0], [0, 0, 1], [0, 15, 19], [0, 15, 20], [0, 15, 21]] indicates 5 elements in a 3D grid. - For a grid the indices start counting at the grid origin, i.e. the lower left corner for a 2D grid. - - Return value: - Numpy array of values in the data type returned by the function get_var_type: one value for each of the indicated elements. - """ - raise NotImplementedError - - @abstractmethod - def set_value(self, long_var_name, src): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - Numpy array of values in the data type returned by the function get_var_type src: all values to set for the given variable. - For a 2D grid these values must be in row major order, starting with the bottom row. - """ - raise NotImplementedError - - @abstractmethod - def set_value_at_indices(self, long_var_name, inds, src): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - List of Lists of integers inds: each nested List contains one index for each dimension of the given variable, - i.e. each nested List indicates one element in the multi-dimensional variable array, - e.g. [[0, 0], [0, 1], [15, 19], [15, 20], [15, 21]] indicates 5 elements in a 2D grid. - For a grid the indices start counting at the grid origin, i.e. the lower left corner for a 2D grid. - Numpy array of values in the data type returned by the function get_var_type src: one value to set for each of the indicated elements. - """ - raise NotImplementedError - - """ - Grid Information Functions - """ - - @abstractmethod - def get_grid_type(self, long_var_name): - """ - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - BmiGridType type of the grid geometry of the given variable. - """ - raise NotImplementedError - - @abstractmethod - def get_grid_shape(self, long_var_name): - """ - Only return something for variables with a uniform, rectilinear or structured grid. Otherwise raise ValueError. - - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - List of integers: the sizes of the dimensions of the given variable, e.g. [400, 500] for a 2D grid with 400 rows and 500 columns. - The dimensions are ordered [y, x] or [z, y, x]. - """ - raise NotImplementedError - - @abstractmethod - def get_grid_spacing(self, long_var_name): - """ - Only return something for variables with a uniform grid. Otherwise raise ValueError. - - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - List of doubles: the size of a grid cell for each of the dimensions of the given variable, e.g. [cellHeight, cellWidth] for a 2D grid. - The dimensions are ordered [y, x] or [z, y, x]. - """ - raise NotImplementedError - - @abstractmethod - def get_grid_origin(self, long_var_name): - """ - Only return something for variables with a uniform grid. Otherwise raise ValueError. - - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - List of doubles: the coordinate of the grid origin for each of the dimensions of the given variable. - For a 2D grid this must be the lower left corner of the grid. - The dimensions are ordered [y, x] or [z, y, x]. - """ - raise NotImplementedError - - @abstractmethod - def get_grid_x(self, long_var_name): - """ - Only return something for variables with a rectilinear, structured or unstructured grid. Otherwise raise ValueError. - - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - Numpy array of doubles: x coordinate of grid cell center for each grid cell, in the same order as the values returned by function get_value. - For a rectilinear grid: x coordinate of column center for each column. - """ - raise NotImplementedError - - @abstractmethod - def get_grid_y(self, long_var_name): - """ - Only return something for variables with a rectilinear, structured or unstructured grid. Otherwise raise ValueError. - - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - Numpy array of doubles: y coordinate of grid cell center for each grid cell, in the same order as the values returned by function get_value. - For a rectilinear grid: y coordinate of row center for each row. - """ - raise NotImplementedError - - @abstractmethod - def get_grid_z(self, long_var_name): - """ - Only return something for variables with a rectilinear, structured or unstructured grid. Otherwise raise ValueError. - - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - Numpy array of doubles: z coordinate of grid cell center for each grid cell, in the same order as the values returned by function get_value. - For a rectilinear grid: z coordinate of layer center for each layer. - """ - raise NotImplementedError - - @abstractmethod - def get_grid_connectivity(self, long_var_name): - """ - Only return something for variables with an unstructured grid. Otherwise raise ValueError. - - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - ??? - """ - raise NotImplementedError - - @abstractmethod - def get_grid_offset(self, long_var_name): - """ - Only return something for variables with an unstructured grid. Otherwise raise ValueError. - - Input parameters: - String long_var_name: identifier of a variable in the model. - - Return value: - ??? - """ - raise NotImplementedError - - -class EBmi(Bmi): - - @abstractmethod - def initialize_config(self, config_file): - """ - First step of two-phase initialize. In this step only the configuration is read in. - This allows a user to then change settings and parameters before fully initializing the model - """ - raise NotImplementedError - - @abstractmethod - def initialize_model(self, source_directory): - """ - Second step of two-phase initialize. In this step the model is setup, and will now allow - reading/setting values. - """ - raise NotImplementedError - - @abstractmethod - def set_start_time(self, start_time): - """ - Set the start time of the model. Can usually only be called after initialize_config - and before initialize_model. Expects a value in the time units of the model - """ - raise NotImplementedError - - @abstractmethod - def set_end_time(self, end_time): - """ - Set the end time of the model. Can usually only be called after initialize_config - and before initialize_model. Expects a value in the time units of the model. - """ - raise NotImplementedError - - @abstractmethod - def get_attribute_names(self): - """ - Gets a list of all supported attributes for this model. Attributes can be considered - the meta-data of a model, for instance author, version, model specific settings, etc. - """ - raise NotImplementedError - - @abstractmethod - def get_attribute_value(self, attribute_name): - """ - Gets the value of a certain attribute for this model. Attributes can be considered - the meta-data of a model, for instance author, version, model specific settings, etc. - """ - raise NotImplementedError - - @abstractmethod - def set_attribute_value(self, attribute_name, attribute_value): - """ - Sets the value of a certain attribute for this model. Usually only string values are allowed. - Attributes can be considered the meta-data of a model, for instance author, version, model specific settings, etc. - """ - raise NotImplementedError - - @abstractmethod - def save_state(self, destination_directory): - """ - Ask the model to write its complete internal current state to one or more state files in the given directory. - Afterwards the given directory should only contain the state files and nothing else. - - Input parameters: - File destination_directory: the directory in which the state files should be written. - """ - raise NotImplementedError - - @abstractmethod - def load_state(self, source_directory): - """ - Ask the model to load its complete internal current state from one or more state files in the given directory. - - Input parameters: - File source_directory: the directory from which the state files should be read. - """ - raise NotImplementedError diff --git a/model/bmiPcrglobwb.py b/model/bmiPcrglobwb.py deleted file mode 100644 index 7b76e65b1..000000000 --- a/model/bmiPcrglobwb.py +++ /dev/null @@ -1,405 +0,0 @@ -#! /usr/bin/env python -from __future__ import print_function - -from configuration import Configuration -from pcrglobwb import PCRGlobWB -import pcraster as pcr -import numpy as np -from currTimeStep import ModelTime -import sys -import logging -from reporting import Reporting -from imagemean import downsample -from bmi import EBmi -from bmi import BmiGridType -import datetime -import os - -logger = logging.getLogger(__name__) - - -class BmiPCRGlobWB(EBmi): - #we use the same epoch as pcrglobwb netcdf reporting - def days_since_industry_epoch(self, modeltime): - return (modeltime - datetime.date(1901, 1, 1)).days - - def in_modeltime(self, days_since_industry_epoch): - return (datetime.datetime(1901, 1, 1) + datetime.timedelta(days=days_since_industry_epoch)).date() - - def calculate_shape(self): - # return pcr.pcr2numpy(self.model.landmask, 1e20).shape - return (pcr.clone().nrRows(), pcr.clone().nrCols()) - - #BMI initialize (as a single step) - def initialize(self, fileName): - self.initialize_config(fileName) - self.initialize_model() - - #EBMI initialize (first step of two) - def initialize_config(self, fileName): - logger.info("PCRGlobWB: initialize_config") - - try: - - self.configuration = Configuration(fileName, relative_ini_meteo_paths = True) - pcr.setclone(self.configuration.cloneMap) - - # set start and end time based on configuration - self.model_time = ModelTime() - self.model_time.getStartEndTimeSteps(self.configuration.globalOptions['startTime'], - self.configuration.globalOptions['endTime']) - - self.model_time.update(0) - - self.shape = self.calculate_shape() - - logger.info("Shape of maps is %s", str(self.shape)) - - self.model = None - - except: - import traceback - traceback.print_exc() - raise - - - #EBMI initialize (second step of two) - def initialize_model(self): - if self.model is not None: - #already initialized - return - - try: - - logger.info("PCRGlobWB: initialize_model") - - initial_state = None - self.model = PCRGlobWB(self.configuration, self.model_time, initial_state) - - self.reporting = Reporting(self.configuration, self.model, self.model_time) - - logger.info("Shape of maps is %s", str(self.shape)) - - logger.info("PCRGlobWB Initialized") - - except: - import traceback - traceback.print_exc() - raise - - - - def update(self): - timestep = self.model_time.timeStepPCR - - self.model_time.update(timestep + 1) - - self.model.read_forcings() - self.model.update(report_water_balance=True) - self.reporting.report() - - # #numpy = pcr.pcr2numpy(self.model.landSurface.satDegUpp000005, 1e20) - # numpy = pcr.pcr2numpy(self.model.landSurface.satDegUpp000005, np.NaN) - # print numpy.shape - # print numpy - - - def update_until(self, time): - while self.get_current_time() + 0.001 < time: - self.update() - - def update_frac(self, time_frac): - raise NotImplementedError - - def finalize(self): - pass - - def get_component_name(self): - return "pcrglobwb" - - def get_input_var_names(self): - return ["top_layer_soil_saturation"] - - def get_output_var_names(self): - return ["top_layer_soil_saturation"] - - def get_var_type(self, long_var_name): - return 'float64' - - def get_var_units(self, long_var_name): - #TODO: this is not a proper unit - return '1' - - def get_var_rank(self, long_var_name): - return 0 - - def get_var_size(self, long_var_name): - return np.prod(self.get_grid_shape(long_var_name)) - - def get_var_nbytes(self, long_var_name): - return self.get_var_size(long_var_name) * np.float64.itemsize - - def get_start_time(self): - return self.days_since_industry_epoch(self.model_time.startTime) - - def get_current_time(self): - return self.days_since_industry_epoch(self.model_time.currTime) - - def get_end_time(self): - return self.days_since_industry_epoch(self.model_time.endTime) - - def get_time_step(self): - return 1 - - def get_time_units(self): - return "Days since 1901-01-01" - - def get_value(self, long_var_name): - logger.info("getting value for var %s", long_var_name) - - if (long_var_name == "top_layer_soil_saturation"): - - if self.model is not None and hasattr(self.model.landSurface, 'satDegUpp000005'): - - #first make all NanS into 0.0 with cover, then cut out the model using the landmask. - # This should not actually make a difference. - remasked = pcr.ifthen(self.model.landmask, pcr.cover(self.model.landSurface.satDegUpp000005, 0.0)) - - pcr.report(self.model.landSurface.satDegUpp000005, "value.map") - pcr.report(remasked, "remasked.map") - - value = pcr.pcr2numpy(remasked, np.NaN) - - else: - logger.info("model has not run yet, returning empty state for top_layer_soil_saturation") - value = pcr.pcr2numpy(pcr.scalar(0.0), np.NaN) - - # print "getting var", value - # sys.stdout.flush() - - doubles = value.astype(np.float64) - - # print "getting var as doubles!!!!", doubles - - result = np.flipud(doubles) - - # print "getting var as doubles flipped!!!!", result - # sys.stdout.flush() - - return result - else: - raise Exception("unknown var name" + long_var_name) - - def get_value_at_indices(self, long_var_name, inds): - raise NotImplementedError - - # def get_satDegUpp000005_from_observation(self): - # - # # assumption for observation values - # # - this should be replaced by values from the ECV soil moisture value (sattelite data) - # # - uncertainty should be included here - # # - note that the value should be between 0.0 and 1.0 - # observed_satDegUpp000005 = pcr.min(1.0,\ - # pcr.max(0.0,\ - # pcr.normal(pcr.boolean(1)) + 1.0)) - # return observed_satDegUpp000005 - - def set_satDegUpp000005(self, src): - mask = np.isnan(src) - src[mask] = 1e20 - observed_satDegUpp000005 = pcr.numpy2pcr(pcr.Scalar, src, 1e20) - - pcr.report(observed_satDegUpp000005, "observed.map") - - constrained_satDegUpp000005 = pcr.min(1.0, pcr.max(0.0, observed_satDegUpp000005)) - - pcr.report(constrained_satDegUpp000005, "constrained.map") - - pcr.report(self.model.landSurface.satDegUpp000005, "origmap.map") - diffmap = constrained_satDegUpp000005 - self.model.landSurface.satDegUpp000005 - pcr.report(diffmap, "diffmap.map") - - # ratio between observation and model - ratio_between_observation_and_model = pcr.ifthenelse(self.model.landSurface.satDegUpp000005 > 0.0, - constrained_satDegUpp000005 / \ - self.model.landSurface.satDegUpp000005, 0.0) - - # updating upper soil states for all lad cover types - for coverType in self.model.landSurface.coverTypes: - # correcting upper soil state (storUpp000005) - self.model.landSurface.landCoverObj[coverType].storUpp000005 *= ratio_between_observation_and_model - - # if model value = 0.0, storUpp000005 is calculated based on storage capacity (model parameter) and observed saturation degree - self.model.landSurface.landCoverObj[coverType].storUpp000005 = pcr.ifthenelse( - self.model.landSurface.satDegUpp000005 > 0.0, \ - self.model.landSurface.landCoverObj[coverType].storUpp000005, \ - constrained_satDegUpp000005 * self.model.landSurface.parameters.storCapUpp000005) - # correct for any scaling issues (value < 0 or > 1 do not make sense - self.model.landSurface.landCoverObj[coverType].storUpp000005 = pcr.min(1.0, pcr.max(0.0, - self.model.landSurface.landCoverObj[ - coverType].storUpp000005)) - - def set_value(self, long_var_name, src): - - if self.model is None or not hasattr(self.model.landSurface, 'satDegUpp000005'): - logger.info("cannot set value for %s, as model has not run yet.", long_var_name) - return - - logger.info("setting value for %s", long_var_name) - - # logger.info("dumping state to %s", self.configuration.endStateDir) - # self.model.dumpStateDir(self.configuration.endStateDir + "/pre/") - - # print "got value to set", src - - # make sure the raster is the right side up - src = np.flipud(src) - - # print "flipped", src - - # cast to pcraster precision - src = src.astype(np.float32) - - # print "as float 32", src - - sys.stdout.flush() - - logger.info("setting value shape %s", src.shape) - - if (long_var_name == "top_layer_soil_saturation"): - self.set_satDegUpp000005(src) - else: - raise Exception("unknown var name" + long_var_name) - - # write state here to facilitate restarting tomorrow - # logger.info("dumping state to %s", self.configuration.endStateDir) - # self.model.dumpStateDir(self.configuration.endStateDir + "/post/") - - def set_value_at_indices(self, long_var_name, inds, src): - raise NotImplementedError - - def get_grid_type(self, long_var_name): - return BmiGridType.UNIFORM - - def get_grid_shape(self, long_var_name): - return - - def get_grid_shape(self, long_var_name): - return self.shape - - def get_grid_spacing(self, long_var_name): - - cellsize = pcr.clone().cellSize() - - return np.array([cellsize, cellsize]) - - def get_grid_origin(self, long_var_name): - - north = pcr.clone().north() - cellSize = pcr.clone().cellSize() - nrRows = pcr.clone().nrRows() - - south = north - (cellSize * nrRows) - - west = pcr.clone().west() - - return np.array([south, west]) - - def get_grid_x(self, long_var_name): - raise ValueError - - def get_grid_y(self, long_var_name): - raise ValueError - - def get_grid_z(self, long_var_name): - raise ValueError - - def get_grid_connectivity(self, long_var_name): - raise ValueError - - def get_grid_offset(self, long_var_name): - raise ValueError - - #EBMI functions - - def set_start_time(self, start_time): - self.model_time.setStartTime(self.in_modeltime(start_time)) - - def set_end_time(self, end_time): - self.model_time.setEndTime(self.in_modeltime(end_time)) - - def get_attribute_names(self): - raise NotImplementedError - - def get_attribute_value(self, attribute_name): - raise NotImplementedError - - def set_attribute_value(self, attribute_name, attribute_value): - raise NotImplementedError - - def save_state(self, destination_directory): - logger.info("saving state to %s", destination_directory) - self.model.dumpStateDir(destination_directory) - - def load_state(self, source_directory): - raise NotImplementedError - - - -class ScaledBmiPCRGlobWB(BmiPCRGlobWB): - factor = 5 - - def set_value(self, long_var_name, scaled_new_value): - # small value for comparison - current_value = self.get_value(long_var_name) - - print('current value after scaling', current_value) - - print('value given by user', scaled_new_value) - - diff = scaled_new_value - current_value - - print("diff now", diff) - - # scale to model resolution - big_diff = np.repeat(np.repeat(diff, self.factor, axis=0), self.factor, axis=1) - - big_current_value = BmiPCRGlobWB.get_value(self, long_var_name) - - new_value = big_current_value + big_diff - - # new_value = np.repeat(np.repeat(src, self.factor, axis=0), self.factor, axis=1) - - BmiPCRGlobWB.set_value(self, long_var_name, new_value) - - def calculate_shape(self): - original = BmiPCRGlobWB.calculate_shape(self) - - logger.info("original shape !!! =" + str(original)) - - return np.array([original[0] // self.factor, original[1] // self.factor]) - - def get_value(self, long_var_name): - big_map = BmiPCRGlobWB.get_value(self, long_var_name) - - print("getting value original shape " + str(big_map.shape)) - print("original size " + str(big_map.size)) - print("nans in original " + str(np.count_nonzero(np.isnan(big_map)))) - - result = np.zeros(shape=self.get_grid_shape(long_var_name)) - - downsample(big_map, result) - - print("getting value new shape " + str(result.shape)) - print("result size " + str(result.size)) - print("nans count in result " + str(np.count_nonzero(np.isnan(result)))) - - print("getting value", result) - sys.stdout.flush() - - return result - - def get_grid_spacing(self, long_var_name): - cellsize = pcr.clone().cellSize() - - return np.array([cellsize * self.factor, cellsize * self.factor]) diff --git a/model/deterministic_runner_merging_ulysses.py b/model/deterministic_runner_merging_ulysses.py deleted file mode 100644 index 95305b32f..000000000 --- a/model/deterministic_runner_merging_ulysses.py +++ /dev/null @@ -1,402 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import sys -import datetime -import glob -import shutil - -import pcraster as pcr -from pcraster.framework import DynamicModel -from pcraster.framework import DynamicFramework - -from configuration_for_modflow import Configuration -from currTimeStep import ModelTime - -try: - from reporting_for_modflow import Reporting -except: - pass - -try: - from modflow import ModflowCoupling -except: - pass - -import virtualOS as vos - -import logging -logger = logging.getLogger(__name__) - -import disclaimer - -class DeterministicRunner(DynamicModel): - - def __init__(self, configuration, modelTime, system_argument = None): - DynamicModel.__init__(self) - - # model time object - self.modelTime = modelTime - - # make the configuration available for the other method/function - self.configuration = configuration - - # indicating whether this run includes modflow or merging processes - self.include_merging_or_modflow = True - # - For the standard PCR-GLOBWB 5arcmin runs, only the "Global" and "part_one" runs include modflow or merging processes - if "cloneAreas" in list(self.configuration.globalOptions.keys()) and\ - self.configuration.globalOptions['cloneAreas'] == "part_two": self.include_merging_or_modflow = False - - if self.include_merging_or_modflow: - - # netcdf merging options - self.netcdf_format = self.configuration.mergingOutputOptions['formatNetCDF'] - self.zlib_option = self.configuration.mergingOutputOptions['zlib'] - - # output files/variables that will be merged - nc_report_list = ["outDailyTotNC", - "outMonthTotNC", "outMonthAvgNC", "outMonthEndNC", "outMonthMaxNC", - "outAnnuaTotNC", "outAnnuaAvgNC", "outAnnuaEndNC", "outAnnuaMaxNC"] - for nc_report_type in nc_report_list: - vars(self)[nc_report_type] = self.configuration.mergingOutputOptions[nc_report_type] - - - # model and reporting objects, required for runs with modflow - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model = ModflowCoupling(configuration, modelTime) - self.reporting = Reporting(configuration, self.model, modelTime) - - - # somehow you need to set the clone map as the dynamic framework needs it (and the "self.model" is not always created) - pcr.setclone(self.configuration.cloneMap) - - - - def initial(self): - - # get or prepare the initial condition for groundwater head - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model.get_initial_heads() - - def dynamic(self): - - # re-calculate current model time using current pcraster timestep value - self.modelTime.update(self.currentTimeStep()) - - # update/calculate model and daily merging, and report ONLY at the last day of the month - if self.modelTime.isLastDayOfMonth(): - - # wait until all pcrglobwb model runs are done - pcrglobwb_is_ready = False - self.count_check = 0 - while pcrglobwb_is_ready == False: - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 49:\ - pcrglobwb_is_ready = self.check_pcrglobwb_status() - - # merging netcdf files at daily resolution - start_date = '%04i-%02i-01' %(self.modelTime.year, self.modelTime.month) # TODO: Make it flexible for a run starting not on the 1st January. - end_date = self.modelTime.fulldate - self.merging_netcdf_files("outDailyTotNC", start_date, end_date) - - # for runs with modflow - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - - # merging pcraster maps that are needed for MODFLOW calculation - msg = "Merging pcraster map files that are needed for the MODFLOW calculation." - logger.info(msg) - cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_pcraster_maps.py " + str(self.modelTime.fulldate) + " " +\ - str(self.configuration.main_output_directory)+"/ maps 8 "+\ - str("Global") - vos.cmd_line(cmd, using_subprocess = False) - - # cleaning up unmerged files (not tested yet) - clean_up_pcraster_maps = False - if self.configuration.mergingOutputOptions["delete_unmerged_pcraster_maps"] == "True": clean_up_pcraster_maps = True # TODO: FIXME: This is NOT working yet. - if clean_up_pcraster_maps: - files_to_be_removed = glob.glob(str(self.configuration.main_output_directory) + "/M*/maps/*" + str(self.modelTime.fulldate) + "*") - for f in files_to_be_removed: - print(f) - os.remove(f) - - # update MODFLOW model (It will pick up current model time from the modelTime object) - self.model.update() - # reporting is only done at the end of the month - self.reporting.report() - - - # merging initial conditions (pcraster maps) of PCR-GLOBWB - # ~ if self.modelTime.isLastDayOfYear(): - # - for Ulysses we have to do it on every month - if self.modelTime.isLastDayOfMonth(): - - msg = "Merging pcraster map files belonging to initial conditions." - logger.info(msg) - # ~ cmd = 'python '+ self.configuration.path_of_this_module + "/merge_pcraster_maps.py " + str(self.modelTime.fulldate) + " " +\ - # ~ str(self.configuration.main_output_directory)+"/ states 8 "+\ - # ~ str("Global") - # - for Ulysses: - # example: python3 merge_pcraster_maps_6_arcmin_ulysses.py ${END_DATE} ${MAIN_OUTPUT_DIR} states 2 Global 71 False - cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_pcraster_maps_6_arcmin_ulysses.py " + str(self.modelTime.fulldate) + " " +\ - str(self.configuration.main_output_directory)+"/ states 32 "+\ - str("Global 71 False") - # ~ vos.cmd_line(cmd, using_subprocess = False) - os.system(cmd) - - # cleaning up unmerged files (not tested yet) - clean_up_pcraster_maps = False - if "delete_unmerged_pcraster_maps" in list(self.configuration.mergingOutputOptions.keys()) and self.configuration.mergingOutputOptions["delete_unmerged_pcraster_maps"] == "True": clean_up_pcraster_maps = True # TODO: FIXME: This is NOT working yet. - if clean_up_pcraster_maps: - files_to_be_removed = glob.glob(str(self.configuration.main_output_directory) + "/M*/states/*" + str(self.modelTime.fulldate) + "*") - for f in files_to_be_removed: - print(f) - os.remove(f) - - - # monthly and annual merging - if self.modelTime.isLastDayOfYear(): - - # merging netcdf files at monthly resolutions - start_date = '%04i-01-31' %(self.modelTime.year) # TODO: Make it flexible for a run starting not on the 1st January. - self.merging_netcdf_files("outMonthTotNC", start_date, end_date) - self.merging_netcdf_files("outMonthAvgNC", start_date, end_date) - self.merging_netcdf_files("outMonthEndNC", start_date, end_date) - self.merging_netcdf_files("outMonthMaxNC", start_date, end_date) - - # merging netcdf files at annual resolutions - start_date = '%04i-12-31' %(self.modelTime.year) # TODO: Make it flexible for a run starting not on the 1st January. - end_date = self.modelTime.fulldate - self.merging_netcdf_files("outAnnuaTotNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaAvgNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaEndNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaMaxNC", start_date, end_date) - - - # make an empty file indicating that merging process is done - if self.modelTime.isLastDayOfMonth() or self.modelTime.isLastDayOfYear(): - - outputDirectory = str(self.configuration.main_output_directory) + "/global/maps/" - if os.path.exists(outputDirectory) == False: os.makedirs(outputDirectory) - filename = outputDirectory + "/merged_files_for_" + str(self.modelTime.fulldate)+"_are_ready.txt" - if os.path.exists(filename): os.remove(filename) - open(filename, "w").close() - - - def merging_netcdf_files(self, nc_report_type, start_date, end_date, max_number_of_cores = 20): - - if str(vars(self)[nc_report_type]) != "None": - - netcdf_files_that_will_be_merged = vars(self)[nc_report_type] - - msg = "Merging netcdf files for the files/variables: " + netcdf_files_that_will_be_merged - logger.info(msg) - - # ~ cmd = 'python '+ self.configuration.path_of_this_module + "/merge_netcdf.py " + str(self.configuration.main_output_directory) + " " +\ - # ~ str(self.configuration.main_output_directory) + "/global/netcdf/ "+\ - # ~ str(nc_report_type) + " " +\ - # ~ str(start_date) + " " +\ - # ~ str(end_date) + " " +\ - # ~ str(netcdf_files_that_will_be_merged) + " " +\ - # ~ str(self.netcdf_format) + " " +\ - # ~ str(self.zlib_option ) + " " +\ - # ~ str(max_number_of_cores) + " " +\ - # ~ str("Global") + " " - - # - for Ulysses: - # example: python3 merge_netcdf_6_arcmin_ulysses.py ${MAIN_OUTPUT_DIR} ${MAIN_OUTPUT_DIR}/global/netcdf outDailyTotNC ${STARTING_DATE} ${END_DATE} ulyssesQrRunoff,ulyssesDischarge NETCDF4 False 12 Global default_lats - cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_netcdf_6_arcmin_ulysses.py " + str(self.configuration.main_output_directory) + " " +\ - str(self.configuration.main_output_directory) + "/global/netcdf/ "+\ - str(nc_report_type) + " " +\ - str(start_date) + " " +\ - str(end_date) + " " +\ - str(netcdf_files_that_will_be_merged) + " " +\ - str(self.netcdf_format) + " " +\ - str(self.zlib_option ) + " " +\ - str(max_number_of_cores) + " " +\ - str("Global default_lats") + " " - - msg = "Using the following command line: " + cmd - logger.info(msg) - - # ~ vos.cmd_line(cmd, using_subprocess = False) - os.system(cmd) - - - def check_pcrglobwb_status(self): - - if self.configuration.globalOptions['cloneAreas'] == "Global" or \ - self.configuration.globalOptions['cloneAreas'] == "part_one": - clone_areas = ['M%02d'%i for i in range(1,53+1,1)] - - # for the Ulysses project - elif self.configuration.globalOptions['cloneAreas'] == "GlobalUlysses": - clone_areas = ['M%07d'%i for i in range(1,71+1,1)] - - else: - clone_areas = list(set(self.configuration.globalOptions['cloneAreas'].split(","))) - - - for clone_area in clone_areas: - status_file = str(self.configuration.main_output_directory) + "/" +str(clone_area) + "/maps/pcrglobwb_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: '+status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - - print(status) - - return status - -def modify_ini_file(original_ini_file, - system_argument): - - # created by Edwin H. Sutanudjaja on August 2020 for the Ulysses project - - # open and read ini file - file_ini = open(original_ini_file, "rt") - file_ini_content = file_ini.read() - file_ini.close() - - # system argument for replacing outputDir (-mod) ; this is always required - main_output_dir = system_argument[system_argument.index("-mod") + 1] - file_ini_content = file_ini_content.replace("MAIN_OUTPUT_DIR", main_output_dir) - msg = "The output folder 'outputDir' is set based on the system argument (-mod): " + main_output_dir - print(msg) - - # optional system arguments for modifying startTime (-sd) and endTime (-ed) - if "-sd" in system_argument: - starting_date = system_argument[system_argument.index("-sd") + 1] - file_ini_content = file_ini_content.replace("STARTING_DATE", starting_date) - msg = "The starting date 'startTime' is set based on the system argument (-sd): " + starting_date - print(msg) - if "-ed" in system_argument: - end_date = system_argument[system_argument.index("-ed") + 1] - file_ini_content = file_ini_content.replace("END_DATE", end_date) - msg = "The end date 'END_DATE' is set based on the system argument (-ed): " + end_date - print(msg) - - # optional system arguments for initial condition files - # - main initial state folder - if "-misd" in system_argument: - main_initial_state_folder = system_argument[system_argument.index("-misd") + 1] - file_ini_content = file_ini_content.replace("MAIN_INITIAL_STATE_FOLDER", main_initial_state_folder) - msg = "The main folder for all initial states is set based on the system argument (-misd): " + main_initial_state_folder - print(msg) - # - date for initial states - if "-dfis" in system_argument: - date_for_initial_states = system_argument[system_argument.index("-dfis") + 1] - file_ini_content = file_ini_content.replace("DATE_FOR_INITIAL_STATES", date_for_initial_states) - msg = "The date for all initial state files is set based on the system argument (-dfis): " + date_for_initial_states - print(msg) - - # optional system argument for modifying forcing files - if "-pff" in system_argument: - precipitation_forcing_file = system_argument[system_argument.index("-pff") + 1] - file_ini_content = file_ini_content.replace("PRECIPITATION_FORCING_FILE", precipitation_forcing_file) - msg = "The precipitation forcing file 'precipitationNC' is set based on the system argument (-pff): " + precipitation_forcing_file - print(msg) - if "-tff" in system_argument: - temperature_forcing_file = system_argument[system_argument.index("-tff") + 1] - file_ini_content = file_ini_content.replace("TEMPERATURE_FORCING_FILE", temperature_forcing_file) - msg = "The temperature forcing file 'temperatureNC' is set based on the system argument (-tff): " + temperature_forcing_file - print(msg) - if "-rpetff" in system_argument: - ref_pot_et_forcing_file = system_argument[system_argument.index("-rpetff") + 1] - file_ini_content = file_ini_content.replace("REF_POT_ET_FORCING_FILE", ref_pot_et_forcing_file) - msg = "The reference potential ET forcing file 'refETPotFileNC' is set based on the system argument (-tff): " + ref_pot_et_forcing_file - print(msg) - - # folder for saving original and modified ini files - folder_for_ini_files = os.path.join(main_output_dir, "ini_files") - - # create folder - if os.path.exists(folder_for_ini_files): shutil.rmtree(folder_for_ini_files) - os.makedirs(folder_for_ini_files) - - # save/copy the original ini file - shutil.copy(original_ini_file, os.path.join(folder_for_ini_files, os.path.basename(original_ini_file) + ".original")) - - # save the new ini file - new_ini_file_name = os.path.join(folder_for_ini_files, os.path.basename(original_ini_file) + ".modified_and_used") - new_ini_file = open(new_ini_file_name, "w") - new_ini_file.write(file_ini_content) - new_ini_file.close() - - return new_ini_file_name - - -def main(): - - # print disclaimer - disclaimer.print_disclaimer() - - # get the full path of configuration/ini file given in the system argument - iniFileName = os.path.abspath(sys.argv[1]) - - # modify ini file and return it in a new location - if "-mod" in sys.argv: - iniFileName = modify_ini_file(original_ini_file = iniFileName, \ - system_argument = sys.argv) - - # debug option - debug_mode = False - if len(sys.argv) > 2: - if sys.argv[2] == "debug" or sys.argv[2] == "debug_parallel": debug_mode = True - - # options to perform steady state calculation - steady_state_only = False - if len(sys.argv) > 3: - if sys.argv[3] == "steady-state-only": steady_state_only = True - - # object to handle configuration/ini file - configuration = Configuration(iniFileName = iniFileName, \ - debug_mode = debug_mode, \ - steady_state_only = steady_state_only) - - # timeStep info: year, month, day, doy, hour, etc - currTimeStep = ModelTime() - - # Running the deterministic_runner - currTimeStep.getStartEndTimeSteps(configuration.globalOptions['startTime'], - configuration.globalOptions['endTime']) - logger.info('Model run starts.') - deterministic_runner = DeterministicRunner(configuration, currTimeStep) - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - -if __name__ == '__main__': - # print disclaimer - disclaimer.print_disclaimer(with_logger = True) - sys.exit(main()) - - diff --git a/model/deterministic_runner_merging_with_arguments.py b/model/deterministic_runner_merging_with_arguments.py deleted file mode 100644 index bf0833e62..000000000 --- a/model/deterministic_runner_merging_with_arguments.py +++ /dev/null @@ -1,457 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import sys -import datetime -import glob -import shutil - -import pcraster as pcr -from pcraster.framework import DynamicModel -from pcraster.framework import DynamicFramework - -from configuration_for_modflow import Configuration -from currTimeStep import ModelTime - -try: - from reporting_for_modflow import Reporting -except: - pass - -try: - from modflow import ModflowCoupling -except: - pass - -import virtualOS as vos - -import logging -logger = logging.getLogger(__name__) - -import disclaimer - -class DeterministicRunner(DynamicModel): - - def __init__(self, configuration, modelTime, system_argument = None): - DynamicModel.__init__(self) - - self.number_of_clones = configuration.globalMergingAndModflowOptions['number_of_clones'] - self.cellsize_in_arcsec = configuration.globalMergingAndModflowOptions['cellsize_in_arcsec'] - self.xmin = configuration.globalMergingAndModflowOptions['xmin'] - self.ymin = configuration.globalMergingAndModflowOptions['ymin'] - self.xmax = configuration.globalMergingAndModflowOptions['xmax'] - self.ymax = configuration.globalMergingAndModflowOptions['ymax'] - - # model time object - self.modelTime = modelTime - - # make the configuration available for the other method/function - self.configuration = configuration - - # indicating whether this run includes modflow or merging processes - self.include_merging_or_modflow = True - # - For the standard PCR-GLOBWB 5arcmin runs, only the "Global" and "part_one" runs include modflow or merging processes - if "cloneAreas" in list(self.configuration.globalOptions.keys()) and\ - self.configuration.globalOptions['cloneAreas'] == "part_two": self.include_merging_or_modflow = False - - if self.include_merging_or_modflow: - - # netcdf merging options - self.netcdf_format = self.configuration.mergingOutputOptions['formatNetCDF'] - self.zlib_option = self.configuration.mergingOutputOptions['zlib'] - - # output files/variables that will be merged - nc_report_list = ["outDailyTotNC", - "outMonthTotNC", "outMonthAvgNC", "outMonthEndNC", "outMonthMaxNC", - "outAnnuaTotNC", "outAnnuaAvgNC", "outAnnuaEndNC", "outAnnuaMaxNC"] - for nc_report_type in nc_report_list: - vars(self)[nc_report_type] = self.configuration.mergingOutputOptions[nc_report_type] - - - # model and reporting objects, required for runs with modflow - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model = ModflowCoupling(configuration, modelTime) - self.reporting = Reporting(configuration, self.model, modelTime) - - - # somehow you need to set the clone map as the dynamic framework needs it (and the "self.model" is not always created) - pcr.setclone(self.configuration.cloneMap) - - - - def initial(self): - - # get or prepare the initial condition for groundwater head - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model.get_initial_heads() - - def dynamic(self): - - # re-calculate current model time using current pcraster timestep value - self.modelTime.update(self.currentTimeStep()) - - # update/calculate model and daily merging, and report ONLY at the last day of the month - if self.modelTime.isLastDayOfMonth(): - - # wait until all pcrglobwb model runs are done - pcrglobwb_is_ready = False - self.count_check = 0 - while pcrglobwb_is_ready == False: - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 49:\ - pcrglobwb_is_ready = self.check_pcrglobwb_status() - - # merging netcdf files at daily resolution - start_date = '%04i-%02i-01' %(self.modelTime.year, self.modelTime.month) # TODO: Make it flexible for a run starting not on the 1st January. - end_date = self.modelTime.fulldate - self.merging_netcdf_files("outDailyTotNC", start_date, end_date) - - # for runs with modflow - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - - # merging pcraster maps that are needed for MODFLOW calculation - msg = "Merging pcraster map files that are needed for the MODFLOW calculation." - logger.info(msg) - cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_pcraster_maps.py " + str(self.modelTime.fulldate) + " " +\ - str(self.configuration.main_output_directory)+"/ maps 8 "+\ - str("Global") - vos.cmd_line(cmd, using_subprocess = False) - - # cleaning up unmerged files (not tested yet) - clean_up_pcraster_maps = False - if self.configuration.mergingOutputOptions["delete_unmerged_pcraster_maps"] == "True": clean_up_pcraster_maps = True # TODO: FIXME: This is NOT working yet. - if clean_up_pcraster_maps: - files_to_be_removed = glob.glob(str(self.configuration.main_output_directory) + "/M*/maps/*" + str(self.modelTime.fulldate) + "*") - for f in files_to_be_removed: - print(f) - os.remove(f) - - # update MODFLOW model (It will pick up current model time from the modelTime object) - self.model.update() - # reporting is only done at the end of the month - self.reporting.report() - - - # merging initial conditions (pcraster maps) of PCR-GLOBWB - if self.modelTime.isLastDayOfYear(): - - # ~ # - for Ulysses we have to do it on every month - # ~ if self.modelTime.isLastDayOfMonth(): - - msg = "Merging pcraster map files belonging to initial conditions." - logger.info(msg) - - # ~ # - for a global extent - # ~ cmd = 'python '+ self.configuration.path_of_this_module + "/merge_pcraster_maps.py " + str(self.modelTime.fulldate) + " " +\ - # ~ str(self.configuration.main_output_directory)+"/ states 8 "+\ - # ~ str("Global") - # ~ # - for Ulysses: - # ~ # example: python3 merge_pcraster_maps_6_arcmin_ulysses.py ${END_DATE} ${MAIN_OUTPUT_DIR} states 2 Global 71 False - # ~ cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_pcraster_maps_6_arcmin_ulysses.py " + str(self.modelTime.fulldate) + " " +\ - # ~ str(self.configuration.main_output_directory)+"/ states 32 "+\ - # ~ str("Global 71 False") - - # - for general (e.g. africa extent, europe, etc) - cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_pcraster_maps_general.py " + str(self.modelTime.fulldate) + " " +\ - str(self.configuration.main_output_directory)+"/ states 32 "+\ - str(self.number_of_clones ) + " " +\ - str("defined") + " " +\ - str(self.cellsize_in_arcsec) + " " +\ - str(self.xmin ) + " " +\ - str(self.ymin ) + " " +\ - str(self.xmax ) + " " +\ - str(self.ymax ) + " " - - print(cmd) - - # ~ pietje - - # ~ vos.cmd_line(cmd, using_subprocess = False) - os.system(cmd) - - - # cleaning up unmerged files (not tested yet) - clean_up_pcraster_maps = False - if "delete_unmerged_pcraster_maps" in list(self.configuration.mergingOutputOptions.keys()) and self.configuration.mergingOutputOptions["delete_unmerged_pcraster_maps"] == "True": clean_up_pcraster_maps = True # TODO: FIXME: This is NOT working yet. - if clean_up_pcraster_maps: - files_to_be_removed = glob.glob(str(self.configuration.main_output_directory) + "/M*/states/*" + str(self.modelTime.fulldate) + "*") - for f in files_to_be_removed: - print(f) - os.remove(f) - - - # monthly and annual merging - if self.modelTime.isLastDayOfYear(): - - # merging netcdf files at monthly resolutions - start_date = '%04i-01-31' %(self.modelTime.year) # TODO: Make it flexible for a run starting not on the 1st January. - self.merging_netcdf_files("outMonthTotNC", start_date, end_date) - self.merging_netcdf_files("outMonthAvgNC", start_date, end_date) - self.merging_netcdf_files("outMonthEndNC", start_date, end_date) - self.merging_netcdf_files("outMonthMaxNC", start_date, end_date) - - # merging netcdf files at annual resolutions - start_date = '%04i-12-31' %(self.modelTime.year) # TODO: Make it flexible for a run starting not on the 1st January. - end_date = self.modelTime.fulldate - self.merging_netcdf_files("outAnnuaTotNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaAvgNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaEndNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaMaxNC", start_date, end_date) - - - # make an empty file indicating that merging process is done - if self.modelTime.isLastDayOfMonth() or self.modelTime.isLastDayOfYear(): - - outputDirectory = str(self.configuration.main_output_directory) + "/global/maps/" - if os.path.exists(outputDirectory) == False: os.makedirs(outputDirectory) - filename = outputDirectory + "/merged_files_for_" + str(self.modelTime.fulldate)+"_are_ready.txt" - if os.path.exists(filename): os.remove(filename) - open(filename, "w").close() - - - def merging_netcdf_files(self, nc_report_type, start_date, end_date, max_number_of_cores = 20): - - if str(vars(self)[nc_report_type]) != "None": - - netcdf_files_that_will_be_merged = vars(self)[nc_report_type] - - msg = "Merging netcdf files for the files/variables: " + netcdf_files_that_will_be_merged - logger.info(msg) - - # ~ cmd = 'python '+ self.configuration.path_of_this_module + "/merge_netcdf.py " + str(self.configuration.main_output_directory) + " " +\ - # ~ str(self.configuration.main_output_directory) + "/global/netcdf/ "+\ - # ~ str(nc_report_type) + " " +\ - # ~ str(start_date) + " " +\ - # ~ str(end_date) + " " +\ - # ~ str(netcdf_files_that_will_be_merged) + " " +\ - # ~ str(self.netcdf_format) + " " +\ - # ~ str(self.zlib_option ) + " " +\ - # ~ str(max_number_of_cores) + " " +\ - # ~ str("Global") + " " - - # ~ # - for Ulysses: - # ~ # example: python3 merge_netcdf_6_arcmin_ulysses.py ${MAIN_OUTPUT_DIR} ${MAIN_OUTPUT_DIR}/global/netcdf outDailyTotNC ${STARTING_DATE} ${END_DATE} ulyssesQrRunoff,ulyssesDischarge NETCDF4 False 12 Global default_lats - # ~ cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_netcdf_6_arcmin_ulysses.py " + str(self.configuration.main_output_directory) + " " +\ - # ~ str(self.configuration.main_output_directory) + "/global/netcdf/ "+\ - # ~ str(nc_report_type) + " " +\ - # ~ str(start_date) + " " +\ - # ~ str(end_date) + " " +\ - # ~ str(netcdf_files_that_will_be_merged) + " " +\ - # ~ str(self.netcdf_format) + " " +\ - # ~ str(self.zlib_option ) + " " +\ - # ~ str(max_number_of_cores) + " " +\ - # ~ str("Global default_lats") + " " - - # - for general: - cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_netcdf_general.py " + str(self.configuration.main_output_directory) + " " +\ - str(self.configuration.main_output_directory) + "/global/netcdf/ "+\ - str(nc_report_type) + " " +\ - str(start_date) + " " +\ - str(end_date) + " " +\ - str(netcdf_files_that_will_be_merged) + " " +\ - str(self.netcdf_format) + " " +\ - str(self.zlib_option ) + " " +\ - str(max_number_of_cores) + " " +\ - str(self.number_of_clones ) + " " +\ - str("defined") + " " +\ - str(self.cellsize_in_arcsec) + " " +\ - str(self.xmin ) + " " +\ - str(self.ymin ) + " " +\ - str(self.xmax ) + " " +\ - str(self.ymax ) + " " - - msg = "Using the following command line: " + cmd - logger.info(msg) - - # ~ vos.cmd_line(cmd, using_subprocess = False) - os.system(cmd) - - - def check_pcrglobwb_status(self): - - # ~ if self.configuration.globalOptions['cloneAreas'] == "Global" or \ - # ~ self.configuration.globalOptions['cloneAreas'] == "part_one": - # ~ clone_areas = ['M%02d'%i for i in range(1,53+1,1)] - - # ~ # for the Ulysses project - # ~ elif self.configuration.globalOptions['cloneAreas'] == "GlobalUlysses": - # ~ clone_areas = ['M%07d'%i for i in range(1,71+1,1)] - - # ~ else: - # ~ clone_areas = list(set(self.configuration.globalOptions['cloneAreas'].split(","))) - - clone_areas = ['M%07d'%i for i in range(1, int(self.number_of_clones) + 1, 1)] - - for clone_area in clone_areas: - status_file = str(self.configuration.main_output_directory) + "/" +str(clone_area) + "/maps/pcrglobwb_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: '+status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - - # ~ # for debugging - # ~ status = True - - if status == False: return status - if status: self.count_check = 0 - - print(status) - - return status - -def modify_ini_file(original_ini_file, - system_argument): - - # created by Edwin H. Sutanudjaja on August 2020 for the Ulysses project - - # open and read ini file - file_ini = open(original_ini_file, "rt") - file_ini_content = file_ini.read() - file_ini.close() - - # system argument for replacing outputDir (-mod) ; this is always required - main_output_dir = system_argument[system_argument.index("-mod") + 1] - file_ini_content = file_ini_content.replace("MAIN_OUTPUT_DIR", main_output_dir) - msg = "The output folder 'outputDir' is set based on the system argument (-mod): " + main_output_dir - print(msg) - - # optional system arguments for modifying startTime (-sd) and endTime (-ed) - if "-sd" in system_argument: - starting_date = system_argument[system_argument.index("-sd") + 1] - file_ini_content = file_ini_content.replace("STARTING_DATE", starting_date) - msg = "The starting date 'startTime' is set based on the system argument (-sd): " + starting_date - print(msg) - if "-ed" in system_argument: - end_date = system_argument[system_argument.index("-ed") + 1] - file_ini_content = file_ini_content.replace("END_DATE", end_date) - msg = "The end date 'END_DATE' is set based on the system argument (-ed): " + end_date - print(msg) - - # optional system arguments for initial condition files - # - main initial state folder - if "-misd" in system_argument: - main_initial_state_folder = system_argument[system_argument.index("-misd") + 1] - file_ini_content = file_ini_content.replace("MAIN_INITIAL_STATE_FOLDER", main_initial_state_folder) - msg = "The main folder for all initial states is set based on the system argument (-misd): " + main_initial_state_folder - print(msg) - # - date for initial states - if "-dfis" in system_argument: - date_for_initial_states = system_argument[system_argument.index("-dfis") + 1] - file_ini_content = file_ini_content.replace("DATE_FOR_INITIAL_STATES", date_for_initial_states) - msg = "The date for all initial state files is set based on the system argument (-dfis): " + date_for_initial_states - print(msg) - - # optional system argument for modifying forcing files - if "-pff" in system_argument: - precipitation_forcing_file = system_argument[system_argument.index("-pff") + 1] - file_ini_content = file_ini_content.replace("PRECIPITATION_FORCING_FILE", precipitation_forcing_file) - msg = "The precipitation forcing file 'precipitationNC' is set based on the system argument (-pff): " + precipitation_forcing_file - print(msg) - if "-tff" in system_argument: - temperature_forcing_file = system_argument[system_argument.index("-tff") + 1] - file_ini_content = file_ini_content.replace("TEMPERATURE_FORCING_FILE", temperature_forcing_file) - msg = "The temperature forcing file 'temperatureNC' is set based on the system argument (-tff): " + temperature_forcing_file - print(msg) - if "-rpetff" in system_argument: - ref_pot_et_forcing_file = system_argument[system_argument.index("-rpetff") + 1] - file_ini_content = file_ini_content.replace("REF_POT_ET_FORCING_FILE", ref_pot_et_forcing_file) - msg = "The reference potential ET forcing file 'refETPotFileNC' is set based on the system argument (-tff): " + ref_pot_et_forcing_file - print(msg) - - # NUMBER_OF_SPINUP_YEARS - if "-num_of_sp_years" in system_argument: - number_of_spinup_years = system_argument[system_argument.index("-num_of_sp_years") + 1] - file_ini_content = file_ini_content.replace("NUMBER_OF_SPINUP_YEARS", number_of_spinup_years) - msg = "The number_of_spinup_years is set based on the system argument (-num_of_sp_years): " + number_of_spinup_years - print(msg) - - # folder for saving original and modified ini files - folder_for_ini_files = os.path.join(main_output_dir, "ini_files") - - # create folder - if os.path.exists(folder_for_ini_files): shutil.rmtree(folder_for_ini_files) - os.makedirs(folder_for_ini_files) - - # save/copy the original ini file - shutil.copy(original_ini_file, os.path.join(folder_for_ini_files, os.path.basename(original_ini_file) + ".original")) - - # save the new ini file - new_ini_file_name = os.path.join(folder_for_ini_files, os.path.basename(original_ini_file) + ".modified_and_used") - new_ini_file = open(new_ini_file_name, "w") - new_ini_file.write(file_ini_content) - new_ini_file.close() - - return new_ini_file_name - - -def main(): - - # print disclaimer - disclaimer.print_disclaimer() - - # get the full path of configuration/ini file given in the system argument - iniFileName = os.path.abspath(sys.argv[1]) - - # modify ini file and return it in a new location - if "-mod" in sys.argv: - iniFileName = modify_ini_file(original_ini_file = iniFileName, \ - system_argument = sys.argv) - - # debug option - debug_mode = False - if len(sys.argv) > 2: - if sys.argv[2] == "debug" or sys.argv[2] == "debug_parallel": debug_mode = True - - # options to perform steady state calculation (for modflow) - steady_state_only = False - if len(sys.argv) > 3: - if sys.argv[3] == "steady-state-only": steady_state_only = True - - # object to handle configuration/ini file - configuration = Configuration(iniFileName = iniFileName, \ - debug_mode = debug_mode, \ - steady_state_only = steady_state_only) - - # timeStep info: year, month, day, doy, hour, etc - currTimeStep = ModelTime() - - # Running the deterministic_runner - currTimeStep.getStartEndTimeSteps(configuration.globalOptions['startTime'], - configuration.globalOptions['endTime']) - logger.info('Model run starts.') - deterministic_runner = DeterministicRunner(configuration, currTimeStep) - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - -if __name__ == '__main__': - # print disclaimer - disclaimer.print_disclaimer(with_logger = True) - sys.exit(main()) diff --git a/model/deterministic_runner_parallel_for_ulysses.py b/model/deterministic_runner_parallel_for_ulysses.py deleted file mode 100755 index 63c2a33a7..000000000 --- a/model/deterministic_runner_parallel_for_ulysses.py +++ /dev/null @@ -1,558 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Ludovicus P. H. (Rens) van Beek, Edwin H. Sutanudjaja, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Inge E. M. de Graaf, Kor de Jong, Patricia Lopez Lopez, -# Stefanie Pessenteiner, Oliver Schmitz, Menno W. Straatsma, Niko Wanders, Dominik Wisser, -# and Marc F. P. Bierkens, -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import sys -import shutil -import datetime - -import pcraster as pcr -from pcraster.framework import DynamicModel -from pcraster.framework import DynamicFramework - -from configuration import Configuration -from currTimeStep import ModelTime -from reporting import Reporting -from spinUp import SpinUp - -from pcrglobwb import PCRGlobWB - -import logging -logger = logging.getLogger(__name__) - -import disclaimer - -class DeterministicRunner(DynamicModel): - - def __init__(self, configuration, modelTime, initialState = None, system_argument = None, spinUpRun = False): - DynamicModel.__init__(self) - - self.modelTime = modelTime - self.model = PCRGlobWB(configuration, modelTime, initialState, spinUpRun) - self.reporting = Reporting(configuration, self.model, modelTime) - - # the model paramaters may be modified - self.parameter_adjusment = False - if (("-adjparm" in list(system_argument)) or ('prefactorOptions' in configuration.allSections)): - self.adusting_parameters(configuration, system_argument) - self.parameter_adjusment = True - - # option to include merging processes for pcraster maps and netcdf files: - # - default option - self.with_merging = False - # - set based on ini file - if ('with_merging' in configuration.globalOptions.keys()) and (configuration.globalOptions['with_merging'] == "False"): - self.with_merging = False - if ('with_merging' in configuration.globalOptions.keys()) and (configuration.globalOptions['with_merging'] == "True"): - self.with_merging = True - - # for a run with spinUp, we have to disactivate merging - if spinUpRun == True: self.with_merging = False - - # make the configuration available for the other method/function - self.configuration = configuration - - def adusting_parameters(self, configuration, system_argument): - - # global pre-multipliers given in the argument: - if "-adjparm" in list(system_argument): - - # starting adjustment index (sai) - sai = system_argument.index("-adjparm") - - logger.info("Adjusting some model parameters based on given values in the system argument.") - - # pre-multipliers for minSoilDepthFrac, kSat, recessionCoeff, storCap and degreeDayFactor - multiplier_for_minSoilDepthFrac = float(system_argument[sai + 1]) # linear scale - multiplier_for_kSat = float(system_argument[sai + 2]) # log scale - multiplier_for_recessionCoeff = float(system_argument[sai + 3]) # log scale - multiplier_for_storCap = float(system_argument[sai + 4]) # linear scale - multiplier_for_degreeDayFactor = float(system_argument[sai + 5]) # linear scale - - # pre-multiplier for the reference potential ET - self.multiplier_for_refPotET = float(system_argument[sai + 6]) # linear scale - - # pre-multiplier for manningsN - multiplier_for_manningsN = float(system_argument[sai + 7]) # linear scale - - # modification for storGroundwaterIni - storGroundwaterIni_file = str(system_argument[sai + 8]) - - # it is also possible to define prefactors via the ini/configuration file: - # - this will be overwrite any previous given pre-multipliers - if 'prefactorOptions' in configuration.allSections: - - logger.info("Adjusting some model parameters based on given values in the ini/configuration file.") - - self.multiplier_for_refPotET = float(configuration.prefactorOptions['linear_multiplier_for_refPotET' ]) # linear scale # Note that this one does NOT work for the changing WMIN or Joyce land cover options. - multiplier_for_degreeDayFactor = float(configuration.prefactorOptions['linear_multiplier_for_degreeDayFactor' ]) # linear scale - multiplier_for_minSoilDepthFrac = float(configuration.prefactorOptions['linear_multiplier_for_minSoilDepthFrac']) # linear scale - multiplier_for_kSat = float(configuration.prefactorOptions['log_10_multiplier_for_kSat' ]) # log scale - multiplier_for_storCap = float(configuration.prefactorOptions['linear_multiplier_for_storCap' ]) # linear scale - multiplier_for_recessionCoeff = float(configuration.prefactorOptions['log_10_multiplier_for_recessionCoeff' ]) # log scale - multiplier_for_manningsN = float(configuration.prefactorOptions['multiplier_for_manningsN' ]) # linear scale - - storGroundwaterIni_file = str(configuration.prefactorOptions['storGroundwaterIni_file']) # file location (please use full path) - - # saving global pre-multipliers to the log file: - msg = "\n" - msg += "\n" - msg += "Multiplier values used: "+"\n" - msg += "For minSoilDepthFrac : "+str(multiplier_for_minSoilDepthFrac)+"\n" - msg += "For kSat (log-scale) : "+str(multiplier_for_kSat )+"\n" - msg += "For recessionCoeff (log-scale) : "+str(multiplier_for_recessionCoeff )+"\n" - msg += "For storCap : "+str(multiplier_for_storCap )+"\n" - msg += "For degreeDayFactor : "+str(multiplier_for_degreeDayFactor )+"\n" - msg += "For refPotET : "+str(self.multiplier_for_refPotET )+"\n" - msg += "For multiplier_for_manningsN : "+str(multiplier_for_manningsN )+"\n" - msg += "For storGroundwaterIni_file : "+str(storGroundwaterIni_file )+"\n" - logger.info(msg) - # - also to a txt file - f = open("multiplier.txt", "w") # this will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - f.write(msg) - f.close() - - # adjust storGroundwaterIni - if storGroundwaterIni_file != "Default": - #~ self.model.groundwater.storGroundwater = pcr.readmap(storGroundwaterIni_file) - self.model.groundwater.storGroundwater = vos.readPCRmapClone(storGroundwaterIni_file,\ - configuration.cloneMap,\ - configuration.tmpDir) - self.model.groundwater.storGroundwater = pcr.ifthen(self.landmask, \ - pcr.cover(self.model.groundwater.storGroundwater, 0.0)) - pcr.report(self.model.groundwater.storGroundwater, "storGroundwaterIni.map") - - # set parameter "manningsN" based on the given pre-multiplier - # - also saving the adjusted parameter maps to pcraster files - # - these will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - # "manningsN" - # minimum value is zero and using log-scale - self.model.routing.manningsN = multiplier_for_manningsN * self.model.routing.manningsN - # report the map - pcr.report(self.model.routing.manningsN, "manningsN.map") - - # set parameter "recessionCoeff" based on the given pre-multiplier - # - also saving the adjusted parameter maps to pcraster files - # - these will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - # "recessionCoeff" - # minimum value is zero and using log-scale - self.model.groundwater.recessionCoeff = pcr.max(0.0, (10**(multiplier_for_recessionCoeff)) * self.model.groundwater.recessionCoeff) - self.model.groundwater.recessionCoeff = pcr.min(1.0, self.model.groundwater.recessionCoeff) - # report the map - pcr.report(self.model.groundwater.recessionCoeff, "recessionCoeff.map") - - # set parameters "kSat", "storCap", "minSoilDepthFrac", and "degreeDayFactor" based on the given pre-multipliers - for coverType in self.model.landSurface.coverTypes: - - # "degreeDayFactor" - self.model.landSurface.landCoverObj[coverType].degreeDayFactor = pcr.max(0.0, multiplier_for_degreeDayFactor *\ - self.model.landSurface.landCoverObj[coverType].degreeDayFactor) - # report the map - pcraster_filename = "degreeDayFactor" + "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].degreeDayFactor , pcraster_filename) - - # "kSat" and "storCap" for 2 layer model - if self.model.landSurface.numberOfSoilLayers == 2: - - # "kSat" - # minimum value is zero and using-log-scale - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp) - self.model.landSurface.landCoverObj[coverType].parameters.kSatLow = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatLow) - # report the maps - pcraster_filename = "kSatUpp"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp, pcraster_filename) - pcraster_filename = "kSatLow"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatLow, pcraster_filename) - - # "storCap" - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp) - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow) - # report the maps - pcraster_filename = "storCapUpp"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp, pcraster_filename) - pcraster_filename = "storCapLow"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapLow, pcraster_filename) - - # "kSat" and "storCap" for 3 layer model - if self.model.landSurface.numberOfSoilLayers == 3: - - # "kSat" - # minimum value is zero and using-log-scale - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005) - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030) - self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150) - # report the maps - pcraster_filename = "kSatUpp000005"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005, pcraster_filename) - pcraster_filename = "kSatUpp005030"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030, pcraster_filename) - pcraster_filename = "kSatLow030150"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150, pcraster_filename) - - # "storCap" - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005) - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030) - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150) - # report the maps - pcraster_filename = "storCapUpp000005"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005, pcraster_filename) - pcraster_filename = "storCapUpp005030"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030, pcraster_filename) - pcraster_filename = "storCapLow030150"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150, pcraster_filename) - - - # re-calculate rootZoneWaterStorageCap as the consequence of the modification of "storCap" - # This is WMAX in the oldcalc script. - if self.model.landSurface.numberOfSoilLayers == 2: - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap = self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow - if self.model.landSurface.numberOfSoilLayers == 3: - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap = self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005 +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030 +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150 - # report the map - pcraster_filename = "rootZoneWaterStorageCap"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap, pcraster_filename) - - # "minSoilDepthFrac" - if multiplier_for_minSoilDepthFrac != 1.0: - - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.max(0.0, multiplier_for_minSoilDepthFrac*\ - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac) - # for minSoilDepthFrac - values will be limited by maxSoilDepthFrac - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.min(\ - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac,\ - self.model.landSurface.landCoverObj[coverType].maxSoilDepthFrac) - # maximum value is 1.0 - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.min(1.0, self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac) - # report the map - pcraster_filename = "minSoilDepthFrac"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac, pcraster_filename) - - # re-calculate arnoBeta (as the consequence of the modification of minSoilDepthFrac) - self.model.landSurface.landCoverObj[coverType].arnoBeta = pcr.max(0.001,\ - (self.model.landSurface.landCoverObj[coverType].maxSoilDepthFrac-1.)/(1.-self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac)+\ - self.model.landSurface.landCoverObj[coverType].parameters.orographyBeta-0.01) - self.model.landSurface.landCoverObj[coverType].arnoBeta = pcr.cover(pcr.max(0.001,\ - self.model.landSurface.landCoverObj[coverType].arnoBeta), 0.001) - # report the map - pcraster_filename = "arnoBeta"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].arnoBeta, pcraster_filename) - - # re-calculate rootZoneWaterStorageMin (as the consequence of the modification of minSoilDepthFrac) - # This is WMIN in the oldcalc script. - # WMIN (unit: m): minimum local soil water capacity within the grid-cell - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin = self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac *\ - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap - # report the map - pcraster_filename = "rootZoneWaterStorageMin"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin, pcraster_filename) - - # re-calculate rootZoneWaterStorageRange (as the consequence of the modification of rootZoneWaterStorageRange and minSoilDepthFrac) - # WMAX - WMIN (unit: m) - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageRange = self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap -\ - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin - # report the map - pcraster_filename = "rootZoneWaterStorageRange"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageRange, pcraster_filename) - - def initial(self): - pass - - def dynamic(self): - - # re-calculate current model time using current pcraster timestep value - self.modelTime.update(self.currentTimeStep()) - - # read model forcing (will pick up current model time from model time object) - self.model.read_forcings() - - # adjust the reference potential ET according to the given pre-multiplier - if self.parameter_adjusment: - self.model.meteo.referencePotET = self.model.meteo.referencePotET * self.multiplier_for_refPotET - - # update model (will pick up current model time from model time object) - # - for a run coupled to MODFLOW, water balance checks are not valid due to lateral flow. - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model.update(report_water_balance = False) - else: - self.model.update(report_water_balance = True) - - # do any needed reporting for this time step - self.reporting.report() - - # at the last day of the month, stop calculation until modflow and related merging process are ready (only for a run with modflow) - if self.modelTime.isLastDayOfMonth() and (self.configuration.online_coupling_between_pcrglobwb_and_modflow or\ - self.with_merging): - - # wait until modflow files are ready - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - modflow_is_ready = False - self.count_check = 0 - while modflow_is_ready == False: - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 59:\ - modflow_is_ready = self.check_modflow_status() - - # wait until merged files are ready - merged_files_are_ready = False - while merged_files_are_ready == False: - self.count_check = 0 - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 59:\ - merged_files_are_ready = self.check_merging_status() - - def check_modflow_status(self): - - status_file = str(self.configuration.main_output_directory) + "/modflow/transient/maps/modflow_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: ' + status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - return status - - def check_merging_status(self): - - status_file = str(self.configuration.main_output_directory) + "/global/maps/merged_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: ' + status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - return status - - -def modify_ini_file(original_ini_file, - system_argument): - - # created by Edwin H. Sutanudjaja on August 2020 for the Ulysses project - - # open and read ini file - file_ini = open(original_ini_file, "rt") - file_ini_content = file_ini.read() - file_ini.close() - - # system argument for replacing outputDir (-mod) ; this is always required - main_output_dir = system_argument[system_argument.index("-mod") + 1] - file_ini_content = file_ini_content.replace("MAIN_OUTPUT_DIR", main_output_dir) - msg = "The output folder 'outputDir' is set based on the system argument (-mod): " + main_output_dir - print(msg) - - # optional system arguments for modifying startTime (-sd) and endTime (-ed) - if "-sd" in system_argument: - starting_date = system_argument[system_argument.index("-sd") + 1] - file_ini_content = file_ini_content.replace("STARTING_DATE", starting_date) - msg = "The starting date 'startTime' is set based on the system argument (-sd): " + starting_date - print(msg) - if "-ed" in system_argument: - end_date = system_argument[system_argument.index("-ed") + 1] - file_ini_content = file_ini_content.replace("END_DATE", end_date) - msg = "The end date 'END_DATE' is set based on the system argument (-ed): " + end_date - print(msg) - - # optional system arguments for initial condition files - # - main initial state folder - if "-misd" in system_argument: - main_initial_state_folder = system_argument[system_argument.index("-misd") + 1] - file_ini_content = file_ini_content.replace("MAIN_INITIAL_STATE_FOLDER", main_initial_state_folder) - msg = "The main folder for all initial states is set based on the system argument (-misd): " + main_initial_state_folder - print(msg) - # - date for initial states - if "-dfis" in system_argument: - date_for_initial_states = system_argument[system_argument.index("-dfis") + 1] - file_ini_content = file_ini_content.replace("DATE_FOR_INITIAL_STATES", date_for_initial_states) - msg = "The date for all initial state files is set based on the system argument (-dfis): " + date_for_initial_states - print(msg) - - # optional system argument for modifying forcing files - if "-pff" in system_argument: - precipitation_forcing_file = system_argument[system_argument.index("-pff") + 1] - file_ini_content = file_ini_content.replace("PRECIPITATION_FORCING_FILE", precipitation_forcing_file) - msg = "The precipitation forcing file 'precipitationNC' is set based on the system argument (-pff): " + precipitation_forcing_file - print(msg) - if "-tff" in system_argument: - temperature_forcing_file = system_argument[system_argument.index("-tff") + 1] - file_ini_content = file_ini_content.replace("TEMPERATURE_FORCING_FILE", temperature_forcing_file) - msg = "The temperature forcing file 'temperatureNC' is set based on the system argument (-tff): " + temperature_forcing_file - print(msg) - if "-rpetff" in system_argument: - ref_pot_et_forcing_file = system_argument[system_argument.index("-rpetff") + 1] - file_ini_content = file_ini_content.replace("REF_POT_ET_FORCING_FILE", ref_pot_et_forcing_file) - msg = "The reference potential ET forcing file 'refETPotFileNC' is set based on the system argument (-tff): " + ref_pot_et_forcing_file - print(msg) - - # optional system argument for modifying baseflow exponent - if "-bfexp" in system_argument: - baseflow_exponent = system_argument[system_argument.index("-bfexp") + 1] - file_ini_content = file_ini_content.replace("BASEFLOW_EXP_INPUT", baseflow_exponent) - msg = "The groundwater baseflow exponent 'bfexp' is set based on the system argument (-bfexp): " + baseflow_exponent - print(msg) - - # folder for saving original and modified ini files - folder_for_ini_files = os.path.join(main_output_dir, "ini_files") - # - for a run that is part of a set of parallel (clone) runs - if system_argument[2] == "parallel" or system_argument[2] == "debug_parallel" or system_argument[2] == "debug-parallel": - clone_code = str(system_argument[3]) - output_folder_with_clone_code = "M%07i" %int(clone_code) - folder_for_ini_files = os.path.join(main_output_dir, output_folder_with_clone_code, "ini_files") - - # create folder - if os.path.exists(folder_for_ini_files): shutil.rmtree(folder_for_ini_files) - os.makedirs(folder_for_ini_files) - - # save/copy the original ini file - shutil.copy(original_ini_file, os.path.join(folder_for_ini_files, os.path.basename(original_ini_file) + ".original")) - - # save the new ini file - new_ini_file_name = os.path.join(folder_for_ini_files, os.path.basename(original_ini_file) + ".modified_and_used") - new_ini_file = open(new_ini_file_name, "w") - new_ini_file.write(file_ini_content) - new_ini_file.close() - - return new_ini_file_name - - -def main(): - - # get the full path of configuration/ini file given in the system argument - iniFileName = os.path.abspath(sys.argv[1]) - - # modify ini file and return it in a new location - if "-mod" in sys.argv: - iniFileName = modify_ini_file(original_ini_file = iniFileName, \ - system_argument = sys.argv) - - # debug option - debug_mode = False - if len(sys.argv) > 2: - if sys.argv[2] == "debug" or sys.argv[2] == "debug_parallel" or sys.argv[2] == "debug-parallel": debug_mode = True - - # parallel option - this_run_is_part_of_a_set_of_parallel_run = False - if len(sys.argv) > 2: - if sys.argv[2] == "parallel" or sys.argv[2] == "debug_parallel" or sys.argv[2] == "debug-parallel": this_run_is_part_of_a_set_of_parallel_run = True - - # object to handle configuration/ini file - configuration = Configuration(iniFileName = iniFileName, \ - debug_mode = debug_mode, \ - no_modification = False) - - - # for a parallel run (e.g. usually for 5min and 6min runs), we assign a specific directory based on the clone number/code: - if this_run_is_part_of_a_set_of_parallel_run: - # modfiying outputDir, clone-map landmask, etc (based on the given system arguments) - # - clone code in string - clone_code = str(sys.argv[3]) - # - output folder - output_folder_with_clone_code = "M%07i" %int(clone_code) - configuration.globalOptions['outputDir'] += "/" + output_folder_with_clone_code - # - clone map - configuration.globalOptions['cloneMap'] = configuration.globalOptions['cloneMap'] %(int(clone_code)) - # - landmask for model calculation - if configuration.globalOptions['landmask'] != "None": - configuration.globalOptions['landmask'] = configuration.globalOptions['landmask'] %(int(clone_code)) - # - landmask for reporting - if configuration.reportingOptions['landmask_for_reporting'] != "None": - configuration.reportingOptions['landmask_for_reporting'] = configuration.reportingOptions['landmask_for_reporting'] %(int(clone_code)) - - # set configuration - configuration.set_configuration(system_arguments = sys.argv) - - - # timeStep info: year, month, day, doy, hour, etc - currTimeStep = ModelTime() - - # object for spin_up - spin_up = SpinUp(configuration) - - # spinning-up - noSpinUps = int(configuration.globalOptions['maxSpinUpsInYears']) - initial_state = None - if noSpinUps > 0: - - logger.info('Spin-Up #Total Years: '+str(noSpinUps)) - - spinUpRun = 0 ; has_converged = False - while spinUpRun < noSpinUps and has_converged == False: - spinUpRun += 1 - currTimeStep.getStartEndTimeStepsForSpinUp( - configuration.globalOptions['startTime'], - spinUpRun, noSpinUps) - logger.info('Spin-Up Run No. '+str(spinUpRun)) - deterministic_runner = DeterministicRunner(configuration, currTimeStep, initial_state, sys.argv, spinUpRun = True) - - all_state_begin = deterministic_runner.model.getAllState() - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - - all_state_end = deterministic_runner.model.getAllState() - - has_converged = spin_up.checkConvergence(all_state_begin, all_state_end, spinUpRun, deterministic_runner.model.routing.cellArea) - - initial_state = deterministic_runner.model.getState() - - # TODO: for a parallel run call merging when the spinUp is done and isolate the states in a separate directory/folder - - # Running the deterministic_runner (excluding DA scheme) - currTimeStep.getStartEndTimeSteps(configuration.globalOptions['startTime'], - configuration.globalOptions['endTime']) - - logger.info('Transient simulation run started.') - deterministic_runner = DeterministicRunner(configuration, currTimeStep, initial_state, sys.argv, spinUpRun = False) - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - -if __name__ == '__main__': - # print disclaimer - disclaimer.print_disclaimer(with_logger = True) - sys.exit(main()) diff --git a/model/deterministic_runner_with_arguments.py b/model/deterministic_runner_with_arguments.py deleted file mode 100644 index 218d440f2..000000000 --- a/model/deterministic_runner_with_arguments.py +++ /dev/null @@ -1,629 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Ludovicus P. H. (Rens) van Beek, Edwin H. Sutanudjaja, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Inge E. M. de Graaf, Kor de Jong, Patricia Lopez Lopez, -# Stefanie Pessenteiner, Oliver Schmitz, Menno W. Straatsma, Niko Wanders, Dominik Wisser, -# and Marc F. P. Bierkens, -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import sys -import shutil -import datetime - -import pcraster as pcr -from pcraster.framework import DynamicModel -from pcraster.framework import DynamicFramework - -from configuration import Configuration -from currTimeStep import ModelTime -from reporting import Reporting -from spinUp import SpinUp - -from pcrglobwb import PCRGlobWB - -import logging -logger = logging.getLogger(__name__) - -import disclaimer - -class DeterministicRunner(DynamicModel): - - def __init__(self, configuration, modelTime, initialState = None, system_argument = None, spinUpRun = False): - DynamicModel.__init__(self) - - self.modelTime = modelTime - self.model = PCRGlobWB(configuration, modelTime, initialState, spinUpRun) - self.reporting = Reporting(configuration, self.model, modelTime) - - # the model paramaters may be modified - self.parameter_adjusment = False - if (("-adjparm" in list(system_argument)) or ('prefactorOptions' in configuration.allSections)): - self.adusting_parameters(configuration, system_argument) - self.parameter_adjusment = True - - # option to include merging processes for pcraster maps and netcdf files: - # - default option - self.with_merging = False - # - set based on ini file - if ('with_merging' in configuration.globalOptions.keys()) and (configuration.globalOptions['with_merging'] == "False"): - self.with_merging = False - if ('with_merging' in configuration.globalOptions.keys()) and (configuration.globalOptions['with_merging'] == "True"): - self.with_merging = True - - # for a run with spinUp, we have to disactivate merging - if spinUpRun == True: self.with_merging = False - - # make the configuration available for the other method/function - self.configuration = configuration - - def adusting_parameters(self, configuration, system_argument): - - # global pre-multipliers given in the argument: - if "-adjparm" in list(system_argument): - - # starting adjustment index (sai) - sai = system_argument.index("-adjparm") - - logger.info("Adjusting some model parameters based on given values in the system argument.") - - # pre-multipliers for minSoilDepthFrac, kSat, recessionCoeff, storCap and degreeDayFactor - multiplier_for_minSoilDepthFrac = float(system_argument[sai + 1]) # linear scale - multiplier_for_kSat = float(system_argument[sai + 2]) # log scale - multiplier_for_recessionCoeff = float(system_argument[sai + 3]) # log scale - multiplier_for_storCap = float(system_argument[sai + 4]) # linear scale - multiplier_for_degreeDayFactor = float(system_argument[sai + 5]) # linear scale - - # pre-multiplier for the reference potential ET - self.multiplier_for_refPotET = float(system_argument[sai + 6]) # linear scale - - # pre-multiplier for manningsN - multiplier_for_manningsN = float(system_argument[sai + 7]) # linear scale - - # modification for storGroundwaterIni - storGroundwaterIni_file = str(system_argument[sai + 8]) - - # it is also possible to define prefactors via the ini/configuration file: - # - this will be overwrite any previous given pre-multipliers - if 'prefactorOptions' in configuration.allSections: - - logger.info("Adjusting some model parameters based on given values in the ini/configuration file.") - - self.multiplier_for_refPotET = float(configuration.prefactorOptions['linear_multiplier_for_refPotET' ]) # linear scale # Note that this one does NOT work for the changing WMIN or Joyce land cover options. - multiplier_for_degreeDayFactor = float(configuration.prefactorOptions['linear_multiplier_for_degreeDayFactor' ]) # linear scale - multiplier_for_minSoilDepthFrac = float(configuration.prefactorOptions['linear_multiplier_for_minSoilDepthFrac']) # linear scale - multiplier_for_kSat = float(configuration.prefactorOptions['log_10_multiplier_for_kSat' ]) # log scale - multiplier_for_storCap = float(configuration.prefactorOptions['linear_multiplier_for_storCap' ]) # linear scale - multiplier_for_recessionCoeff = float(configuration.prefactorOptions['log_10_multiplier_for_recessionCoeff' ]) # log scale - multiplier_for_manningsN = float(configuration.prefactorOptions['multiplier_for_manningsN' ]) # linear scale - - storGroundwaterIni_file = str(configuration.prefactorOptions['storGroundwaterIni_file']) # file location (please use full path) - - # saving global pre-multipliers to the log file: - msg = "\n" - msg += "\n" - msg += "Multiplier values used: "+"\n" - msg += "For minSoilDepthFrac : "+str(multiplier_for_minSoilDepthFrac)+"\n" - msg += "For kSat (log-scale) : "+str(multiplier_for_kSat )+"\n" - msg += "For recessionCoeff (log-scale) : "+str(multiplier_for_recessionCoeff )+"\n" - msg += "For storCap : "+str(multiplier_for_storCap )+"\n" - msg += "For degreeDayFactor : "+str(multiplier_for_degreeDayFactor )+"\n" - msg += "For refPotET : "+str(self.multiplier_for_refPotET )+"\n" - msg += "For multiplier_for_manningsN : "+str(multiplier_for_manningsN )+"\n" - msg += "For storGroundwaterIni_file : "+str(storGroundwaterIni_file )+"\n" - logger.info(msg) - # - also to a txt file - f = open("multiplier.txt", "w") # this will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - f.write(msg) - f.close() - - # adjust storGroundwaterIni - if storGroundwaterIni_file != "Default": - #~ self.model.groundwater.storGroundwater = pcr.readmap(storGroundwaterIni_file) - self.model.groundwater.storGroundwater = vos.readPCRmapClone(storGroundwaterIni_file,\ - configuration.cloneMap,\ - configuration.tmpDir) - self.model.groundwater.storGroundwater = pcr.ifthen(self.landmask, \ - pcr.cover(self.model.groundwater.storGroundwater, 0.0)) - pcr.report(self.model.groundwater.storGroundwater, "storGroundwaterIni.map") - - # set parameter "manningsN" based on the given pre-multiplier - # - also saving the adjusted parameter maps to pcraster files - # - these will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - # "manningsN" - # minimum value is zero and using log-scale - self.model.routing.manningsN = multiplier_for_manningsN * self.model.routing.manningsN - # report the map - pcr.report(self.model.routing.manningsN, "manningsN.map") - - # set parameter "recessionCoeff" based on the given pre-multiplier - # - also saving the adjusted parameter maps to pcraster files - # - these will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - # "recessionCoeff" - # minimum value is zero and using log-scale - self.model.groundwater.recessionCoeff = pcr.max(0.0, (10**(multiplier_for_recessionCoeff)) * self.model.groundwater.recessionCoeff) - self.model.groundwater.recessionCoeff = pcr.min(1.0, self.model.groundwater.recessionCoeff) - # report the map - pcr.report(self.model.groundwater.recessionCoeff, "recessionCoeff.map") - - # set parameters "kSat", "storCap", "minSoilDepthFrac", and "degreeDayFactor" based on the given pre-multipliers - for coverType in self.model.landSurface.coverTypes: - - # "degreeDayFactor" - self.model.landSurface.landCoverObj[coverType].degreeDayFactor = pcr.max(0.0, multiplier_for_degreeDayFactor *\ - self.model.landSurface.landCoverObj[coverType].degreeDayFactor) - # report the map - pcraster_filename = "degreeDayFactor" + "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].degreeDayFactor , pcraster_filename) - - # "kSat" and "storCap" for 2 layer model - if self.model.landSurface.numberOfSoilLayers == 2: - - # "kSat" - # minimum value is zero and using-log-scale - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp) - self.model.landSurface.landCoverObj[coverType].parameters.kSatLow = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatLow) - # report the maps - pcraster_filename = "kSatUpp"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp, pcraster_filename) - pcraster_filename = "kSatLow"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatLow, pcraster_filename) - - # "storCap" - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp) - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow) - # report the maps - pcraster_filename = "storCapUpp"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp, pcraster_filename) - pcraster_filename = "storCapLow"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapLow, pcraster_filename) - - # "kSat" and "storCap" for 3 layer model - if self.model.landSurface.numberOfSoilLayers == 3: - - # "kSat" - # minimum value is zero and using-log-scale - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005) - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030) - self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150) - # report the maps - pcraster_filename = "kSatUpp000005"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005, pcraster_filename) - pcraster_filename = "kSatUpp005030"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030, pcraster_filename) - pcraster_filename = "kSatLow030150"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150, pcraster_filename) - - # "storCap" - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005) - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030) - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150) - # report the maps - pcraster_filename = "storCapUpp000005"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005, pcraster_filename) - pcraster_filename = "storCapUpp005030"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030, pcraster_filename) - pcraster_filename = "storCapLow030150"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150, pcraster_filename) - - - # re-calculate rootZoneWaterStorageCap as the consequence of the modification of "storCap" - # This is WMAX in the oldcalc script. - if self.model.landSurface.numberOfSoilLayers == 2: - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap = self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow - if self.model.landSurface.numberOfSoilLayers == 3: - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap = self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005 +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030 +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150 - # report the map - pcraster_filename = "rootZoneWaterStorageCap"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap, pcraster_filename) - - # "minSoilDepthFrac" - if multiplier_for_minSoilDepthFrac != 1.0: - - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.max(0.0, multiplier_for_minSoilDepthFrac*\ - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac) - # for minSoilDepthFrac - values will be limited by maxSoilDepthFrac - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.min(\ - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac,\ - self.model.landSurface.landCoverObj[coverType].maxSoilDepthFrac) - # maximum value is 1.0 - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.min(1.0, self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac) - # report the map - pcraster_filename = "minSoilDepthFrac"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac, pcraster_filename) - - # re-calculate arnoBeta (as the consequence of the modification of minSoilDepthFrac) - self.model.landSurface.landCoverObj[coverType].arnoBeta = pcr.max(0.001,\ - (self.model.landSurface.landCoverObj[coverType].maxSoilDepthFrac-1.)/(1.-self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac)+\ - self.model.landSurface.landCoverObj[coverType].parameters.orographyBeta-0.01) - self.model.landSurface.landCoverObj[coverType].arnoBeta = pcr.cover(pcr.max(0.001,\ - self.model.landSurface.landCoverObj[coverType].arnoBeta), 0.001) - # report the map - pcraster_filename = "arnoBeta"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].arnoBeta, pcraster_filename) - - # re-calculate rootZoneWaterStorageMin (as the consequence of the modification of minSoilDepthFrac) - # This is WMIN in the oldcalc script. - # WMIN (unit: m): minimum local soil water capacity within the grid-cell - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin = self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac *\ - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap - # report the map - pcraster_filename = "rootZoneWaterStorageMin"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin, pcraster_filename) - - # re-calculate rootZoneWaterStorageRange (as the consequence of the modification of rootZoneWaterStorageRange and minSoilDepthFrac) - # WMAX - WMIN (unit: m) - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageRange = self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap -\ - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin - # report the map - pcraster_filename = "rootZoneWaterStorageRange"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageRange, pcraster_filename) - - def initial(self): - pass - - def dynamic(self): - - # re-calculate current model time using current pcraster timestep value - self.modelTime.update(self.currentTimeStep()) - - # read model forcing (will pick up current model time from model time object) - self.model.read_forcings() - - # adjust the reference potential ET according to the given pre-multiplier - if self.parameter_adjusment: - self.model.meteo.referencePotET = self.model.meteo.referencePotET * self.multiplier_for_refPotET - - # update model (will pick up current model time from model time object) - # - for a run coupled to MODFLOW, water balance checks are not valid due to lateral flow. - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model.update(report_water_balance = False) - else: - self.model.update(report_water_balance = True) - - # do any needed reporting for this time step - self.reporting.report() - - # at the last day of the month, stop calculation until modflow and related merging process are ready (only for a run with modflow) - if self.modelTime.isLastDayOfMonth() and (self.configuration.online_coupling_between_pcrglobwb_and_modflow or\ - self.with_merging): - - # wait until modflow files are ready - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - modflow_is_ready = False - self.count_check = 0 - while modflow_is_ready == False: - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 59:\ - modflow_is_ready = self.check_modflow_status() - - # wait until merged files are ready - merged_files_are_ready = False - while merged_files_are_ready == False: - self.count_check = 0 - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 59:\ - merged_files_are_ready = self.check_merging_status() - - def check_modflow_status(self): - - status_file = str(self.configuration.main_output_directory) + "/modflow/transient/maps/modflow_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: ' + status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - return status - - def check_merging_status(self): - - status_file = str(self.configuration.main_output_directory) + "/global/maps/merged_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: ' + status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - return status - - -def modify_ini_file(original_ini_file, - system_argument): - - # created by Edwin H. Sutanudjaja on August 2020 for the Ulysses project - - # open and read ini file - file_ini = open(original_ini_file, "rt") - file_ini_content = file_ini.read() - file_ini.close() - - # system argument for replacing outputDir (-mod) ; this is always required - main_output_dir = system_argument[system_argument.index("-mod") + 1] - file_ini_content = file_ini_content.replace("MAIN_OUTPUT_DIR", main_output_dir) - msg = "The output folder 'outputDir' is set based on the system argument (-mod): " + main_output_dir - print(msg) - - # optional system arguments for modifying startTime (-sd) and endTime (-ed) - if "-sd" in system_argument: - starting_date = system_argument[system_argument.index("-sd") + 1] - file_ini_content = file_ini_content.replace("STARTING_DATE", starting_date) - msg = "The starting date 'startTime' is set based on the system argument (-sd): " + starting_date - print(msg) - if "-ed" in system_argument: - end_date = system_argument[system_argument.index("-ed") + 1] - file_ini_content = file_ini_content.replace("END_DATE", end_date) - msg = "The end date 'END_DATE' is set based on the system argument (-ed): " + end_date - print(msg) - - # optional system arguments for initial condition files - # - main initial state folder - if "-misd" in system_argument: - main_initial_state_folder = system_argument[system_argument.index("-misd") + 1] - file_ini_content = file_ini_content.replace("MAIN_INITIAL_STATE_FOLDER", main_initial_state_folder) - msg = "The main folder for all initial states is set based on the system argument (-misd): " + main_initial_state_folder - print(msg) - # - date for initial states - if "-dfis" in system_argument: - date_for_initial_states = system_argument[system_argument.index("-dfis") + 1] - file_ini_content = file_ini_content.replace("DATE_FOR_INITIAL_STATES", date_for_initial_states) - msg = "The date for all initial state files is set based on the system argument (-dfis): " + date_for_initial_states - print(msg) - - - # optional system argument for modifying forcing files - - # - precipitationNC = PRECIPITATION_FORCING_FILE - if "-pff" in system_argument: - precipitation_forcing_file = system_argument[system_argument.index("-pff") + 1] - file_ini_content = file_ini_content.replace("PRECIPITATION_FORCING_FILE", precipitation_forcing_file) - msg = "The precipitation forcing file 'precipitationNC' is set based on the system argument (-pff): " + precipitation_forcing_file - print(msg) - - # - temperatureNC = TEMPERATURE_FORCING_FILE - if "-tff" in system_argument: - temperature_forcing_file = system_argument[system_argument.index("-tff") + 1] - file_ini_content = file_ini_content.replace("TEMPERATURE_FORCING_FILE", temperature_forcing_file) - msg = "The temperature forcing file 'temperatureNC' is set based on the system argument (-tff): " + temperature_forcing_file - print(msg) - - # - refETPotFileNC = REF_POT_ET_FORCING_FILE - if "-rpetff" in system_argument: - ref_pot_et_forcing_file = system_argument[system_argument.index("-rpetff") + 1] - file_ini_content = file_ini_content.replace("REF_POT_ET_FORCING_FILE", ref_pot_et_forcing_file) - msg = "The reference potential ET forcing file 'refETPotFileNC' is set based on the system argument (-tff): " + ref_pot_et_forcing_file - print(msg) - - # atmospheric_pressure = PRESSURE_FORCING_FILE - if "-presff" in system_argument: - pressure_forcing_file = system_argument[system_argument.index("-presff") + 1] - file_ini_content = file_ini_content.replace("PRESSURE_FORCING_FILE", pressure_forcing_file) - msg = "The pressure forcing file 'atmospheric_pressure' is set based on the system argument (-presff): " + pressure_forcing_file - print(msg) - - # wind_speed_10m = WIND_FORCING_FILE - if "-windff" in system_argument: - wind_forcing_file = system_argument[system_argument.index("-windff") + 1] - file_ini_content = file_ini_content.replace("WIND_FORCING_FILE", wind_forcing_file) - msg = "The wind forcing file 'wind_speed_10m' is set based on the system argument (-windff): " + wind_forcing_file - print(msg) - - # shortwave_radiation = SHORTWAVE_RADIATION_FORCING_FILE - if "-swradff" in system_argument: - shorwave_radiation_forcing_file = system_argument[system_argument.index("-swradff") + 1] - file_ini_content = file_ini_content.replace("SHORTWAVE_RADIATION_FORCING_FILE", shorwave_radiation_forcing_file) - msg = "The shortwave radiation forcing file 'shortwave_radiation' is set based on the system argument (-windff): " + shorwave_radiation_forcing_file - print(msg) - - # relative_humidity = RELATIVE_HUMIDITY_FORCING_FILE - if "-relhumff" in system_argument: - relative_humidity_forcing_file = system_argument[system_argument.index("-relhumff") + 1] - file_ini_content = file_ini_content.replace("RELATIVE_HUMIDITY_FORCING_FILE", relative_humidity_forcing_file) - msg = "The relative humidity forcing file 'relative_humidity' is set based on the system argument (-relhumff): " + relative_humidity_forcing_file - print(msg) - - # optional system argument for modifying baseflow exponent - if "-bfexp" in system_argument: - baseflow_exponent = system_argument[system_argument.index("-bfexp") + 1] - file_ini_content = file_ini_content.replace("BASEFLOW_EXP_INPUT", baseflow_exponent) - msg = "The groundwater baseflow exponent 'bfexp' is set based on the system argument (-bfexp): " + baseflow_exponent - print(msg) - - # NUMBER_OF_SPINUP_YEARS - if "-num_of_sp_years" in system_argument: - number_of_spinup_years = system_argument[system_argument.index("-num_of_sp_years") + 1] - file_ini_content = file_ini_content.replace("NUMBER_OF_SPINUP_YEARS", number_of_spinup_years) - msg = "The number_of_spinup_years is set based on the system argument (-num_of_sp_years): " + number_of_spinup_years - print(msg) - - # CLONEMAP - if "-clonemap" in system_argument: - clonemap = system_argument[system_argument.index("-clonemap") + 1] - file_ini_content = file_ini_content.replace("CLONEMAP", clonemap) - msg = "The clonemap is set based on the system argument (-clonemap): " + clonemap - print(msg) - - # USE_MAXIMUM_STOR_GROUNDWATER_FOSSIL_INI - if "-use_max_fossil_gw_ini" in system_argument: - use_max_fossil_gw_ini = system_argument[system_argument.index("-use_max_fossil_gw_ini") + 1] - file_ini_content = file_ini_content.replace("USE_MAXIMUM_STOR_GROUNDWATER_FOSSIL_INI", use_max_fossil_gw_ini) - msg = "The option 'useMaximumStorGroundwaterFossilIni' is set based on the system argument (-use_max_fossil_gw_ini): " + use_max_fossil_gw_ini - print(msg) - - # ESTIMATE_STOR_GROUNDWATER_INI_FROM_RECHARGE - if "-est_stor_gw_from_rch" in system_argument: - est_stor_gw_from_rch = system_argument[system_argument.index("-est_stor_gw_from_rch") + 1] - file_ini_content = file_ini_content.replace("ESTIMATE_STOR_GROUNDWATER_INI_FROM_RECHARGE", est_stor_gw_from_rch) - msg = "The option 'estimateStorGroundwaterIniFromRecharge' is set based on the system argument (-est_stor_gw_from_rch): " + est_stor_gw_from_rch - print(msg) - - # dailyGroundwaterRechargeIni / DAILY_GROUNDWATER_RECHARGE_INI - if "-day_gw_rch_ini" in system_argument: - day_gw_rch_ini = system_argument[system_argument.index("-day_gw_rch_ini") + 1] - file_ini_content = file_ini_content.replace("DAILY_GROUNDWATER_RECHARGE_INI", day_gw_rch_ini) - msg = "The option 'dailyGroundwaterRechargeIni' is set based on the system argument (-day_gw_rch_ini): " + day_gw_rch_ini - print(msg) - - - # folder for saving original and modified ini files - folder_for_ini_files = os.path.join(main_output_dir, "ini_files") - # - for a run that is part of a set of parallel (clone) runs - if system_argument[2] == "parallel" or system_argument[2] == "debug_parallel" or system_argument[2] == "debug-parallel": - clone_code = str(system_argument[3]) - output_folder_with_clone_code = "M%07i" %int(clone_code) - folder_for_ini_files = os.path.join(main_output_dir, output_folder_with_clone_code, "ini_files") - - # create folder - if os.path.exists(folder_for_ini_files): shutil.rmtree(folder_for_ini_files) - os.makedirs(folder_for_ini_files) - - # save/copy the original ini file - shutil.copy(original_ini_file, os.path.join(folder_for_ini_files, os.path.basename(original_ini_file) + ".original")) - - # save the new ini file - new_ini_file_name = os.path.join(folder_for_ini_files, os.path.basename(original_ini_file) + ".modified_and_used") - new_ini_file = open(new_ini_file_name, "w") - new_ini_file.write(file_ini_content) - new_ini_file.close() - - return new_ini_file_name - - -def main(): - - # get the full path of configuration/ini file given in the system argument - iniFileName = os.path.abspath(sys.argv[1]) - - # modify ini file and return it in a new location - if "-mod" in sys.argv: - iniFileName = modify_ini_file(original_ini_file = iniFileName, \ - system_argument = sys.argv) - - # debug option - debug_mode = False - if len(sys.argv) > 2: - if sys.argv[2] == "debug" or sys.argv[2] == "debug_parallel" or sys.argv[2] == "debug-parallel": debug_mode = True - - # parallel option - this_run_is_part_of_a_set_of_parallel_run = False - if len(sys.argv) > 2: - if sys.argv[2] == "parallel" or sys.argv[2] == "debug_parallel" or sys.argv[2] == "debug-parallel": this_run_is_part_of_a_set_of_parallel_run = True - - # object to handle configuration/ini file - configuration = Configuration(iniFileName = iniFileName, \ - debug_mode = debug_mode, \ - no_modification = False) - - - # for a parallel run (e.g. usually for 5min and 6min runs), we assign a specific directory based on the clone number/code: - if this_run_is_part_of_a_set_of_parallel_run: - # modfiying outputDir, clone-map landmask, etc (based on the given system arguments) - # - clone code in string - clone_code = str(sys.argv[3]) - # - output folder - output_folder_with_clone_code = "M%07i" %int(clone_code) - configuration.globalOptions['outputDir'] += "/" + output_folder_with_clone_code - # - clone map - configuration.globalOptions['cloneMap'] = configuration.globalOptions['cloneMap'] %(int(clone_code)) - # - landmask for model calculation - if configuration.globalOptions['landmask'] != "None": - configuration.globalOptions['landmask'] = configuration.globalOptions['landmask'] %(int(clone_code)) - # - landmask for reporting - if configuration.reportingOptions['landmask_for_reporting'] != "None": - configuration.reportingOptions['landmask_for_reporting'] = configuration.reportingOptions['landmask_for_reporting'] %(int(clone_code)) - - # set configuration - configuration.set_configuration(system_arguments = sys.argv) - - - # timeStep info: year, month, day, doy, hour, etc - currTimeStep = ModelTime() - - # object for spin_up - spin_up = SpinUp(configuration) - - # spinning-up - noSpinUps = int(configuration.globalOptions['maxSpinUpsInYears']) - initial_state = None - if noSpinUps > 0: - - logger.info('Spin-Up #Total Years: '+str(noSpinUps)) - - spinUpRun = 0 ; has_converged = False - while spinUpRun < noSpinUps and has_converged == False: - spinUpRun += 1 - currTimeStep.getStartEndTimeStepsForSpinUp( - configuration.globalOptions['startTime'], - spinUpRun, noSpinUps) - logger.info('Spin-Up Run No. '+str(spinUpRun)) - deterministic_runner = DeterministicRunner(configuration, currTimeStep, initial_state, sys.argv, spinUpRun = True) - - all_state_begin = deterministic_runner.model.getAllState() - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - - all_state_end = deterministic_runner.model.getAllState() - - has_converged = spin_up.checkConvergence(all_state_begin, all_state_end, spinUpRun, deterministic_runner.model.routing.cellArea) - - initial_state = deterministic_runner.model.getState() - - # TODO: for a parallel run call merging when the spinUp is done and isolate the states in a separate directory/folder - - # Running the deterministic_runner (excluding DA scheme) - currTimeStep.getStartEndTimeSteps(configuration.globalOptions['startTime'], - configuration.globalOptions['endTime']) - - logger.info('Transient simulation run started.') - deterministic_runner = DeterministicRunner(configuration, currTimeStep, initial_state, sys.argv, spinUpRun = False) - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - -if __name__ == '__main__': - # print disclaimer - disclaimer.print_disclaimer(with_logger = True) - sys.exit(main()) diff --git a/model/etc/deterministic_runner_for_monthly_modflow_and_merging.py b/model/etc/deterministic_runner_for_monthly_modflow_and_merging.py deleted file mode 100644 index dafad450e..000000000 --- a/model/etc/deterministic_runner_for_monthly_modflow_and_merging.py +++ /dev/null @@ -1,284 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import sys -import datetime -import glob - -import pcraster as pcr -from pcraster.framework import DynamicModel -from pcraster.framework import DynamicFramework - -from configuration import Configuration -from currTimeStep import ModelTime - -try: - from reporting_for_modflow import Reporting -except: - pass - -try: - from modflow import ModflowCoupling -except: - pass - -import virtualOS as vos - -import logging -logger = logging.getLogger(__name__) - -import disclaimer - -class DeterministicRunner(DynamicModel): - - def __init__(self, configuration, modelTime): - DynamicModel.__init__(self) - - # model time object - self.modelTime = modelTime - - # make the configuration available for the other method/function - self.configuration = configuration - - # indicating whether this run includes modflow or merging processes - # - Only the "Global" and "part_one" runs include modflow or merging processes - self.include_merging_or_modflow = True - if self.configuration.globalOptions['cloneAreas'] == "part_two": self.include_merging_or_modflow = False - - if self.include_merging_or_modflow: - - # netcdf merging options - self.netcdf_format = self.configuration.mergingOutputOptions['formatNetCDF'] - self.zlib_option = self.configuration.mergingOutputOptions['zlib'] - - # output files/variables that will be merged - nc_report_list = ["outDailyTotNC", - "outMonthTotNC", "outMonthAvgNC", "outMonthEndNC", "outMonthMaxNC", - "outAnnuaTotNC", "outAnnuaAvgNC", "outAnnuaEndNC", "outAnnuaMaxNC"] - for nc_report_type in nc_report_list: - vars(self)[nc_report_type] = self.configuration.mergingOutputOptions[nc_report_type] - - - # model and reporting objects - # - Note that both are still needed even - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model = ModflowCoupling(configuration, modelTime) - self.reporting = Reporting(configuration, self.model, modelTime) - else: - # somehow you need to set the clone map (as the dynamic framework needs it and the "self.model" is not made) - pcr.setclone(configuration.cloneMap) - - def initial(self): - - # get or prepare the initial condition for groundwater head - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model.get_initial_heads() - - def dynamic(self): - - # re-calculate current model time using current pcraster timestep value - self.modelTime.update(self.currentTimeStep()) - - # update/calculate model and daily merging, and report ONLY at the last day of the month - if self.modelTime.isLastDayOfMonth(): - - # wait until all pcrglobwb model runs are done - pcrglobwb_is_ready = False - self.count_check = 0 - while pcrglobwb_is_ready == False: - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 49:\ - pcrglobwb_is_ready = self.check_pcrglobwb_status() - - # merging netcdf files at daily resolution - start_date = '%04i-%02i-01' %(self.modelTime.year, self.modelTime.month) # TODO: Make it flexible for a run starting not on the 1st January. - end_date = self.modelTime.fulldate - self.merging_netcdf_files("outDailyTotNC", start_date, end_date) - - # for runs with modflow - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - - # merging pcraster maps that are needed for MODFLOW calculation - msg = "Merging pcraster map files that are needed for the MODFLOW calculation." - logger.info(msg) - cmd = 'python '+ self.configuration.path_of_this_module + "/merge_pcraster_maps.py " + str(self.modelTime.fulldate) + " " +\ - str(self.configuration.main_output_directory)+"/ maps 8 "+\ - str("Global") - vos.cmd_line(cmd, using_subprocess = False) - - # cleaning up unmerged files (not tested yet) - clean_up_pcraster_maps = False - if self.configuration.mergingOutputOptions["delete_unmerged_pcraster_maps"] == "True": clean_up_pcraster_maps = True # TODO: FIXME: This is NOT working yet. - if clean_up_pcraster_maps: - files_to_be_removed = glob.glob(str(self.configuration.main_output_directory) + "/M*/maps/*" + str(self.modelTime.fulldate) + "*") - for f in files_to_be_removed: - print(f) - os.remove(f) - - # update MODFLOW model (It will pick up current model time from the modelTime object) - self.model.update() - # reporting is only done at the end of the month - self.reporting.report() - - - # merging initial conditions (pcraster maps) of PCR-GLOBWB - # ~ if self.modelTime.isLastDayOfYear(): - if self.modelTime.isLastDayOfMonth(): - - msg = "Merging pcraster map files belonging to initial conditions." - logger.info(msg) - cmd = 'python '+ self.configuration.path_of_this_module + "/merge_pcraster_maps.py " + str(self.modelTime.fulldate) + " " +\ - str(self.configuration.main_output_directory)+"/ states 8 "+\ - str("Global") - vos.cmd_line(cmd, using_subprocess = False) - - # cleaning up unmerged files (not tested yet) - clean_up_pcraster_maps = False - if self.configuration.mergingOutputOptions["delete_unmerged_pcraster_maps"] == "True": clean_up_pcraster_maps = True # TODO: FIXME: This is NOT working yet. - if clean_up_pcraster_maps: - files_to_be_removed = glob.glob(str(self.configuration.main_output_directory) + "/M*/states/*" + str(self.modelTime.fulldate) + "*") - for f in files_to_be_removed: - print(f) - os.remove(f) - - - # monthly and annual merging - if self.modelTime.isLastDayOfYear(): - - # merging netcdf files at monthly resolutions - start_date = '%04i-01-31' %(self.modelTime.year) # TODO: Make it flexible for a run starting not on the 1st January. - self.merging_netcdf_files("outMonthTotNC", start_date, end_date) - self.merging_netcdf_files("outMonthAvgNC", start_date, end_date) - self.merging_netcdf_files("outMonthEndNC", start_date, end_date) - self.merging_netcdf_files("outMonthMaxNC", start_date, end_date) - - # merging netcdf files at annual resolutions - start_date = '%04i-12-31' %(self.modelTime.year) # TODO: Make it flexible for a run starting not on the 1st January. - end_date = self.modelTime.fulldate - self.merging_netcdf_files("outAnnuaTotNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaAvgNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaEndNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaMaxNC", start_date, end_date) - - - # make an empty file indicating that merging process is done - if self.modelTime.isLastDayOfMonth() or self.modelTime.isLastDayOfYear(): - - outputDirectory = str(self.configuration.main_output_directory) + "/global/maps/" - filename = outputDirectory + "/merged_files_for_" + str(self.modelTime.fulldate)+"_are_ready.txt" - if os.path.exists(filename): os.remove(filename) - open(filename, "w").close() - - - def merging_netcdf_files(self, nc_report_type, start_date, end_date, max_number_of_cores = 20): - - if str(vars(self)[nc_report_type]) != "None": - - netcdf_files_that_will_be_merged = vars(self)[nc_report_type] - - msg = "Merging netcdf files for the files/variables: " + netcdf_files_that_will_be_merged - logger.info(msg) - - cmd = 'python '+ self.configuration.path_of_this_module + "/merge_netcdf.py " + str(self.configuration.main_output_directory) + " " +\ - str(self.configuration.main_output_directory) + "/global/netcdf/ "+\ - str(nc_report_type) + " " +\ - str(start_date) + " " +\ - str(end_date) + " " +\ - str(netcdf_files_that_will_be_merged) + " " +\ - str(self.netcdf_format) + " " +\ - str(self.zlib_option ) + " " +\ - str(max_number_of_cores) + " " +\ - str("Global") + " " - - msg = "Using the following command line: " + cmd - logger.info(msg) - - vos.cmd_line(cmd, using_subprocess = False) - - def check_pcrglobwb_status(self): - - if self.configuration.globalOptions['cloneAreas'] == "Global" or \ - self.configuration.globalOptions['cloneAreas'] == "part_one": - clone_areas = ['M%02d'%i for i in range(1,53+1,1)] - else: - clone_areas = list(set(self.configuration.globalOptions['cloneAreas'].split(","))) - for clone_area in clone_areas: - status_file = str(self.configuration.main_output_directory) + "/" +str(clone_area) + "/maps/pcrglobwb_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: '+status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - - print(status) - - return status - -def main(): - - # print disclaimer - disclaimer.print_disclaimer() - - # get the full path of configuration/ini file given in the system argument - iniFileName = os.path.abspath(sys.argv[1]) - - # debug option - debug_mode = False - if len(sys.argv) > 2: - if sys.argv[2] == "debug" or sys.argv[2] == "debug_parallel": debug_mode = True - - # options to perform steady state calculation - steady_state_only = False - if len(sys.argv) > 3: - if sys.argv[3] == "steady-state-only": steady_state_only = True - - # object to handle configuration/ini file - configuration = Configuration(iniFileName = iniFileName, \ - debug_mode = debug_mode, \ - steady_state_only = steady_state_only) - - # timeStep info: year, month, day, doy, hour, etc - currTimeStep = ModelTime() - - # Running the deterministic_runner - currTimeStep.getStartEndTimeSteps(configuration.globalOptions['startTime'], - configuration.globalOptions['endTime']) - logger.info('Model run starts.') - deterministic_runner = DeterministicRunner(configuration, currTimeStep) - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - -if __name__ == '__main__': - # print disclaimer - disclaimer.print_disclaimer(with_logger = True) - sys.exit(main()) - diff --git a/model/etc/deterministic_runner_for_monthly_modflow_and_merging_ulysses.py b/model/etc/deterministic_runner_for_monthly_modflow_and_merging_ulysses.py deleted file mode 100644 index 54a36a6da..000000000 --- a/model/etc/deterministic_runner_for_monthly_modflow_and_merging_ulysses.py +++ /dev/null @@ -1,306 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import sys -import datetime -import glob - -import pcraster as pcr -from pcraster.framework import DynamicModel -from pcraster.framework import DynamicFramework - -from configuration_for_modflow import Configuration -from currTimeStep import ModelTime - -try: - from reporting_for_modflow import Reporting -except: - pass - -try: - from modflow import ModflowCoupling -except: - pass - -import virtualOS as vos - -import logging -logger = logging.getLogger(__name__) - -import disclaimer - -class DeterministicRunner(DynamicModel): - - def __init__(self, configuration, modelTime): - DynamicModel.__init__(self) - - # model time object - self.modelTime = modelTime - - # make the configuration available for the other method/function - self.configuration = configuration - - # indicating whether this run includes modflow or merging processes - self.include_merging_or_modflow = True - # - For the standard PCR-GLOBWB 5arcmin runs, only the "Global" and "part_one" runs include modflow or merging processes - if "cloneAreas" in list(self.configuration.globalOptions.keys()) and\ - self.configuration.globalOptions['cloneAreas'] == "part_two": self.include_merging_or_modflow = False - - if self.include_merging_or_modflow: - - # netcdf merging options - self.netcdf_format = self.configuration.mergingOutputOptions['formatNetCDF'] - self.zlib_option = self.configuration.mergingOutputOptions['zlib'] - - # output files/variables that will be merged - nc_report_list = ["outDailyTotNC", - "outMonthTotNC", "outMonthAvgNC", "outMonthEndNC", "outMonthMaxNC", - "outAnnuaTotNC", "outAnnuaAvgNC", "outAnnuaEndNC", "outAnnuaMaxNC"] - for nc_report_type in nc_report_list: - vars(self)[nc_report_type] = self.configuration.mergingOutputOptions[nc_report_type] - - - # model and reporting objects - # - Note that both are still needed even - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model = ModflowCoupling(configuration, modelTime) - self.reporting = Reporting(configuration, self.model, modelTime) - else: - # somehow you need to set the clone map (as the dynamic framework needs it and the "self.model" is not made) - pcr.setclone(configuration.cloneMap) - - def initial(self): - - # get or prepare the initial condition for groundwater head - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model.get_initial_heads() - - def dynamic(self): - - # re-calculate current model time using current pcraster timestep value - self.modelTime.update(self.currentTimeStep()) - - # update/calculate model and daily merging, and report ONLY at the last day of the month - if self.modelTime.isLastDayOfMonth(): - - # wait until all pcrglobwb model runs are done - pcrglobwb_is_ready = False - self.count_check = 0 - while pcrglobwb_is_ready == False: - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 49:\ - pcrglobwb_is_ready = self.check_pcrglobwb_status() - - # merging netcdf files at daily resolution - start_date = '%04i-%02i-01' %(self.modelTime.year, self.modelTime.month) # TODO: Make it flexible for a run starting not on the 1st January. - end_date = self.modelTime.fulldate - self.merging_netcdf_files("outDailyTotNC", start_date, end_date) - - # for runs with modflow - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - - # merging pcraster maps that are needed for MODFLOW calculation - msg = "Merging pcraster map files that are needed for the MODFLOW calculation." - logger.info(msg) - cmd = 'python '+ self.configuration.path_of_this_module + "/merge_pcraster_maps.py " + str(self.modelTime.fulldate) + " " +\ - str(self.configuration.main_output_directory)+"/ maps 8 "+\ - str("Global") - vos.cmd_line(cmd, using_subprocess = False) - - # cleaning up unmerged files (not tested yet) - clean_up_pcraster_maps = False - if self.configuration.mergingOutputOptions["delete_unmerged_pcraster_maps"] == "True": clean_up_pcraster_maps = True # TODO: FIXME: This is NOT working yet. - if clean_up_pcraster_maps: - files_to_be_removed = glob.glob(str(self.configuration.main_output_directory) + "/M*/maps/*" + str(self.modelTime.fulldate) + "*") - for f in files_to_be_removed: - print(f) - os.remove(f) - - # update MODFLOW model (It will pick up current model time from the modelTime object) - self.model.update() - # reporting is only done at the end of the month - self.reporting.report() - - - # merging initial conditions (pcraster maps) of PCR-GLOBWB - # ~ if self.modelTime.isLastDayOfYear(): - # - for Ulysses we have to do it on every month - if self.modelTime.isLastDayOfMonth(): - - msg = "Merging pcraster map files belonging to initial conditions." - logger.info(msg) - # ~ cmd = 'python '+ self.configuration.path_of_this_module + "/merge_pcraster_maps.py " + str(self.modelTime.fulldate) + " " +\ - # ~ str(self.configuration.main_output_directory)+"/ states 8 "+\ - # ~ str("Global") - # - for Ulysses: - # example: python3 merge_pcraster_maps_6_arcmin_ulysses.py ${END_DATE} ${MAIN_OUTPUT_DIR} states 2 Global 71 False - cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_pcraster_maps_6_arcmin_ulysses.py " + str(self.modelTime.fulldate) + " " +\ - str(self.configuration.main_output_directory)+"/ states 8 "+\ - str("Global 71 False") - vos.cmd_line(cmd, using_subprocess = False) - - # cleaning up unmerged files (not tested yet) - clean_up_pcraster_maps = False - if self.configuration.mergingOutputOptions["delete_unmerged_pcraster_maps"] == "True": clean_up_pcraster_maps = True # TODO: FIXME: This is NOT working yet. - if clean_up_pcraster_maps: - files_to_be_removed = glob.glob(str(self.configuration.main_output_directory) + "/M*/states/*" + str(self.modelTime.fulldate) + "*") - for f in files_to_be_removed: - print(f) - os.remove(f) - - - # monthly and annual merging - if self.modelTime.isLastDayOfYear(): - - # merging netcdf files at monthly resolutions - start_date = '%04i-01-31' %(self.modelTime.year) # TODO: Make it flexible for a run starting not on the 1st January. - self.merging_netcdf_files("outMonthTotNC", start_date, end_date) - self.merging_netcdf_files("outMonthAvgNC", start_date, end_date) - self.merging_netcdf_files("outMonthEndNC", start_date, end_date) - self.merging_netcdf_files("outMonthMaxNC", start_date, end_date) - - # merging netcdf files at annual resolutions - start_date = '%04i-12-31' %(self.modelTime.year) # TODO: Make it flexible for a run starting not on the 1st January. - end_date = self.modelTime.fulldate - self.merging_netcdf_files("outAnnuaTotNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaAvgNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaEndNC", start_date, end_date) - self.merging_netcdf_files("outAnnuaMaxNC", start_date, end_date) - - - # make an empty file indicating that merging process is done - if self.modelTime.isLastDayOfMonth() or self.modelTime.isLastDayOfYear(): - - outputDirectory = str(self.configuration.main_output_directory) + "/global/maps/" - filename = outputDirectory + "/merged_files_for_" + str(self.modelTime.fulldate)+"_are_ready.txt" - if os.path.exists(filename): os.remove(filename) - open(filename, "w").close() - - - def merging_netcdf_files(self, nc_report_type, start_date, end_date, max_number_of_cores = 20): - - if str(vars(self)[nc_report_type]) != "None": - - netcdf_files_that_will_be_merged = vars(self)[nc_report_type] - - msg = "Merging netcdf files for the files/variables: " + netcdf_files_that_will_be_merged - logger.info(msg) - - # ~ cmd = 'python '+ self.configuration.path_of_this_module + "/merge_netcdf.py " + str(self.configuration.main_output_directory) + " " +\ - # ~ str(self.configuration.main_output_directory) + "/global/netcdf/ "+\ - # ~ str(nc_report_type) + " " +\ - # ~ str(start_date) + " " +\ - # ~ str(end_date) + " " +\ - # ~ str(netcdf_files_that_will_be_merged) + " " +\ - # ~ str(self.netcdf_format) + " " +\ - # ~ str(self.zlib_option ) + " " +\ - # ~ str(max_number_of_cores) + " " +\ - # ~ str("Global") + " " - - # - for Ulysses: - # example: python3 merge_netcdf_6_arcmin_ulysses.py ${MAIN_OUTPUT_DIR} ${MAIN_OUTPUT_DIR}/global/netcdf outDailyTotNC ${STARTING_DATE} ${END_DATE} ulyssesQrRunoff,ulyssesDischarge NETCDF4 False 12 Global default_lats - cmd = 'python3 '+ self.configuration.path_of_this_module + "/merge_netcdf_6_arcmin_ulysses.py " + str(self.configuration.main_output_directory) + " " +\ - str(self.configuration.main_output_directory) + "/global/netcdf/ "+\ - str(nc_report_type) + " " +\ - str(start_date) + " " +\ - str(end_date) + " " +\ - str(netcdf_files_that_will_be_merged) + " " +\ - str(self.netcdf_format) + " " +\ - str(self.zlib_option ) + " " +\ - str(max_number_of_cores) + " " +\ - str("Global default_lats") + " " - - msg = "Using the following command line: " + cmd - logger.info(msg) - - vos.cmd_line(cmd, using_subprocess = False) - - def check_pcrglobwb_status(self): - - if self.configuration.globalOptions['cloneAreas'] == "Global" or \ - self.configuration.globalOptions['cloneAreas'] == "part_one": - clone_areas = ['M%02d'%i for i in range(1,53+1,1)] - elif self.configuration.globalOptions['cloneAreas'] == "GlobalUlysses": - clone_areas = ['M%07d'%i for i in range(1,71+1,1)] - else: - clone_areas = list(set(self.configuration.globalOptions['cloneAreas'].split(","))) - for clone_area in clone_areas: - status_file = str(self.configuration.main_output_directory) + "/" +str(clone_area) + "/maps/pcrglobwb_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: '+status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - - print(status) - - return status - -def main(): - - # print disclaimer - disclaimer.print_disclaimer() - - # get the full path of configuration/ini file given in the system argument - iniFileName = os.path.abspath(sys.argv[1]) - - # debug option - debug_mode = False - if len(sys.argv) > 2: - if sys.argv[2] == "debug" or sys.argv[2] == "debug_parallel": debug_mode = True - - # options to perform steady state calculation - steady_state_only = False - if len(sys.argv) > 3: - if sys.argv[3] == "steady-state-only": steady_state_only = True - - # object to handle configuration/ini file - configuration = Configuration(iniFileName = iniFileName, \ - debug_mode = debug_mode, \ - steady_state_only = steady_state_only) - - # timeStep info: year, month, day, doy, hour, etc - currTimeStep = ModelTime() - - # Running the deterministic_runner - currTimeStep.getStartEndTimeSteps(configuration.globalOptions['startTime'], - configuration.globalOptions['endTime']) - logger.info('Model run starts.') - deterministic_runner = DeterministicRunner(configuration, currTimeStep) - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - -if __name__ == '__main__': - # print disclaimer - disclaimer.print_disclaimer(with_logger = True) - sys.exit(main()) - diff --git a/model/etc/deterministic_runner_glue_with_parallel_and_modflow_options.py b/model/etc/deterministic_runner_glue_with_parallel_and_modflow_options.py deleted file mode 100644 index 14d6beaad..000000000 --- a/model/etc/deterministic_runner_glue_with_parallel_and_modflow_options.py +++ /dev/null @@ -1,411 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Ludovicus P. H. (Rens) van Beek, Edwin H. Sutanudjaja, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Inge E. M. de Graaf, Kor de Jong, Patricia Lopez Lopez, -# Stefanie Pessenteiner, Oliver Schmitz, Menno W. Straatsma, Niko Wanders, Dominik Wisser, -# and Marc F. P. Bierkens, -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import sys -import datetime - -import pcraster as pcr -from pcraster.framework import DynamicModel -from pcraster.framework import DynamicFramework - -from configuration import Configuration -from currTimeStep import ModelTime -from reporting import Reporting -from spinUp import SpinUp - -from pcrglobwb import PCRGlobWB - -import logging -logger = logging.getLogger(__name__) - -import disclaimer - -class DeterministicRunner(DynamicModel): - - def __init__(self, configuration, modelTime, initialState = None, system_argument = None): - DynamicModel.__init__(self) - - self.modelTime = modelTime - self.model = PCRGlobWB(configuration, modelTime, initialState) - self.reporting = Reporting(configuration, self.model, modelTime) - - # the model will set paramaters based on global pre-multipliers given in the argument: - if system_argument != None: self.adusting_parameters(configuration, system_argument) - - # option to include merging processes for pcraster maps and netcdf files: - self.with_merging = True - if ('with_merging' in configuration.globalOptions.keys()) and (configuration.globalOptions['with_merging'] == "False"): - self.with_merging = False - - # make the configuration available for the other method/function - self.configuration = configuration - - - def adusting_parameters(self, configuration, system_argument): - - # global pre-multipliers given in the argument: - if len(system_argument) > 4: - - logger.info("Adjusting some model parameters based on given values in the system argument.") - - # pre-multipliers for minSoilDepthFrac, kSat, recessionCoeff, storCap and degreeDayFactor - multiplier_for_minSoilDepthFrac = float(system_argument[4]) # linear scale # Note that this one does NOT work for the changing WMIN or Joyce land cover options. - multiplier_for_kSat = float(system_argument[5]) # log scale - multiplier_for_recessionCoeff = float(system_argument[6]) # log scale - multiplier_for_storCap = float(system_argument[7]) # linear scale - multiplier_for_degreeDayFactor = float(system_argument[8]) # linear scale - - # pre-multiplier for the reference potential ET - self.multiplier_for_refPotET = float(system_argument[9]) # linear scale - - # it is also possible to define prefactors via the ini/configuration file: - # - this will be overwrite any previous given pre-multipliers - if 'prefactorOptions' in configuration.allSections: - - logger.info("Adjusting some model parameters based on given values in the ini/configuration file.") - - self.multiplier_for_refPotET = float(configuration.prefactorOptions['linear_multiplier_for_refPotET' ]) # linear scale # Note that this one does NOT work for the changing WMIN or Joyce land cover options. - multiplier_for_degreeDayFactor = float(configuration.prefactorOptions['linear_multiplier_for_degreeDayFactor' ]) # linear scale - multiplier_for_minSoilDepthFrac = float(configuration.prefactorOptions['linear_multiplier_for_minSoilDepthFrac']) # linear scale - multiplier_for_kSat = float(configuration.prefactorOptions['log_10_multiplier_for_kSat' ]) # log scale - multiplier_for_storCap = float(configuration.prefactorOptions['linear_multiplier_for_storCap' ]) # linear scale - multiplier_for_recessionCoeff = float(configuration.prefactorOptions['log_10_multiplier_for_recessionCoeff' ]) # log scale - - # saving global pre-multipliers to the log file: - msg = "\n" - msg += "\n" - msg += "Multiplier values used: "+"\n" - msg += "For minSoilDepthFrac : "+str(multiplier_for_minSoilDepthFrac)+"\n" - msg += "For kSat (log-scale) : "+str(multiplier_for_kSat )+"\n" - msg += "For recessionCoeff (log-scale) : "+str(multiplier_for_recessionCoeff )+"\n" - msg += "For storCap : "+str(multiplier_for_storCap )+"\n" - msg += "For degreeDayFactor : "+str(multiplier_for_degreeDayFactor )+"\n" - msg += "For refPotET : "+str(self.multiplier_for_refPotET )+"\n" - logger.info(msg) - # - also to a txt file - f = open("multiplier.txt","w") # this will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - f.write(msg) - f.close() - - # set parameter "recessionCoeff" based on the given pre-multiplier - # - also saving the adjusted parameter maps to pcraster files - # - these will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - # "recessionCoeff" - # minimum value is zero and using log-scale - self.model.groundwater.recessionCoeff = pcr.max(0.0, (10**(multiplier_for_recessionCoeff)) * self.model.groundwater.recessionCoeff) - self.model.groundwater.recessionCoeff = pcr.min(1.0, self.model.groundwater.recessionCoeff) - # report the map - pcr.report(self.model.groundwater.recessionCoeff, "recessionCoeff.map") - - # set parameters "kSat", "storCap", "minSoilDepthFrac", and "degreeDayFactor" based on the given pre-multipliers - for coverType in self.model.landSurface.coverTypes: - - # "degreeDayFactor" - self.model.landSurface.landCoverObj[coverType].degreeDayFactor = pcr.max(0.0, multiplier_for_degreeDayFactor *\ - self.model.landSurface.landCoverObj[coverType].degreeDayFactor) - # report the map - pcraster_filename = "degreeDayFactor" + "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].degreeDayFactor , pcraster_filename) - - # "kSat" and "storCap" for 2 layer model - if self.model.landSurface.numberOfSoilLayers == 2: - - # "kSat" - # minimum value is zero and using-log-scale - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp) - self.model.landSurface.landCoverObj[coverType].parameters.kSatLow = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatLow) - # report the maps - pcraster_filename = "kSatUpp"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp, pcraster_filename) - pcraster_filename = "kSatLow"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatLow, pcraster_filename) - - # "storCap" - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp) - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow) - # report the maps - pcraster_filename = "storCapUpp"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp, pcraster_filename) - pcraster_filename = "storCapLow"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapLow, pcraster_filename) - - # "kSat" and "storCap" for 3 layer model - if self.model.landSurface.numberOfSoilLayers == 3: - - # "kSat" - # minimum value is zero and using-log-scale - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005) - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030) - self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150) - # report the maps - pcraster_filename = "kSatUpp000005"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005, pcraster_filename) - pcraster_filename = "kSatUpp005030"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030, pcraster_filename) - pcraster_filename = "kSatLow030150"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150, pcraster_filename) - - # "storCap" - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005) - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030) - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150) - # report the maps - pcraster_filename = "storCapUpp000005"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005, pcraster_filename) - pcraster_filename = "storCapUpp005030"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030, pcraster_filename) - pcraster_filename = "storCapLow030150"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150, pcraster_filename) - - - # re-calculate rootZoneWaterStorageCap as the consequence of the modification of "storCap" - # This is WMAX in the oldcalc script. - if self.model.landSurface.numberOfSoilLayers == 2: - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap = self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow - if self.model.landSurface.numberOfSoilLayers == 3: - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap = self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005 +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030 +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150 - # report the map - pcraster_filename = "rootZoneWaterStorageCap"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap, pcraster_filename) - - # "minSoilDepthFrac" - if multiplier_for_minSoilDepthFrac != 1.0: - - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.max(0.0, multiplier_for_minSoilDepthFrac*\ - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac) - # for minSoilDepthFrac - values will be limited by maxSoilDepthFrac - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.min(\ - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac,\ - self.model.landSurface.landCoverObj[coverType].maxSoilDepthFrac) - # maximum value is 1.0 - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.min(1.0, self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac) - # report the map - pcraster_filename = "minSoilDepthFrac"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac, pcraster_filename) - - # re-calculate arnoBeta (as the consequence of the modification of minSoilDepthFrac) - self.model.landSurface.landCoverObj[coverType].arnoBeta = pcr.max(0.001,\ - (self.model.landSurface.landCoverObj[coverType].maxSoilDepthFrac-1.)/(1.-self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac)+\ - self.model.landSurface.landCoverObj[coverType].parameters.orographyBeta-0.01) - self.model.landSurface.landCoverObj[coverType].arnoBeta = pcr.cover(pcr.max(0.001,\ - self.model.landSurface.landCoverObj[coverType].arnoBeta), 0.001) - # report the map - pcraster_filename = "arnoBeta"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].arnoBeta, pcraster_filename) - - # re-calculate rootZoneWaterStorageMin (as the consequence of the modification of minSoilDepthFrac) - # This is WMIN in the oldcalc script. - # WMIN (unit: m): minimum local soil water capacity within the grid-cell - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin = self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac *\ - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap - # report the map - pcraster_filename = "rootZoneWaterStorageMin"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin, pcraster_filename) - - # re-calculate rootZoneWaterStorageRange (as the consequence of the modification of rootZoneWaterStorageRange and minSoilDepthFrac) - # WMAX - WMIN (unit: m) - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageRange = self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap -\ - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin - # report the map - pcraster_filename = "rootZoneWaterStorageRange"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageRange, pcraster_filename) - - def initial(self): - pass - - def dynamic(self): - - # re-calculate current model time using current pcraster timestep value - self.modelTime.update(self.currentTimeStep()) - - # read model forcing (will pick up current model time from model time object) - self.model.read_forcings() - - # adjust the reference potential ET according to the given pre-multiplier - self.model.meteo.referencePotET = self.model.meteo.referencePotET * self.multiplier_for_refPotET - - # update model (will pick up current model time from model time object) - # - for a run coupled to MODFLOW, water balance checks are not valid due to lateral flow. - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model.update(report_water_balance = False) - else: - self.model.update(report_water_balance = True) - - # do any needed reporting for this time step - self.reporting.report() - - # at the last day of the month, stop calculation until modflow and related merging process are ready (only for a run with modflow) - if self.modelTime.isLastDayOfMonth() and (self.configuration.online_coupling_between_pcrglobwb_and_modflow or\ - self.with_merging): - - # wait until modflow files are ready - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - modflow_is_ready = False - self.count_check = 0 - while modflow_is_ready == False: - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 59:\ - modflow_is_ready = self.check_modflow_status() - - # wait until merged files are ready - merged_files_are_ready = False - while merged_files_are_ready == False: - self.count_check = 0 - if datetime.datetime.now().second == 14 or\ - datetime.datetime.now().second == 29 or\ - datetime.datetime.now().second == 34 or\ - datetime.datetime.now().second == 59:\ - merged_files_are_ready = self.check_merging_status() - - def check_modflow_status(self): - - status_file = str(self.configuration.main_output_directory) + "/modflow/transient/maps/modflow_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: ' + status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - return status - - def check_merging_status(self): - - status_file = str(self.configuration.main_output_directory) + "/global/maps/merged_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: ' + status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - return status - - -def main(): - - # get the full path of configuration/ini file given in the system argument - iniFileName = os.path.abspath(sys.argv[1]) - - # debug option - debug_mode = False - if len(sys.argv) > 2: - if sys.argv[2] == "debug" or sys.argv[2] == "debug_parallel" or sys.argv[2] == "debug-parallel": debug_mode = True - - # object to handle configuration/ini file - configuration = Configuration(iniFileName = iniFileName, \ - debug_mode = debug_mode, \ - no_modification = False) - - # parallel option - this_run_is_part_of_a_set_of_parallel_run = False - if len(sys.argv) > 2: - if sys.argv[2] == "parallel" or sys.argv[2] == "debug_parallel" or sys.argv[2] == "debug-parallel": this_run_is_part_of_a_set_of_parallel_run = True - - # for a non parallel run (usually 30min), a specific directory given in the system argument (sys.argv[3]) will be assigned for a given parameter combination: - if this_run_is_part_of_a_set_of_parallel_run == False: - # modfiying 'outputDir' (based on the given system argument) - configuration.globalOptions['outputDir'] += "/"+str(sys.argv[3])+"/" - - # for a parallel run (usually 5min), we assign a specific directory based on the clone number/code: - if this_run_is_part_of_a_set_of_parallel_run: - # modfiying outputDir, clone-map and landmask (based on the given system arguments) - clone_code = str(sys.argv[3]) - configuration.globalOptions['outputDir'] += "/"+clone_code+"/" - configuration.globalOptions['cloneMap'] = configuration.globalOptions['cloneMap'] %(clone_code) - configuration.globalOptions['landmask'] = configuration.globalOptions['landmask'] %(clone_code) - - # set configuration - configuration.set_configuration(system_arguments = sys.argv) - - # timeStep info: year, month, day, doy, hour, etc - currTimeStep = ModelTime() - - # object for spin_up - spin_up = SpinUp(configuration) - - # spinning-up - noSpinUps = int(configuration.globalOptions['maxSpinUpsInYears']) - initial_state = None - if noSpinUps > 0: - - logger.info('Spin-Up #Total Years: '+str(noSpinUps)) - - spinUpRun = 0 ; has_converged = False - while spinUpRun < noSpinUps and has_converged == False: - spinUpRun += 1 - currTimeStep.getStartEndTimeStepsForSpinUp( - configuration.globalOptions['startTime'], - spinUpRun, noSpinUps) - logger.info('Spin-Up Run No. '+str(spinUpRun)) - deterministic_runner = DeterministicRunner(configuration, currTimeStep, initial_state, sys.argv) - - all_state_begin = deterministic_runner.model.getAllState() - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - - all_state_end = deterministic_runner.model.getAllState() - - has_converged = spin_up.checkConvergence(all_state_begin, all_state_end, spinUpRun, deterministic_runner.model.routing.cellArea) - - initial_state = deterministic_runner.model.getState() - # - # Running the deterministic_runner (excluding DA scheme) - currTimeStep.getStartEndTimeSteps(configuration.globalOptions['startTime'], - configuration.globalOptions['endTime']) - - logger.info('Transient simulation run started.') - deterministic_runner = DeterministicRunner(configuration, currTimeStep, initial_state, sys.argv) - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - -if __name__ == '__main__': - # print disclaimer - disclaimer.print_disclaimer(with_logger = True) - sys.exit(main()) diff --git a/model/etc/deterministic_runner_glue_with_parallel_and_modflow_options_for_jessica.py b/model/etc/deterministic_runner_glue_with_parallel_and_modflow_options_for_jessica.py deleted file mode 100644 index eb9005d5b..000000000 --- a/model/etc/deterministic_runner_glue_with_parallel_and_modflow_options_for_jessica.py +++ /dev/null @@ -1,436 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Ludovicus P. H. (Rens) van Beek, Edwin H. Sutanudjaja, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Inge E. M. de Graaf, Kor de Jong, Patricia Lopez Lopez, -# Stefanie Pessenteiner, Oliver Schmitz, Menno W. Straatsma, Niko Wanders, Dominik Wisser, -# and Marc F. P. Bierkens, -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . - -import os -import sys -import datetime - -import pcraster as pcr -from pcraster.framework import DynamicModel -from pcraster.framework import DynamicFramework - -from configuration import Configuration -from currTimeStep import ModelTime -from reporting import Reporting -from spinUp import SpinUp - -from pcrglobwb import PCRGlobWB - -import logging -logger = logging.getLogger(__name__) - -import disclaimer - -class DeterministicRunner(DynamicModel): - - def __init__(self, configuration, modelTime, initialState = None, system_argument = None): - DynamicModel.__init__(self) - - self.modelTime = modelTime - self.model = PCRGlobWB(configuration, modelTime, initialState) - self.reporting = Reporting(configuration, self.model, modelTime) - - # the model will set paramaters based on global pre-multipliers given in the argument: - if system_argument != None: self.adusting_parameters(configuration, system_argument) - - # option to include merging processes for pcraster maps and netcdf files: - self.with_merging = True - if ('with_merging' in configuration.globalOptions.keys()) and (configuration.globalOptions['with_merging'] == "False"): - self.with_merging = False - - # make the configuration available for the other method/function - self.configuration = configuration - - - def adusting_parameters(self, configuration, system_argument): - - # global pre-multipliers given in the argument: - if len(system_argument) > 4: - - logger.info("Adjusting some model parameters based on given values in the system argument.") - - # pre-multipliers for minSoilDepthFrac, kSat, recessionCoeff, storCap and degreeDayFactor - multiplier_for_minSoilDepthFrac = float(system_argument[4]) # linear scale # Note that this one does NOT work for the changing WMIN or Joyce land cover options. - multiplier_for_kSat = float(system_argument[5]) # log scale - multiplier_for_recessionCoeff = float(system_argument[6]) # log scale - multiplier_for_storCap = float(system_argument[7]) # linear scale - multiplier_for_degreeDayFactor = float(system_argument[8]) # linear scale - - # pre-multiplier for the reference potential ET - self.multiplier_for_refPotET = float(system_argument[9]) # linear scale - - # pre-multiplier for the reference potential ET - multiplier_for_manningsN = float(system_argument[10]) # linear scale - - # modfication for storGroundwaterIni - storGroundwaterIni_file = str(system_argument[11]) - - - # it is also possible to define prefactors via the ini/configuration file: - # - this will be overwrite any previous given pre-multipliers - if 'prefactorOptions' in configuration.allSections: - - logger.info("Adjusting some model parameters based on given values in the ini/configuration file.") - - self.multiplier_for_refPotET = float(configuration.prefactorOptions['linear_multiplier_for_refPotET' ]) # linear scale # Note that this one does NOT work for the changing WMIN or Joyce land cover options. - multiplier_for_degreeDayFactor = float(configuration.prefactorOptions['linear_multiplier_for_degreeDayFactor' ]) # linear scale - multiplier_for_minSoilDepthFrac = float(configuration.prefactorOptions['linear_multiplier_for_minSoilDepthFrac']) # linear scale - multiplier_for_kSat = float(configuration.prefactorOptions['log_10_multiplier_for_kSat' ]) # log scale - multiplier_for_storCap = float(configuration.prefactorOptions['linear_multiplier_for_storCap' ]) # linear scale - multiplier_for_recessionCoeff = float(configuration.prefactorOptions['log_10_multiplier_for_recessionCoeff' ]) # log scale - multiplier_for_manningsN = float(configuration.prefactorOptions['multiplier_for_manningsN' ]) # linear scale - - storGroundwaterIni_file = str(configuration.prefactorOptions['storGroundwaterIni_file']) # file location (please use full path) - - # saving global pre-multipliers to the log file: - msg = "\n" - msg += "\n" - msg += "Multiplier values used: "+"\n" - msg += "For minSoilDepthFrac : "+str(multiplier_for_minSoilDepthFrac)+"\n" - msg += "For kSat (log-scale) : "+str(multiplier_for_kSat )+"\n" - msg += "For recessionCoeff (log-scale) : "+str(multiplier_for_recessionCoeff )+"\n" - msg += "For storCap : "+str(multiplier_for_storCap )+"\n" - msg += "For degreeDayFactor : "+str(multiplier_for_degreeDayFactor )+"\n" - msg += "For refPotET : "+str(self.multiplier_for_refPotET )+"\n" - msg += "For multiplier_for_manningsN : "+str(multiplier_for_manningsN )+"\n" - msg += "For storGroundwaterIni_file : "+str(storGroundwaterIni_file )+"\n" - logger.info(msg) - # - also to a txt file - f = open("multiplier.txt","w") # this will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - f.write(msg) - f.close() - - # adjust storGroundwaterIni - if storGroundwaterIni_file != "Default": self.model.groundwater.storGroundwater = pcr.readmap(storGroundwaterIni_file) - pcr.report(self.model.groundwater.storGroundwater, "storGroundwaterIni.map") - - # set parameter "manningsN" based on the given pre-multiplier - # - also saving the adjusted parameter maps to pcraster files - # - these will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - # "manningsN" - # minimum value is zero and using log-scale - self.model.routing.manningsN = multiplier_for_manningsN * self.model.routing.manningsN - # report the map - pcr.report(self.model.routing.manningsN, "manningsN.map") - - # set parameter "recessionCoeff" based on the given pre-multiplier - # - also saving the adjusted parameter maps to pcraster files - # - these will be stored in the "map" folder of the 'outputDir' (as we set the current working directory to this "map" folder, see configuration.py) - # "recessionCoeff" - # minimum value is zero and using log-scale - self.model.groundwater.recessionCoeff = pcr.max(0.0, (10**(multiplier_for_recessionCoeff)) * self.model.groundwater.recessionCoeff) - self.model.groundwater.recessionCoeff = pcr.min(1.0, self.model.groundwater.recessionCoeff) - # report the map - pcr.report(self.model.groundwater.recessionCoeff, "recessionCoeff.map") - - # set parameters "kSat", "storCap", "minSoilDepthFrac", and "degreeDayFactor" based on the given pre-multipliers - for coverType in self.model.landSurface.coverTypes: - - # "degreeDayFactor" - self.model.landSurface.landCoverObj[coverType].degreeDayFactor = pcr.max(0.0, multiplier_for_degreeDayFactor *\ - self.model.landSurface.landCoverObj[coverType].degreeDayFactor) - # report the map - pcraster_filename = "degreeDayFactor" + "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].degreeDayFactor , pcraster_filename) - - # "kSat" and "storCap" for 2 layer model - if self.model.landSurface.numberOfSoilLayers == 2: - - # "kSat" - # minimum value is zero and using-log-scale - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp) - self.model.landSurface.landCoverObj[coverType].parameters.kSatLow = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatLow) - # report the maps - pcraster_filename = "kSatUpp"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp, pcraster_filename) - pcraster_filename = "kSatLow"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatLow, pcraster_filename) - - # "storCap" - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp) - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow) - # report the maps - pcraster_filename = "storCapUpp"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp, pcraster_filename) - pcraster_filename = "storCapLow"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapLow, pcraster_filename) - - # "kSat" and "storCap" for 3 layer model - if self.model.landSurface.numberOfSoilLayers == 3: - - # "kSat" - # minimum value is zero and using-log-scale - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005) - self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030) - self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150 = \ - pcr.max(0.0, (10**(multiplier_for_kSat)) * self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150) - # report the maps - pcraster_filename = "kSatUpp000005"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp000005, pcraster_filename) - pcraster_filename = "kSatUpp005030"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatUpp005030, pcraster_filename) - pcraster_filename = "kSatLow030150"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.kSatLow030150, pcraster_filename) - - # "storCap" - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005) - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030) - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150 = pcr.max(0.0, multiplier_for_storCap*\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150) - # report the maps - pcraster_filename = "storCapUpp000005"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005, pcraster_filename) - pcraster_filename = "storCapUpp005030"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030, pcraster_filename) - pcraster_filename = "storCapLow030150"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150, pcraster_filename) - - - # re-calculate rootZoneWaterStorageCap as the consequence of the modification of "storCap" - # This is WMAX in the oldcalc script. - if self.model.landSurface.numberOfSoilLayers == 2: - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap = self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow - if self.model.landSurface.numberOfSoilLayers == 3: - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap = self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp000005 +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapUpp005030 +\ - self.model.landSurface.landCoverObj[coverType].parameters.storCapLow030150 - # report the map - pcraster_filename = "rootZoneWaterStorageCap"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap, pcraster_filename) - - # "minSoilDepthFrac" - if multiplier_for_minSoilDepthFrac != 1.0: - - # minimum value is zero - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.max(0.0, multiplier_for_minSoilDepthFrac*\ - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac) - # for minSoilDepthFrac - values will be limited by maxSoilDepthFrac - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.min(\ - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac,\ - self.model.landSurface.landCoverObj[coverType].maxSoilDepthFrac) - # maximum value is 1.0 - self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac = pcr.min(1.0, self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac) - # report the map - pcraster_filename = "minSoilDepthFrac"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac, pcraster_filename) - - # re-calculate arnoBeta (as the consequence of the modification of minSoilDepthFrac) - self.model.landSurface.landCoverObj[coverType].arnoBeta = pcr.max(0.001,\ - (self.model.landSurface.landCoverObj[coverType].maxSoilDepthFrac-1.)/(1.-self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac)+\ - self.model.landSurface.landCoverObj[coverType].parameters.orographyBeta-0.01) - self.model.landSurface.landCoverObj[coverType].arnoBeta = pcr.cover(pcr.max(0.001,\ - self.model.landSurface.landCoverObj[coverType].arnoBeta), 0.001) - # report the map - pcraster_filename = "arnoBeta"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].arnoBeta, pcraster_filename) - - # re-calculate rootZoneWaterStorageMin (as the consequence of the modification of minSoilDepthFrac) - # This is WMIN in the oldcalc script. - # WMIN (unit: m): minimum local soil water capacity within the grid-cell - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin = self.model.landSurface.landCoverObj[coverType].minSoilDepthFrac *\ - self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap - # report the map - pcraster_filename = "rootZoneWaterStorageMin"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin, pcraster_filename) - - # re-calculate rootZoneWaterStorageRange (as the consequence of the modification of rootZoneWaterStorageRange and minSoilDepthFrac) - # WMAX - WMIN (unit: m) - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageRange = self.model.landSurface.landCoverObj[coverType].parameters.rootZoneWaterStorageCap -\ - self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageMin - # report the map - pcraster_filename = "rootZoneWaterStorageRange"+ "_" + coverType + ".map" - pcr.report(self.model.landSurface.landCoverObj[coverType].rootZoneWaterStorageRange, pcraster_filename) - - def initial(self): - pass - - def dynamic(self): - - # re-calculate current model time using current pcraster timestep value - self.modelTime.update(self.currentTimeStep()) - - # read model forcing (will pick up current model time from model time object) - self.model.read_forcings() - - # adjust the reference potential ET according to the given pre-multiplier - self.model.meteo.referencePotET = self.model.meteo.referencePotET * self.multiplier_for_refPotET - - # update model (will pick up current model time from model time object) - # - for a run coupled to MODFLOW, water balance checks are not valid due to lateral flow. - if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - self.model.update(report_water_balance = False) - else: - self.model.update(report_water_balance = True) - - # do any needed reporting for this time step - self.reporting.report() - - #~ # at the last day of the month, stop calculation until modflow and related merging process are ready (only for a run with modflow) - #~ if self.modelTime.isLastDayOfMonth() and (self.configuration.online_coupling_between_pcrglobwb_and_modflow or\ - #~ self.with_merging): - - # wait until modflow files are ready - #~ if self.configuration.online_coupling_between_pcrglobwb_and_modflow: - #~ modflow_is_ready = False - #~ self.count_check = 0 - #~ while modflow_is_ready == False: - #~ if datetime.datetime.now().second == 14 or\ - #~ datetime.datetime.now().second == 29 or\ - #~ datetime.datetime.now().second == 34 or\ - #~ datetime.datetime.now().second == 59:\ - #~ modflow_is_ready = self.check_modflow_status() - - # wait until merged files are ready - #~ merged_files_are_ready = False - #~ while merged_files_are_ready == False: - #~ self.count_check = 0 - #~ if datetime.datetime.now().second == 14 or\ - #~ datetime.datetime.now().second == 29 or\ - #~ datetime.datetime.now().second == 34 or\ - #~ datetime.datetime.now().second == 59:\ - #~ merged_files_are_ready = self.check_merging_status() - - def check_modflow_status(self): - - status_file = str(self.configuration.main_output_directory) + "/modflow/transient/maps/modflow_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: ' + status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - return status - - def check_merging_status(self): - - status_file = str(self.configuration.main_output_directory) + "/global/maps/merged_files_for_" + str(self.modelTime.fulldate) + "_are_ready.txt" - msg = 'Waiting for the file: ' + status_file - if self.count_check == 1: logger.info(msg) - if self.count_check < 7: - #~ logger.debug(msg) # INACTIVATE THIS AS THIS MAKE A HUGE DEBUG (dbg) FILE - self.count_check += 1 - status = os.path.exists(status_file) - if status == False: return status - if status: self.count_check = 0 - return status - - -def main(): - - # get the full path of configuration/ini file given in the system argument - iniFileName = os.path.abspath(sys.argv[1]) - - # debug option - debug_mode = False - if len(sys.argv) > 2: - if sys.argv[2] == "debug" or sys.argv[2] == "debug_parallel" or sys.argv[2] == "debug-parallel": debug_mode = True - - # object to handle configuration/ini file - configuration = Configuration(iniFileName = iniFileName, \ - debug_mode = debug_mode, \ - no_modification = False) - - # parallel option - this_run_is_part_of_a_set_of_parallel_run = False - if len(sys.argv) > 2: - if sys.argv[2] == "parallel" or sys.argv[2] == "debug_parallel" or sys.argv[2] == "debug-parallel": this_run_is_part_of_a_set_of_parallel_run = True - - # for a non parallel run (usually 30min), a specific directory given in the system argument (sys.argv[3]) will be assigned for a given parameter combination: - if this_run_is_part_of_a_set_of_parallel_run == False: - # modfiying 'outputDir' (based on the given system argument) - configuration.globalOptions['outputDir'] += "/"+str(sys.argv[3])+"/" - - # for a parallel run (usually 5min), we assign a specific directory based on the clone number/code: - if this_run_is_part_of_a_set_of_parallel_run: - # modfiying outputDir, clone-map and landmask (based on the given system arguments) - clone_code = str(sys.argv[3]) - configuration.globalOptions['outputDir'] += "/"+clone_code+"/" - configuration.globalOptions['cloneMap'] = configuration.globalOptions['cloneMap'] %(clone_code) - configuration.globalOptions['landmask'] = configuration.globalOptions['landmask'] %(clone_code) - - # set configuration - configuration.set_configuration(system_arguments = sys.argv) - - # timeStep info: year, month, day, doy, hour, etc - currTimeStep = ModelTime() - - # object for spin_up - spin_up = SpinUp(configuration) - - # spinning-up - noSpinUps = int(configuration.globalOptions['maxSpinUpsInYears']) - initial_state = None - if noSpinUps > 0: - - logger.info('Spin-Up #Total Years: '+str(noSpinUps)) - - spinUpRun = 0 ; has_converged = False - while spinUpRun < noSpinUps and has_converged == False: - spinUpRun += 1 - currTimeStep.getStartEndTimeStepsForSpinUp( - configuration.globalOptions['startTime'], - spinUpRun, noSpinUps) - logger.info('Spin-Up Run No. '+str(spinUpRun)) - deterministic_runner = DeterministicRunner(configuration, currTimeStep, initial_state, sys.argv) - - all_state_begin = deterministic_runner.model.getAllState() - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - - all_state_end = deterministic_runner.model.getAllState() - - has_converged = spin_up.checkConvergence(all_state_begin, all_state_end, spinUpRun, deterministic_runner.model.routing.cellArea) - - initial_state = deterministic_runner.model.getState() - # - # Running the deterministic_runner (excluding DA scheme) - currTimeStep.getStartEndTimeSteps(configuration.globalOptions['startTime'], - configuration.globalOptions['endTime']) - - logger.info('Transient simulation run started.') - deterministic_runner = DeterministicRunner(configuration, currTimeStep, initial_state, sys.argv) - - dynamic_framework = DynamicFramework(deterministic_runner,currTimeStep.nrOfTimeSteps) - dynamic_framework.setQuiet(True) - dynamic_framework.run() - -if __name__ == '__main__': - # print disclaimer - disclaimer.print_disclaimer(with_logger = True) - sys.exit(main()) diff --git a/model/landCover.py b/model/landCover.py index 0e01b3ba4..57b00cc49 100644 --- a/model/landCover.py +++ b/model/landCover.py @@ -34,8 +34,6 @@ import virtualOS as vos from ncConverter import * -import surfaceWaterQuality as swq - class LandCover(object): def __init__(self,iniItems,nameOfSectionInIniFile,soil_and_topo_parameters,landmask,irrigationEfficiency,usingAllocSegments = False): @@ -931,21 +929,15 @@ def getICsLC(self,iniItems,iniConditions = None): vars(self)[var] = iniConditions[str(var)] vars(self)[var] = pcr.ifthen(self.landmask,vars(self)[var]) - def updateLC(self,meteo,groundwater,routing, - capRiseFrac, - nonIrrGrossDemandDict,swAbstractionFractionDict, - currTimeStep, - allocSegments, - desalinationWaterUse, - groundwater_pumping_region_ids, - regionalAnnualGroundwaterAbstractionLimit, - wq_state, wq_threshold, consider_water_quality=False): - - # assiging water quality input information - self.consider_water_quality = consider_water_quality - self.wq_state = wq_state - self.wq_threshold = wq_threshold - + def updateLC(self,meteo,groundwater,routing,\ + capRiseFrac,\ + nonIrrGrossDemandDict,swAbstractionFractionDict,\ + currTimeStep,\ + allocSegments,\ + desalinationWaterUse,\ + groundwater_pumping_region_ids,\ + regionalAnnualGroundwaterAbstractionLimit): + # get land cover parameters at the first day of the year or the first day of the simulation if self.noAnnualChangesInLandCoverParameter == False and\ (currTimeStep.timeStepPCR == 1 or currTimeStep.doy == 1): @@ -1743,108 +1735,103 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ # the following value will be reduced by available/accesible water self.totalPotentialGrossDemand = self.totalPotentialMaximumGrossDemand - - ################################################################################################################################### # Abstraction and Allocation of DESALINATED WATER - ################################################################################################################################### - # Desalination water abstraction and allocation.................................................................................... + # ################################################################################################################## + # - desalination water to satisfy water demand if self.usingAllocSegments: # using zone/segments at which networks are defined (as defined in the landSurface options) + # logger.debug("Allocation of supply from desalination water.") - + # volDesalinationAbstraction, volDesalinationAllocation = \ vos.waterAbstractionAndAllocation( - water_demand_volume = self.totalPotentialGrossDemand*routing.cellArea, - available_water_volume = pcr.max(0.00, desalinationWaterUse*routing.cellArea), - allocation_zones = allocSegments, - zone_area = self.segmentArea, - high_volume_treshold = None, - debug_water_balance = True, - extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), - landmask = self.landmask, - ignore_small_values = False, - prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) - + water_demand_volume = self.totalPotentialGrossDemand*routing.cellArea,\ + available_water_volume = pcr.max(0.00, desalinationWaterUse*routing.cellArea),\ + allocation_zones = allocSegments,\ + zone_area = self.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + # self.desalinationAbstraction = volDesalinationAbstraction / routing.cellArea self.desalinationAllocation = volDesalinationAllocation / routing.cellArea - - else: + # + else: + # logger.debug("Supply from desalination water is only for satisfying local demand (no network).") - self.desalinationAbstraction = pcr.min(desalinationWaterUse, self.totalPotentialGrossDemand) self.desalinationAllocation = self.desalinationAbstraction - + # self.desalinationAbstraction = pcr.ifthen(self.landmask, self.desalinationAbstraction) self.desalinationAllocation = pcr.ifthen(self.landmask, self.desalinationAllocation) + # ################################################################################################################## + # - end of Abstraction and Allocation of DESALINATED WATER - # Satisfied water demand (unit: m/day) - after desalination........................................................................ - # - irrigation (excluding livestock) + + # water demand that have been satisfied (unit: m/day) - after desalination + ################################################################################################################################ + # - for irrigation (excluding livestock) satisfiedIrrigationDemand = vos.getValDivZero(self.irrGrossDemand, self.totalPotentialGrossDemand) * self.desalinationAllocation - # - non-irrigation: domestic, industry and livestock + # - for domestic, industry and livestock satisfiedNonIrrDemand = pcr.max(0.00, self.desalinationAllocation - satisfiedIrrigationDemand) - # - domestic + # - for domestic satisfiedDomesticDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['domestic'], self.totalPotentialMaximumNonIrrGrossDemand) - # - industry + # - for industry satisfiedIndustryDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['industry'], self.totalPotentialMaximumNonIrrGrossDemand) - # - livestock + # - for livestock satisfiedLivestockDemand = pcr.max(0.0, satisfiedNonIrrDemand - satisfiedDomesticDemand - satisfiedIndustryDemand) - # - water abstraction by source - self.waterAbstractionSource = {} - self.waterAbstractionSource['desalination'] = {} - self.waterAbstractionSource['desalination']['irrigation'] = satisfiedNonIrrDemand - self.waterAbstractionSource['desalination']['domestic'] = satisfiedDomesticDemand - self.waterAbstractionSource['desalination']['industrial'] = satisfiedIndustryDemand - self.waterAbstractionSource['desalination']['livestock'] = satisfiedLivestockDemand - - # Total remaining GROSS demand (m/day) - after desalination........................................................................ + + + # total remaining gross demand (m/day) after desalination + ################################################################################################################################ self.totalGrossDemandAfterDesalination = pcr.max(0.0, self.totalPotentialGrossDemand - self.desalinationAllocation) # the remaining water demand per sector - # - domestic + # - for domestic remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) - # - industry + # - for industry remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) - # - livestock + # - for livestock remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) - # - irrigation (excluding livestock) - remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) # - total for livestock and irrigation remainingIrrigationLivestock = remainingIrrigation + remainingLivestock # - total for industrial and domestic (excluding livestock) - remainingIndustrialDomestic = pcr.max(0.0, self.totalGrossDemandAfterDesalination - remainingIrrigationLivestock) + remainingIndustrialDomestic = pcr.max(0.0, self.totalGrossDemandAfterDesalination - remainingIrrigationLivestock) - ################################################################################################################################### # Abstraction and Allocation of SURFACE WATER - ################################################################################################################################### - # Estimates of surface water demand (considering by swAbstractionFractionDict) .................................................... - # - industrial and domestic + ############################################################################################################################## + # calculate the estimate of surface water demand (considering by swAbstractionFractionDict) + # - for industrial and domestic swAbstractionFraction_industrial_domestic = pcr.min(swAbstractionFractionDict['max_for_non_irrigation'],\ swAbstractionFractionDict['estimate']) if swAbstractionFractionDict['non_irrigation'] is not None: swAbstractionFraction_industrial_domestic = swAbstractionFractionDict['non_irrigation'] - + surface_water_demand_estimate = swAbstractionFraction_industrial_domestic * remainingIndustrialDomestic - # - irrigation and livestock + # - for irrigation and livestock surface_water_irrigation_demand_estimate = swAbstractionFractionDict['irrigation'] * remainingIrrigationLivestock - - # - surface water source as priority if groundwater irrigation fraction is relatively low + # - surface water source as priority if groundwater irrigation fraction is relatively low surface_water_irrigation_demand_estimate = \ pcr.ifthenelse(swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_maximize_irrigation_surface_water'],\ remainingIrrigationLivestock, surface_water_irrigation_demand_estimate) - # - update estimate of surface water demand withdrawal (unit: m/day) surface_water_demand_estimate += surface_water_irrigation_demand_estimate # - prioritize surface water use in non productive aquifers that have limited groundwater supply surface_water_demand_estimate = pcr.ifthenelse(groundwater.productive_aquifer, surface_water_demand_estimate,\ pcr.max(0.0, remainingIrrigationLivestock - \ pcr.min(groundwater.avgAllocationShort, groundwater.avgAllocation))) - # - maximize/optimize surface water use in areas with the overestimation of groundwater supply + # - maximize/optimize surface water use in areas with the overestimation of groundwater supply surface_water_demand_estimate += pcr.max(0.0, pcr.max(groundwater.avgAllocationShort, groundwater.avgAllocation) -\ (1.0 - swAbstractionFractionDict['irrigation']) * totalIrrigationLivestockDemand -\ (1.0 - swAbstractionFraction_industrial_domestic) * (self.totalPotentialMaximumGrossDemand - totalIrrigationLivestockDemand)) - - # total demand (unit: m/day) that should be allocated from surface water + # + # total demand (unit: m/day) that should be allocated from surface water # (corrected/limited by swAbstractionFractionDict and limited by the remaining demand) surface_water_demand_estimate = pcr.min(self.totalGrossDemandAfterDesalination, surface_water_demand_estimate) correctedRemainingIrrigationLivestock = pcr.min(surface_water_demand_estimate, remainingIrrigationLivestock) @@ -1852,111 +1839,67 @@ def calculateWaterDemand(self, nonIrrGrossDemandDict, \ pcr.max(0.0, surface_water_demand_estimate - remainingIrrigationLivestock)) correctedSurfaceWaterDemandEstimate = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic surface_water_demand = correctedSurfaceWaterDemandEstimate - # Note the variable "surface_water_demand" is the estimate of surface water demand for all sectors (total) based on Siebert et al.; McDonald et al.; de Graaf et al. - # available_surface_water (without considering quality) - available_surface_water = pcr.max(0.00, routing.readAvlChannelStorage) + # + # if surface water abstraction as the first priority + if self.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination + # + if self.usingAllocSegments: # using zone/segment at which supply network is defined + # + logger.debug("Allocation of surface water abstraction.") + # + volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = surface_water_demand*routing.cellArea,\ + available_water_volume = pcr.max(0.00, routing.readAvlChannelStorage),\ + allocation_zones = allocSegments,\ + zone_area = self.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + + self.actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea + self.allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea + # + else: + logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") + self.actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ + surface_water_demand) # unit: m + self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m + # + self.actSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.actSurfaceWaterAbstract) + self.allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.allocSurfaceWaterAbstract) + ################################################################################################################################ + # - end of Abstraction and Allocation of SURFACE WATER - # Water quality evaluation ......................................................................................... - if self.consider_water_quality: - logger.info("Surface water allocation to meet demand will consider water quality.") - - # Estimates of surface water demand for every sector, after the above schemes (Siebert et al.; McDonald et al.; de Graaf et al.) - sectoral_surface_water_demand = {} - sectoral_surface_water_demand["irrigation"] = correctedRemainingIrrigationLivestock * vos.getValDivZero(remainingIrrigation, remainingLivestock + remainingIrrigation) - sectoral_surface_water_demand["livestock"] = pcr.max(0.0, correctedRemainingIrrigationLivestock - sectoral_surface_water_demand["irrigation"]) - sectoral_surface_water_demand["industrial"] = correctedRemainingIndustrialDomestic * vos.getValDivZero(remainingIndustry, remainingIndustry + remainingDomestic) - sectoral_surface_water_demand["domestic"] = pcr.max(0.0, correctedRemainingIndustrialDomestic - sectoral_surface_water_demand["industrial"]) - - # Water quality evaluation and surface water allocation - totalActSurfaceWaterAbstract, sectoral_surface_water_demand_satisfied = \ - swq.surface_water_allocation_based_on_quality(available_surface_water, sectoral_surface_water_demand, - self.wq_state, self.wq_threshold, self.surfaceWaterPiority, self.usingAllocSegments, self.allocSegments, - routing.cellArea, self.segmentArea, self.landmask, self.prioritizeLocalSourceToMeetWaterDemand, currTimeStep) - - # Tracking water demand that have been satisfied (unit: m/day) - # - for irrigation and livestock water demand - satisfiedIrrigationLivestockDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["irrigation"] + sectoral_surface_water_demand_satisfied["livestock"] - # - for irrigation water demand, but not including livestock - satisfiedIrrigationDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["irrigation"] - satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater - # - for non irrigation water demand: livestock, domestic and industry - satisfiedNonIrrDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] + sectoral_surface_water_demand_satisfied["livestock"] - satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater - # - for livestock - satisfiedLivestockDemand += sectoral_surface_water_demand_satisfied["livestock"] - # - for industrial and domestic demand (excluding livestock) - satisfiedIndustrialDomesticDemandFromSurfaceWater = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] - # - for domestic - satisfiedDomesticDemand += sectoral_surface_water_demand_satisfied["domestic"] - # - for industry - satisfiedIndustryDemand += sectoral_surface_water_demand_satisfied["industrial"] - - self.actSurfaceWaterAbstract = totalActSurfaceWaterAbstract - self.allocSurfaceWaterAbstract = sectoral_surface_water_demand_satisfied["domestic"] + sectoral_surface_water_demand_satisfied["industrial"] + sectoral_surface_water_demand_satisfied["livestock"] + sectoral_surface_water_demand_satisfied["irrigation"] - # End of Water quality evaluation .................................................................................. - # - # Conventional water allocation scheme ............................................................................. - else: - # if surface water abstraction as the first priority (False by default) - if self.surfaceWaterPiority: - surface_water_demand = self.totalGrossDemandAfterDesalination - - if self.usingAllocSegments: # using zone/segment at which supply network is defined - logger.debug("Allocation of surface water abstraction.") - # - volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ - vos.waterAbstractionAndAllocation( - water_demand_volume = surface_water_demand*routing.cellArea,\ - available_water_volume = available_surface_water,\ - allocation_zones = allocSegments,\ - zone_area = self.segmentArea,\ - high_volume_treshold = None,\ - debug_water_balance = True,\ - extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), - landmask = self.landmask, - ignore_small_values = False, - prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) - - self.actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea - self.allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea - # - else: - logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") - self.actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ - surface_water_demand) # unit: m - self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m - # - self.actSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.actSurfaceWaterAbstract) - self.allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.allocSurfaceWaterAbstract) - ################################################################################################################################ - # - end of Abstraction and Allocation of SURFACE WATER - - - # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply - ################################################################################################################################ - # - for irrigation and livestock water demand - satisfiedIrrigationLivestockDemandFromSurfaceWater = self.allocSurfaceWaterAbstract * \ - vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedSurfaceWaterDemandEstimate) - # - for irrigation water demand, but not including livestock - satisfiedIrrigationDemandFromSurfaceWater = satisfiedIrrigationLivestockDemandFromSurfaceWater * \ - vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) - satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater - # - for non irrigation water demand: livestock, domestic and industry - satisfiedNonIrrDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract - satisfiedIrrigationDemandFromSurfaceWater) - satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater - # - for livestock - satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromSurfaceWater - \ - satisfiedIrrigationDemandFromSurfaceWater) - # - for industrial and domestic demand (excluding livestock) - satisfiedIndustrialDomesticDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract -\ - satisfiedIrrigationLivestockDemandFromSurfaceWater) - # - for domestic - satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingDomestic, \ - remainingIndustrialDomestic) - # - for industry - satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingIndustry, \ - remainingIndustrialDomestic) + # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply + ################################################################################################################################ + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromSurfaceWater = self.allocSurfaceWaterAbstract * \ + vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedSurfaceWaterDemandEstimate) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromSurfaceWater = satisfiedIrrigationLivestockDemandFromSurfaceWater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract - satisfiedIrrigationDemandFromSurfaceWater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromSurfaceWater - \ + satisfiedIrrigationDemandFromSurfaceWater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract -\ + satisfiedIrrigationLivestockDemandFromSurfaceWater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + ###################################################################################################################### @@ -3741,12 +3684,41 @@ def upperSoilUpdate(self,meteo,groundwater,routing,\ self.getSoilStates() # calculate water demand (including partitioning to different source) - self.calculateWaterDemand(nonIrrGrossDemandDict, swAbstractionFractionDict, \ - groundwater, routing, \ - allocSegments, currTimeStep,\ - desalinationWaterUse,\ - groundwater_pumping_region_ids,regionalAnnualGroundwaterAbstractionLimit) - + # self.calculateWaterDemand(nonIrrGrossDemandDict, swAbstractionFractionDict, \ + # groundwater, routing, \ + # allocSegments, currTimeStep,\ + # desalinationWaterUse,\ + # groundwater_pumping_region_ids,regionalAnnualGroundwaterAbstractionLimit) + self.totalPotentialGrossDemand = pcr.scalar(0.0) + self.nonIrrGrossDemand = pcr.scalar(0.0) + self.irrGrossDemand = pcr.scalar(0.0) + self.irrGrossDemandPaddy = pcr.scalar(0.0) + self.irrGrossDemandNonPaddy = pcr.scalar(0.0) + self.desalinationAbstraction = pcr.scalar(0.0) + self.desalinationAllocation = pcr.scalar(0.0) + self.actSurfaceWaterAbstract = pcr.scalar(0.0) + self.allocSurfaceWaterAbstract = pcr.scalar(0.0) + self.nonFossilGroundwaterAbs = pcr.scalar(0.0) + self.allocNonFossilGroundwater = pcr.scalar(0.0) + self.fossilGroundwaterAbstr = pcr.scalar(0.0) + self.fossilGroundwaterAlloc = pcr.scalar(0.0) + self.totalGroundwaterAbstraction = pcr.scalar(0.0) + self.totalGroundwaterAllocation = pcr.scalar(0.0) + self.reducedCapRise = pcr.scalar(0.0) + + self.totalPotentialMaximumGrossDemand = pcr.scalar(0.0) + self.totalPotentialMaximumNonIrrGrossDemand = pcr.scalar(0.0) + self.totalPotentialMaximumIrrGrossDemand = pcr.scalar(0.0) + self.totalPotentialMaximumIrrGrossDemandPaddy = pcr.scalar(0.0) + self.totalPotentialMaximumIrrGrossDemandNonPaddy = pcr.scalar(0.0) + + self.domesticWaterWithdrawal = pcr.scalar(0.0) + self.industryWaterWithdrawal = pcr.scalar(0.0) + self.livestockWaterWithdrawal = pcr.scalar(0.0) + + self.nonIrrReturnFlow = pcr.scalar(0.0) + self.irrigationEfficiencyUsed = pcr.scalar(1.0) + # calculate openWaterEvap: open water evaporation from the paddy field, # and update topWaterLayer after openWaterEvap. self.calculateOpenWaterEvap() diff --git a/model/landSurface.py b/model/landSurface.py index 958d537af..2e3556547 100644 --- a/model/landSurface.py +++ b/model/landSurface.py @@ -32,13 +32,14 @@ from ncConverter import * import landCover as lc +import waterUse as wu import parameterSoilAndTopo as parSoilAndTopo class LandSurface(object): - + def getState(self): result = {} - + if self.numberOfSoilLayers == 2: for coverType in self.coverTypes: result[coverType] = {} @@ -56,7 +57,7 @@ def getState(self): self.landCoverObj[coverType].storLow result[coverType]['interflow' ] = \ self.landCoverObj[coverType].interflow - + if self.numberOfSoilLayers == 3: for coverType in self.coverTypes: result[coverType] = {} @@ -76,34 +77,34 @@ def getState(self): self.landCoverObj[coverType].storLow030150 result[coverType]['interflow' ] = \ self.landCoverObj[coverType].interflow - - return result - - def getPseudoState(self): - result = {} - - if self.numberOfSoilLayers == 2: - result['interceptStor'] = self.interceptStor - result['snowCoverSWE'] = self.snowCoverSWE - result['snowFreeWater'] = self.snowFreeWater - result['topWaterLayer'] = self.topWaterLayer - result['storUpp'] = self.storUpp - result['storLow'] = self.storLow - - if self.numberOfSoilLayers == 3: - result['interceptStor'] = self.interceptStor - result['snowCoverSWE'] = self.snowCoverSWE - result['snowFreeWater'] = self.snowFreeWater - result['topWaterLayer'] = self.topWaterLayer - result['storUpp000005'] = self.storUpp000005 - result['storUpp005030'] = self.storUpp005030 - result['storLow030150'] = self.storLow030150 return result +# def getPseudoState(self): +# result = {} +# +# if self.numberOfSoilLayers == 2: +# result['interceptStor'] = self.interceptStor +# result['snowCoverSWE'] = self.snowCoverSWE +# result['snowFreeWater'] = self.snowFreeWater +# result['topWaterLayer'] = self.topWaterLayer +# result['storUpp'] = self.storUpp +# result['storLow'] = self.storLow +# +# if self.numberOfSoilLayers == 3: +# result['interceptStor'] = self.interceptStor +# result['snowCoverSWE'] = self.snowCoverSWE +# result['snowFreeWater'] = self.snowFreeWater +# result['topWaterLayer'] = self.topWaterLayer +# result['storUpp000005'] = self.storUpp000005 +# result['storUpp005030'] = self.storUpp005030 +# result['storLow030150'] = self.storLow030150 +# +# return result + def __init__(self,iniItems,landmask,initialState=None): object.__init__(self) - + # clone map, temporary directory, absolute path of input directory, and landmask self.cloneMap = iniItems.cloneMap self.tmpDir = iniItems.tmpDir @@ -123,14 +124,14 @@ def __init__(self,iniItems,landmask,initialState=None): # list of aggregated variables that MUST be defined in the module: # - aggregated from landCover modules - # - some are needed for water balance checking + # - some are needed for water balance checking # - some are needed in other modules (e.g. routing, groundwater) # - some are needed for initialConditions # # main state variables (unit: m) - self.mainStates = ['interceptStor',\ - 'snowCoverSWE' ,\ - 'snowFreeWater',\ + self.mainStates = ['interceptStor', + 'snowCoverSWE' , + 'snowFreeWater', 'topWaterLayer'] # # state variables (unit: m) @@ -141,7 +142,9 @@ def __init__(self,iniItems,landmask,initialState=None): 'satDegTotal'] # # flux variables (unit: m/day) - self.fluxVars = ['infiltration','gwRecharge','netLqWaterToSoil', + self.fluxVars = ['infiltration', + 'gwRecharge', + 'netLqWaterToSoil', 'totalPotET', 'actualET', 'interceptEvap', @@ -150,13 +153,25 @@ def __init__(self,iniItems,landmask,initialState=None): 'actBareSoilEvap', 'actTranspiUppTotal', 'actTranspiLowTotal', - 'actTranspiTotal', + 'actTranspiTotal', 'directRunoff', 'interflow', 'interflowTotal', + 'landSurfaceRunoff', + 'satExcess', + 'snowMelt', + 'irrGrossDemand', + 'irrGrossDemandPaddy', + 'irrGrossDemandNonPaddy', 'nonIrrGrossDemand', 'totalPotentialGrossDemand', + 'totalPotentialMaximumGrossDemand', + 'totalPotentialMaximumIrrGrossDemand', + 'totalPotentialMaximumIrrGrossDemandPaddy', + 'totalPotentialMaximumIrrGrossDemandNonPaddy', + 'totalPotentialMaximumNonIrrGrossDemand', + 'actSurfaceWaterAbstract', 'allocSurfaceWaterAbstract', 'desalinationAbstraction', @@ -165,18 +180,9 @@ def __init__(self,iniItems,landmask,initialState=None): 'allocNonFossilGroundwater', 'fossilGroundwaterAbstr', 'fossilGroundwaterAlloc', - 'landSurfaceRunoff', - 'satExcess', - 'snowMelt', 'totalGroundwaterAbstraction', 'totalGroundwaterAllocation', - 'totalPotentialMaximumGrossDemand', - 'totalPotentialMaximumIrrGrossDemand', - 'totalPotentialMaximumIrrGrossDemandPaddy', - 'totalPotentialMaximumIrrGrossDemandNonPaddy', - 'totalPotentialMaximumNonIrrGrossDemand', - 'irrGrossDemandPaddy', - 'irrGrossDemandNonPaddy', + 'domesticWaterWithdrawal', 'industryWaterWithdrawal', 'livestockWaterWithdrawal', @@ -201,32 +207,38 @@ def __init__(self,iniItems,landmask,initialState=None): self.aggrVars = self.stateVars + self.fluxVars if self.numberOfSoilLayers == 2: self.aggrVars += ['satDegUpp','satDegLow'] if self.numberOfSoilLayers == 3: self.aggrVars += ['satDegUpp000005','satDegUpp005030','satDegLow030150'] - + self.debugWaterBalance = iniItems.landSurfaceOptions['debugWaterBalance'] - # TDOD: Perform water balance checks for aggregates values (from values of each land cover type). + # TODO: Perform water balance checks for aggregates values (from values of each land cover type). + # Move it to waterUse.py ..................................................................................................................... + # # limitAbstraction self.limitAbstraction = False if iniItems.landSurfaceOptions['limitAbstraction'] == "True": self.limitAbstraction = True - - # landCover types included in the simulation: - self.coverTypes = ["forest","grassland"] # + #............................................................................................................................................. + + # landCover types included in the simulation: <------------- Modifications to easily create new coverTypes + self.coverTypes = ["forest","grassland"] + self.includeIrrigation = False if iniItems.landSurfaceOptions['includeIrrigation'] == "True": self.includeIrrigation = True self.coverTypes += ["irrPaddy","irrNonPaddy"] logger.info("Irrigation is included/considered in this run.") - else: + else: logger.info("Irrigation is NOT included/considered in this run.") - + # if user define their land cover types: if 'landCoverTypes' in list(iniItems.landSurfaceOptions.keys()): self.coverTypes = iniItems.landSurfaceOptions['landCoverTypes'].split(",") - - # water demand options: irrigation efficiency, non irrigation water demand, and desalination supply - self.waterDemandOptions(iniItems) + # Move it to waterUse.py ..................................................................................................................... + # + # water demand options: irrigation efficiency, non irrigation water demand, and desalination supply + self.waterDemandOptions(iniItems) + self.wu.waterDemandOptions(iniItems) # TODO: Make an option so that users can easily perform natural runs (without water user, without reservoirs). # pre-defined surface water source fraction for satisfying irrigation and livestock water demand @@ -247,24 +259,24 @@ def __init__(self,iniItems,landmask,initialState=None): pcr.cover(\ vos.readPCRmapClone(iniItems.landSurfaceOptions['irrigationSurfaceWaterAbstractionFractionDataQuality'],\ self.cloneMap,self.tmpDir,self.inputDir), 0.0) - # ignore value with the quality above 5 (very bad) + # ignore value with the quality above 5 (very bad) # - Note: The resulting map has values only in cells with the data auality <= 5.0 self.swAbstractionFractionData = pcr.ifthen(self.swAbstractionFractionDataQuality <= 5.0, \ self.swAbstractionFractionData) - + # maximum pre-defined surface water source fraction for satisfying industrial and domestic water demand: # - if not defined (default), set it to the maximum self.maximumNonIrrigationSurfaceWaterAbstractionFractionData = pcr.scalar(1.0) if 'maximumNonIrrigationSurfaceWaterAbstractionFractionData' in list(iniItems.landSurfaceOptions.keys()): if iniItems.landSurfaceOptions['maximumNonIrrigationSurfaceWaterAbstractionFractionData'] != "None" or\ iniItems.landSurfaceOptions['maximumNonIrrigationSurfaceWaterAbstractionFractionData'] != "False": - + logger.info('Set the maximum fraction for predefined surface water source for satisfying domestic and industrial demand.') self.maximumNonIrrigationSurfaceWaterAbstractionFractionData = pcr.min(1.0,\ pcr.cover(\ vos.readPCRmapClone(iniItems.landSurfaceOptions['maximumNonIrrigationSurfaceWaterAbstractionFractionData'],\ self.cloneMap,self.tmpDir,self.inputDir), 1.0)) - + # pre-defined surface water source fraction for satisfying industrial and domestic water demand self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData = None if 'predefinedNonIrrigationSurfaceWaterAbstractionFractionData' in list(iniItems.landSurfaceOptions.keys()) and \ @@ -278,7 +290,7 @@ def __init__(self,iniItems,landmask,initialState=None): self.cloneMap,self.tmpDir,self.inputDir), 1.0)) self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData = pcr.max(0.0, \ pcr.min(self.maximumNonIrrigationSurfaceWaterAbstractionFractionData, \ - self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData)) + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData)) # threshold values defining the preference for irrigation water source (unit: fraction/percentage) self.treshold_to_maximize_irrigation_surface_water = \ @@ -287,6 +299,8 @@ def __init__(self,iniItems,landmask,initialState=None): self.treshold_to_minimize_fossil_groundwater_irrigation = \ vos.readPCRmapClone(iniItems.landSurfaceOptions['treshold_to_minimize_fossil_groundwater_irrigation'],\ self.cloneMap,self.tmpDir,self.inputDir) + # + #............................................................................................................................................. # assign the topography and soil parameters self.soil_topo_parameters = {} @@ -299,7 +313,7 @@ def __init__(self,iniItems,landmask,initialState=None): dictionary_of_land_cover_settings = iniItems.__getattribute__(name_of_section_given_in_ini_file) if 'usingSpecificSoilTopo' not in list(dictionary_of_land_cover_settings.keys()): dictionary_of_land_cover_settings['usingSpecificSoilTopo'] = "False" - if dictionary_of_land_cover_settings['usingSpecificSoilTopo'] == "True": + if dictionary_of_land_cover_settings['usingSpecificSoilTopo'] == "True": msg = "Using a specific set of soil and topo parameters " msg += "as defined in the "+name_of_section_given_in_ini_file+" of the ini/configuration file." @@ -307,23 +321,21 @@ def __init__(self,iniItems,landmask,initialState=None): self.soil_topo_parameters[coverType] = parSoilAndTopo.SoilAndTopoParameters(iniItems,self.landmask) self.soil_topo_parameters[coverType].read(iniItems, dictionary_of_land_cover_settings) else: - msg = "Using the default set of soil and topo parameters " msg += "as defined in the landSurfaceOptions of the ini/configuration file." - - self.soil_topo_parameters[coverType] = self.soil_topo_parameters['default'] - + self.soil_topo_parameters[coverType] = self.soil_topo_parameters['default'] + logger.info(msg) - - + # instantiate self.landCoverObj[coverType] self.landCoverObj = {} - for coverType in self.coverTypes: - self.landCoverObj[coverType] = lc.LandCover(iniItems,\ - str(coverType)+'Options',\ - self.soil_topo_parameters[coverType],self.landmask,\ - self.irrigationEfficiency,\ - self.usingAllocSegments) + for coverType in self.coverTypes: + self.landCoverObj[coverType] = lc.LandCover(iniItems, + str(coverType)+'Options', + self.soil_topo_parameters[coverType], + self.landmask, + self.irrigationEfficiency, + self.usingAllocSegments) # remove self.irrigationEfficiency and self.usingAllocSegments # rescale landCover Fractions # - by default, the land cover fraction will always be corrected (to ensure the total of all fractions = 1.0) @@ -347,11 +359,11 @@ def __init__(self,iniItems,landmask,initialState=None): if self.noAnnualChangesInLandCoverParameter == False and self.noLandCoverFractionCorrection == False: self.noLandCoverFractionCorrection = True msg = "WARNING! No land cover fraction correction will be performed. Please make sure that the 'total' of all fracVegCover adds to one." - logger.warning(msg) - logger.warning(msg) - logger.warning(msg) - logger.warning(msg) - logger.warning(msg) + logger.warning(msg) + logger.warning(msg) + logger.warning(msg) + logger.warning(msg) + logger.warning(msg) ######################################################################################################################################################################################### # 29 July 2014: @@ -379,68 +391,6 @@ def __init__(self,iniItems,landmask,initialState=None): self.landCoverObj[coverType].irrTypeFracOverIrr = vos.getValDivZero(self.landCoverObj[coverType].fracVegCover,\ totalIrrAreaFrac, vos.smallNumber) - ############################################################################################################################################# - # Surface water quality option: Water allocation based on sectoral water quality requirements - # - # Evaluating if surface water quality will be considered for water allocation - self.consider_water_quality = False - self.wq_threshold = {} - if ("considerWaterQuality" in list(iniItems.landSurfaceOptions.keys()) and iniItems.landSurfaceOptions["considerWaterQuality"] == "True"): - logger.info('Water use in this model run considers water quality.') - self.consider_water_quality = True - - # - input files with surface water quality concentrations - self.inputFileSWT = iniItems.landSurfaceOptions["inputFileSWTemperature"] - self.inputFileBOD = iniItems.landSurfaceOptions["inputFileBiochemOxigenDemand"] - self.inputFileTDS = iniItems.landSurfaceOptions["inputFileTotalDissolvedSolid"] - self.inputFileFC = iniItems.landSurfaceOptions["inputFileFecalColiform"] - - # - threshold values of water quality constituents -# self.wq_threshold["sw_temperature"] = {} -# self.wq_threshold["sw_temperature"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) -# self.wq_threshold["sw_temperature"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdSWTForLivestock"]) -# self.wq_threshold["sw_temperature"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdSWTForDomestic"]) -# self.wq_threshold["sw_temperature"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIndustrial"]) -# self.wq_threshold["bio_o2_demand"] = {} -# self.wq_threshold["bio_o2_demand"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) -# self.wq_threshold["bio_o2_demand"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdBODForLivestock"]) -# self.wq_threshold["bio_o2_demand"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdBODForDomestic"]) -# self.wq_threshold["bio_o2_demand"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdBODForIndustrial"]) -# self.wq_threshold["tot_dis_solid"] = {} -# self.wq_threshold["tot_dis_solid"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) -# self.wq_threshold["tot_dis_solid"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdTDSForLivestock"]) -# self.wq_threshold["tot_dis_solid"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdTDSForDomestic"]) -# self.wq_threshold["tot_dis_solid"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIndustrial"]) -# self.wq_threshold["fecal_coliform"] = {} -# self.wq_threshold["fecal_coliform"]["irrigation"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) -# self.wq_threshold["fecal_coliform"]["livestock"] = eval(iniItems.landSurfaceOptions["thresholdFCForLivestock"]) -# self.wq_threshold["fecal_coliform"]["domestic"] = eval(iniItems.landSurfaceOptions["thresholdFCForDomestic"]) -# self.wq_threshold["fecal_coliform"]["industrial"] = eval(iniItems.landSurfaceOptions["thresholdFCForIndustrial"]) - - self.wq_threshold["irrigation"] = {} - self.wq_threshold["irrigation"]["sw_temperature"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIrrigation"]) - self.wq_threshold["irrigation"]["bio_o2_demand"] = eval(iniItems.landSurfaceOptions["thresholdBODForIrrigation"]) - self.wq_threshold["irrigation"]["tot_dis_solid"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIrrigation"]) - self.wq_threshold["irrigation"]["fecal_coliform"] = eval(iniItems.landSurfaceOptions["thresholdFCForIrrigation"]) - self.wq_threshold["livestock"] = {} - self.wq_threshold["livestock"]["sw_temperature"] = eval(iniItems.landSurfaceOptions["thresholdSWTForLivestock"]) - self.wq_threshold["livestock"]["bio_o2_demand"] = eval(iniItems.landSurfaceOptions["thresholdBODForLivestock"]) - self.wq_threshold["livestock"]["tot_dis_solid"] = eval(iniItems.landSurfaceOptions["thresholdTDSForLivestock"]) - self.wq_threshold["livestock"]["fecal_coliform"] = eval(iniItems.landSurfaceOptions["thresholdFCForLivestock"]) - self.wq_threshold["domestic"] = {} - self.wq_threshold["domestic"]["sw_temperature"] = eval(iniItems.landSurfaceOptions["thresholdSWTForDomestic"]) - self.wq_threshold["domestic"]["bio_o2_demand"] = eval(iniItems.landSurfaceOptions["thresholdBODForDomestic"]) - self.wq_threshold["domestic"]["tot_dis_solid"] = eval(iniItems.landSurfaceOptions["thresholdTDSForDomestic"]) - self.wq_threshold["domestic"]["fecal_coliform"] = eval(iniItems.landSurfaceOptions["thresholdFCForDomestic"]) - self.wq_threshold["industrial"] = {} - self.wq_threshold["industrial"]["sw_temperature"] = eval(iniItems.landSurfaceOptions["thresholdSWTForIndustrial"]) - self.wq_threshold["industrial"]["bio_o2_demand"] = eval(iniItems.landSurfaceOptions["thresholdBODForIndustrial"]) - self.wq_threshold["industrial"]["tot_dis_solid"] = eval(iniItems.landSurfaceOptions["thresholdTDSForIndustrial"]) - self.wq_threshold["industrial"]["fecal_coliform"] = eval(iniItems.landSurfaceOptions["thresholdFCForIndustrial"]) - # - # - ############################################################################################################################################# - # get the initial conditions (for every land cover type) self.getInitialConditions(iniItems, initialState) @@ -449,7 +399,7 @@ def __init__(self,iniItems,landmask,initialState=None): def initiate_old_style_land_surface_reporting(self,iniItems): - + self.report = True try: self.outDailyTotNC = iniItems.landSurfaceOptions['outDailyTotNC'].split(",") @@ -533,15 +483,14 @@ def getInitialConditions(self, iniItems, iniConditions = None): # starting year in integer starting_year = int(iniItems.globalOptions['startTime'][0:4]) - # - # check if the run start at the first day of the year: + + # check if the run start at the first day of the year start_on_1_Jan = False if iniItems.globalOptions['startTime'][-5:] == "01-01": start_on_1_Jan = True - + # condition to consider previous year land cover fraction consider_previous_year_land_cover_fraction = False - - + ####################################################################################################################################### # obtaining initial land cover fractions for runs with dynamicIrrigationArea # @@ -561,10 +510,9 @@ def getInitialConditions(self, iniItems, iniConditions = None): self.dynamicIrrigationArea and self.noLandCoverFractionCorrection == False: # just using the current year land cover fractions: self.scaleDynamicIrrigation(starting_year) # the current year land cover fractions - # + ################################################################################################################################# - - ####################################################################################################################################### + # obtaining initial land cover fractions for runs with noLandCoverFractionCorrection and annualChangesInLandCoverParameters # # For non spin-up runs that start at the first day of the year (1 January), @@ -583,24 +531,24 @@ def getInitialConditions(self, iniItems, iniConditions = None): # correcting land cover fractions total_fractions = pcr.scalar(0.0) for coverType in self.coverTypes: - total_fractions += self.landCoverObj[coverType].previousFracVegCover - + total_fractions += self.landCoverObj[coverType].previousFracVegCover + if 'grassland' in list(self.landCoverObj.keys()): self.landCoverObj['grassland'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].previousFracVegCover, 1.0) if 'short_natural' in list(self.landCoverObj.keys()): self.landCoverObj['short_natural'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].previousFracVegCover, 1.0) - + total_fractions = pcr.scalar(0.0) for coverType in self.coverTypes: - total_fractions += self.landCoverObj[coverType].previousFracVegCover - + total_fractions += self.landCoverObj[coverType].previousFracVegCover + for coverType in self.coverTypes: - self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].previousFracVegCover / total_fractions + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].previousFracVegCover / total_fractions #################################################################################################################################################################### consider_previous_year_land_cover_fraction = True - + # For spin-up runs or for runs that start after 1 January, # - we do not have to consider the previous year land cover fractions # @@ -611,27 +559,26 @@ def getInitialConditions(self, iniItems, iniConditions = None): for coverType in self.coverTypes: self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].get_land_cover_parameters(date_in_string = one_january_this_year, \ get_only_fracVegCover = True) - + #################################################################################################################################################################### # correcting land cover fractions total_fractions = pcr.scalar(0.0) for coverType in self.coverTypes: - total_fractions += self.landCoverObj[coverType].previousFracVegCover - + total_fractions += self.landCoverObj[coverType].previousFracVegCover + if 'grassland' in list(self.landCoverObj.keys()): self.landCoverObj['grassland'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].previousFracVegCover, 1.0) if 'short_natural' in list(self.landCoverObj.keys()): self.landCoverObj['short_natural'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].previousFracVegCover, 1.0) - + total_fractions = pcr.scalar(0.0) for coverType in self.coverTypes: - total_fractions += self.landCoverObj[coverType].previousFracVegCover - + total_fractions += self.landCoverObj[coverType].previousFracVegCover + for coverType in self.coverTypes: - self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].previousFracVegCover / total_fractions - #################################################################################################################################################################### - + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].previousFracVegCover / total_fractions + ##################################################################################################################################################################### # get initial conditions # - first, we set all aggregated states to zero (only the ones in mainStates): @@ -652,34 +599,34 @@ def getInitialConditions(self, iniItems, iniConditions = None): land_cover_states = vars(self.landCoverObj[coverType])[var] vars(self)[var] += land_cover_states * land_cover_fraction + # Move to waterUse.py ............................................................................................................................ + # def waterDemandOptions(self,iniItems): - + # domestic water demand (unit: m/day) - # self.domesticWaterDemandOption = False if iniItems.landSurfaceOptions['includeDomesticWaterDemand'] == "True": logger.info("Domestic water demand is included in the calculation.") - self.domesticWaterDemandOption = True + self.domesticWaterDemandOption = True else: logger.info("Domestic water demand is NOT included in the calculation.") - # + if self.domesticWaterDemandOption: self.domesticWaterDemandFile = vos.getFullPath(\ iniItems.landSurfaceOptions['domesticWaterDemandFile'],self.inputDir,False) - + # industry water demand (unit: m/day) - # self.industryWaterDemandOption = False if iniItems.landSurfaceOptions['includeIndustryWaterDemand'] == "True": logger.info("Industry water demand is included in the calculation.") self.industryWaterDemandOption = True else: logger.info("Industry water demand is NOT included in the calculation.") - # + if self.industryWaterDemandOption: self.industryWaterDemandFile = vos.getFullPath(\ iniItems.landSurfaceOptions['industryWaterDemandFile'],self.inputDir,False) - + # livestock water demand (unit: m/day) self.livestockWaterDemandOption = False if iniItems.landSurfaceOptions['includeLivestockWaterDemand'] == "True": @@ -687,7 +634,7 @@ def waterDemandOptions(self,iniItems): self.livestockWaterDemandOption = True else: logger.info("Livestock water demand is NOT included in the calculation.") - # + if self.livestockWaterDemandOption: self.livestockWaterDemandFile = vos.getFullPath(\ iniItems.landSurfaceOptions['livestockWaterDemandFile'],self.inputDir,False) @@ -697,23 +644,23 @@ def waterDemandOptions(self,iniItems): if iniItems.landSurfaceOptions['historicalIrrigationArea'] != "None": logger.info("Using the dynamicIrrigationArea option. Extent of irrigation areas is based on the file provided in the 'historicalIrrigationArea'.") self.dynamicIrrigationArea = True - # + if self.dynamicIrrigationArea: self.dynamicIrrigationAreaFile = vos.getFullPath(\ iniItems.landSurfaceOptions['historicalIrrigationArea'],self.inputDir,False) - # irrigation efficiency map (in percentage) # TODO: Using the time series of efficiency (considering historical technological development). + # irrigation efficiency map (in percentage) # TODO: Using the time series of efficiency (considering historical technological development) self.irrigationEfficiency = vos.readPCRmapClone(\ iniItems.landSurfaceOptions['irrigationEfficiency'], self.cloneMap,self.tmpDir,self.inputDir) - + extrapolate = True - if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False - + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": + extrapolate = False + if extrapolate: - # extrapolate efficiency map: # TODO: Make a better extrapolation algorithm (considering cell size, etc.). - + window_size = 1.25 * pcr.clone().cellSize() window_size = min(window_size, min(pcr.clone().nrRows(), pcr.clone().nrCols())*pcr.clone().cellSize()) try: @@ -725,10 +672,9 @@ def waterDemandOptions(self,iniItems): self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 0.75)) self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.00)) self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.50)) - except: + except: pass - #~ self.irrigationEfficiency = pcr.ifthen(self.landmask, self.irrigationEfficiency) self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, 1.0) self.irrigationEfficiency = pcr.max(0.1, self.irrigationEfficiency) self.irrigationEfficiency = pcr.ifthen(self.landmask, self.irrigationEfficiency) @@ -739,52 +685,51 @@ def waterDemandOptions(self,iniItems): logger.info("Monthly desalination water is included.") self.includeDesalination = True self.desalinationWaterFile = vos.getFullPath(iniItems.landSurfaceOptions['desalinationWater'], self.inputDir) - else: + else: logger.info("Monthly desalination water is NOT included.") - + # zones at which water allocation (surface and groundwater allocation) is determined self.usingAllocSegments = False self.allocSegments = None - if iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'] != "None": - self.usingAllocSegments = True + if iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'] != "None": + self.usingAllocSegments = True self.allocSegments = vos.readPCRmapClone(\ - iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'], - self.cloneMap,self.tmpDir,self.inputDir,isLddMap=False,cover=None,isNomMap=True) + iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'], + self.cloneMap,self.tmpDir,self.inputDir,isLddMap=False,cover=None,isNomMap=True) self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) self.allocSegments = pcr.clump(self.allocSegments) + # extrapolate values to cover landmask extrapolate = True - if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False - + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": + extrapolate = False if extrapolate: - # extrapolate it self.allocSegments = pcr.cover(self.allocSegments, \ pcr.windowmajority(self.allocSegments, 0.5)) - self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) # clump it and cover the rests with cell ids self.allocSegments = pcr.clump(self.allocSegments) cell_ids = pcr.mapmaximum(pcr.scalar(self.allocSegments)) + pcr.scalar(100.0) + pcr.uniqueid(pcr.boolean(1.0)) - self.allocSegments = pcr.cover(self.allocSegments, pcr.nominal(cell_ids)) + self.allocSegments = pcr.cover(self.allocSegments, pcr.nominal(cell_ids)) self.allocSegments = pcr.clump(self.allocSegments) self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) - + # cell area (unit: m2) cellArea = vos.readPCRmapClone(\ iniItems.routingOptions['cellAreaMap'], self.cloneMap,self.tmpDir,self.inputDir) cellArea = pcr.ifthen(self.landmask, cellArea) - + # zonal/segment area (unit: m2) self.segmentArea = pcr.areatotal(pcr.cover(cellArea, 0.0), self.allocSegments) self.segmentArea = pcr.ifthen(self.landmask, self.segmentArea) - + else: - logger.info("If there is any, water demand is satisfied by local source only.") - + # + #................................................................................................................................................. def scaleNaturalLandCoverFractions(self): ''' rescales natural land cover fractions (make sure the total = 1)''' @@ -913,6 +858,8 @@ def scaleModifiedLandCoverFractions(self): if abs(a) > threshold or abs(b) > threshold: logger.error("fraction total (from all land cover types) is not equal to 1.0 ... Min %f Max %f Mean %f" %(a,b,c)) + # Move to waterUse.py ............................................................................................................................ + # def obtainNonIrrWaterDemand(self,routing,currTimeStep): # get NON-Irrigation GROSS water demand and its return flow fraction @@ -1047,6 +994,8 @@ def obtainNonIrrWaterDemand(self,routing,currTimeStep): nonIrrigationWaterDemandDict['return_flow_fraction']['livestock'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.livestockReturnFlowFraction*1000.)/1000.), 1.0) return nonIrrigationWaterDemandDict + # + #................................................................................................................................................. def calculateCapRiseFrac(self,groundwater,routing,currTimeStep): # calculate cell fraction influenced by capillary rise: @@ -1116,8 +1065,7 @@ def calculateCapRiseFrac(self,groundwater,routing,currTimeStep): # zero fracwat assumption used for debugging against version 1.0 if routing.zeroFracWatAllAndAlways: FRACWAT = pcr.scalar(0.0) - - + CRFRAC = pcr.min( 1.0,1.0 -(self.soil_topo_parameters['default'].dzRel0100-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0100-self.soil_topo_parameters['default'].dzRel0090) ); CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0090,0.9 -(self.soil_topo_parameters['default'].dzRel0090-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0090-self.soil_topo_parameters['default'].dzRel0080),CRFRAC); CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0080,0.8 -(self.soil_topo_parameters['default'].dzRel0080-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0080-self.soil_topo_parameters['default'].dzRel0070),CRFRAC); @@ -1130,46 +1078,41 @@ def calculateCapRiseFrac(self,groundwater,routing,currTimeStep): CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0010,0.1 -(self.soil_topo_parameters['default'].dzRel0010-dzGroundwater)*0.05/pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0010-self.soil_topo_parameters['default'].dzRel0005),CRFRAC); CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0005,0.05-(self.soil_topo_parameters['default'].dzRel0005-dzGroundwater)*0.04/pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0005-self.soil_topo_parameters['default'].dzRel0001),CRFRAC); CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0001,0.01-(self.soil_topo_parameters['default'].dzRel0001-dzGroundwater)*0.01/pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0001 ),CRFRAC); - - #~ print(FRACWAT) - CRFRAC = pcr.ifthenelse(FRACWAT < 1.0,pcr.max(0.0,CRFRAC-FRACWAT)/(1.0-FRACWAT),0.0); capRiseFrac = pcr.max(0.0,pcr.min(1.0,CRFRAC)) - #~ capRiseFrac = 0.0 - return capRiseFrac + # Move to waterUse.py ............................................................................................................................ + # def partitioningGroundSurfaceAbstraction(self,groundwater,routing): - # partitioning abstraction sources: groundwater and surface water - # de Graaf et al., 2014 principle: partitioning based on local average baseflow (m3/s) and upstream average discharge (m3/s) + # (de Graaf et al., 2014) partitioning based on local average baseflow (m3/s) and upstream average discharge (m3/s) # - estimates of fractions of groundwater and surface water abstractions - averageBaseflowInput = routing.avgBaseflow + averageBaseflowInput = routing.avgBaseflow averageUpstreamInput = pcr.max(routing.avgDischarge, pcr.cover(pcr.upstream(routing.lddMap, routing.avgDischarge), 0.0)) if self.usingAllocSegments: - - averageBaseflowInput = pcr.max(0.0, pcr.ifthen(self.landmask, averageBaseflowInput)) + averageBaseflowInput = pcr.max(0.0, pcr.ifthen(self.landmask, averageBaseflowInput)) averageUpstreamInput = pcr.max(0.0, pcr.ifthen(self.landmask, averageUpstreamInput)) - averageBaseflowInput = pcr.cover(pcr.areaaverage(averageBaseflowInput, self.allocSegments), 0.0) + averageBaseflowInput = pcr.cover(pcr.areaaverage(averageBaseflowInput, self.allocSegments), 0.0) averageUpstreamInput = pcr.cover(pcr.areamaximum(averageUpstreamInput, self.allocSegments), 0.0) - + else: logger.debug("Water demand can only be satisfied by local source.") - + swAbstractionFraction = vos.getValDivZero(\ averageUpstreamInput, averageUpstreamInput+averageBaseflowInput, vos.smallNumber) swAbstractionFraction = pcr.roundup(swAbstractionFraction*100.)/100. swAbstractionFraction = pcr.max(0.0, swAbstractionFraction) swAbstractionFraction = pcr.min(1.0, swAbstractionFraction) - + if self.usingAllocSegments: swAbstractionFraction = pcr.areamaximum(swAbstractionFraction, self.allocSegments) - + swAbstractionFraction = pcr.cover(swAbstractionFraction, 1.0) swAbstractionFraction = pcr.ifthen(self.landmask, swAbstractionFraction) @@ -1191,7 +1134,6 @@ def partitioningGroundSurfaceAbstraction(self,groundwater,routing): # Principle: Areas with swAbstractionFractionDict['irrigation'] above this treshold will not extract fossil groundwater. swAbstractionFractionDict['treshold_to_minimize_fossil_groundwater_irrigation'] = self.treshold_to_minimize_fossil_groundwater_irrigation - # the default value of surface water source fraction is None or not defined (in this case, this value will be the 'estimate' and limited with 'max_for_non_irrigation') swAbstractionFractionDict['non_irrigation'] = None @@ -1212,13 +1154,15 @@ def partitioningGroundSurfaceAbstraction(self,groundwater,routing): swAbstractionFractionDict['non_irrigation'] = pcr.min(\ swAbstractionFractionDict['non_irrigation'], \ swAbstractionFractionDict['max_for_non_irrigation']) - - else: - + else: logger.debug('NOT using/incorporating the predefined fractions of surface water source.') - + return swAbstractionFractionDict + # + #................................................................................................................................................. + # Move to waterUse.py ............................................................................................................................ + # def partitioningGroundSurfaceAbstractionForIrrigation(self,\ swAbstractionFractionEstimate,\ swAbstractionFractionData,\ @@ -1237,11 +1181,12 @@ def partitioningGroundSurfaceAbstractionForIrrigation(self,\ swAbstractionFractionForIrrigation = pcr.ifthen(self.landmask, swAbstractionFractionForIrrigation) return swAbstractionFractionForIrrigation + # + #................................................................................................................................................. def scaleDynamicIrrigation(self,yearInInteger): # This method is to update fracVegCover of landCover for historical irrigation areas (done at yearly basis). - #~ # Available datasets are only from 1960 to 2010 (status on 24 September 2010) #~ yearInInteger = int(yearInInteger) #~ if float(yearInInteger) < 1960. or float(yearInInteger) > 2010.: @@ -1252,7 +1197,6 @@ def scaleDynamicIrrigation(self,yearInInteger): # TODO: Generally, I do not need the aforementioned lines as I have defined the functions "findLastYearInNCTime" and "findFirstYearInNCTime" in the module virtualOS.py # However, Niko still need them for his DA scheme as we somehow his DA scheme cannot handle the netcdf file of historical irrigation areas (and therefore we have to use pcraster map files). - yearInString = str(yearInInteger) # read historical irrigation areas @@ -1284,60 +1228,51 @@ def scaleDynamicIrrigation(self,yearInInteger): self.landCoverObj[coverType].fractionArea = 0.0 # reset self.landCoverObj[coverType].fractionArea = self.landCoverObj[coverType].irrTypeFracOverIrr * self.irrigationArea # unit: m2 self.landCoverObj[coverType].fracVegCover = pcr.min(1.0, self.landCoverObj[coverType].fractionArea/ self.cellArea) - + # avoid small values self.landCoverObj[coverType].fracVegCover = pcr.rounddown(self.landCoverObj[coverType].fracVegCover * 1000.)/1000. # rescale land cover fractions (for all land cover types): self.scaleModifiedLandCoverFractions() - + + + + + + + def update(self,meteo,groundwater,routing,currTimeStep): - # updating any information related to water quality - self.wq_state = {} - if self.consider_water_quality: - # converting .nc files to .map per timestep - self.wq_state["sw_temperature"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileSWT, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) - self.wq_state["bio_o2_demand"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileBOD, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) - self.wq_state["tot_dis_solid"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileTDS, varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) - self.wq_state["fecal_coliform"] = vos.netcdf2PCRobjClone(ncFile=self.inputFileFC , varName="automatic", dateInput=currTimeStep.fulldate, useDoy="daily", cloneMapFileName=self.cloneMap) - - # filling nans with zero values - self.wq_state["sw_temperature"] = pcr.cover(self.wq_state["sw_temperature"],0.0) - self.wq_state["bio_o2_demand"] = pcr.cover(self.wq_state["bio_o2_demand"], 0.0) - self.wq_state["tot_dis_solid"] = pcr.cover(self.wq_state["tot_dis_solid"], 0.0) - self.wq_state["fecal_coliform"] = pcr.cover(self.wq_state["fecal_coliform"],0.0) - # updating regional groundwater abstraction limit (at the begining of the year or at the beginning of simulation) if groundwater.limitRegionalAnnualGroundwaterAbstraction: - logger.debug('Total groundwater abstraction is limited by regional annual pumping capacity.') - if currTimeStep.doy == 1 or currTimeStep.timeStepPCR == 1: + if currTimeStep.doy == 1 or currTimeStep.timeStepPCR == 1: self.groundwater_pumping_region_ids = \ vos.netcdf2PCRobjClone(groundwater.pumpingCapacityNC,'region_ids',\ currTimeStep.fulldate, useDoy = 'yearly', cloneMapFileName = self.cloneMap) other_ids = pcr.mapmaximum(self.groundwater_pumping_region_ids) + pcr.scalar(1000.) + pcr.uniqueid(self.landmask) self.groundwater_pumping_region_ids = pcr.cover(self.groundwater_pumping_region_ids, other_ids) self.groundwater_pumping_region_ids = pcr.ifthen(self.landmask, pcr.nominal(self.groundwater_pumping_region_ids)) - + self.regionalAnnualGroundwaterAbstractionLimit = \ pcr.ifthen(self.landmask,\ pcr.cover(\ vos.netcdf2PCRobjClone(groundwater.pumpingCapacityNC,'regional_pumping_limit',\ currTimeStep.fulldate, useDoy = 'yearly', cloneMapFileName = self.cloneMap), 0.0)) - - self.regionalAnnualGroundwaterAbstractionLimit = pcr.areamaximum(self.regionalAnnualGroundwaterAbstractionLimit, self.groundwater_pumping_region_ids) - + + self.regionalAnnualGroundwaterAbstractionLimit = pcr.areamaximum(self.regionalAnnualGroundwaterAbstractionLimit,\ + self.groundwater_pumping_region_ids) + self.regionalAnnualGroundwaterAbstractionLimit *= 1000. * 1000. * 1000. # unit: m3/year self.regionalAnnualGroundwaterAbstractionLimit = pcr.ifthen(self.landmask,\ self.regionalAnnualGroundwaterAbstractionLimit) # minimum value (unit: m3/year at the regional scale) minimum_value = 1000. self.regionalAnnualGroundwaterAbstractionLimit = pcr.max(minimum_value,\ - self.regionalAnnualGroundwaterAbstractionLimit) + self.regionalAnnualGroundwaterAbstractionLimit) + else: - logger.debug('Total groundwater abstraction is NOT limited by regional annual pumping capacity.') self.groundwater_pumping_region_ids = None self.regionalAnnualGroundwaterAbstractionLimit = None @@ -1351,12 +1286,12 @@ def update(self,meteo,groundwater,routing,currTimeStep): # scale land cover fraction (due to expansion/reduction of irrigated areas) self.scaleDynamicIrrigation(currTimeStep.year) - - #################################################################################################################################################################### + + ################################################################################################################################################## # correcting land cover fractions total_fractions = pcr.scalar(0.0) for coverType in self.coverTypes: - total_fractions += self.landCoverObj[coverType].fracVegCover + total_fractions += self.landCoverObj[coverType].fracVegCover if 'grassland' in list(self.landCoverObj.keys()): self.landCoverObj['grassland'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].fracVegCover, 1.0) @@ -1366,13 +1301,12 @@ def update(self,meteo,groundwater,routing,currTimeStep): total_fractions = pcr.scalar(0.0) for coverType in self.coverTypes: - total_fractions += self.landCoverObj[coverType].fracVegCover + total_fractions += self.landCoverObj[coverType].fracVegCover for coverType in self.coverTypes: - self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].fracVegCover / total_fractions - #################################################################################################################################################################### - - + self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].fracVegCover / total_fractions + ################################################################################################################################################## + # read land cover fractions from netcdf files # - assumption: annual resolution if self.noAnnualChangesInLandCoverParameter == False and self.dynamicIrrigationArea == False and \ @@ -1380,14 +1314,15 @@ def update(self,meteo,groundwater,routing,currTimeStep): msg = 'Read land cover fractions based on the given netcdf file.' logger.debug(msg) for coverType in self.coverTypes: - self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].get_land_cover_parameters(date_in_string = str(currTimeStep.fulldate), \ - get_only_fracVegCover = True) - - #################################################################################################################################################################### + self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].get_land_cover_parameters(\ + date_in_string = str(currTimeStep.fulldate),\ + get_only_fracVegCover = True) + + ################################################################################################################################################## # correcting land cover fractions total_fractions = pcr.scalar(0.0) for coverType in self.coverTypes: - total_fractions += self.landCoverObj[coverType].fracVegCover + total_fractions += self.landCoverObj[coverType].fracVegCover if 'grassland' in list(self.landCoverObj.keys()): self.landCoverObj['grassland'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].fracVegCover, 1.0) @@ -1397,25 +1332,23 @@ def update(self,meteo,groundwater,routing,currTimeStep): total_fractions = pcr.scalar(0.0) for coverType in self.coverTypes: - total_fractions += self.landCoverObj[coverType].fracVegCover + total_fractions += self.landCoverObj[coverType].fracVegCover for coverType in self.coverTypes: - self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].fracVegCover / total_fractions - #################################################################################################################################################################### - - + self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].fracVegCover / total_fractions + ################################################################################################################################################## + # transfer some states, due to changes/dynamics in land cover conditions # - if considering dynamic/historical irrigation areas (expansion/reduction of irrigated areas) # - done at yearly basis, at the beginning of each year # - note that this must be done at the beginning of each year, including for the first time step (timeStepPCR == 1) - # + if ((self.dynamicIrrigationArea and self.includeIrrigation) or self.noAnnualChangesInLandCoverParameter == False) and currTimeStep.doy == 1: # # loop for all main states: for var in self.mainStates: - logger.info("Transfering states for the variable "+str(var)) - + moving_fraction = pcr.scalar(0.0) # total land cover fractions that will be transferred moving_states = pcr.scalar(0.0) # total states that will be transferred @@ -1426,7 +1359,7 @@ def update(self,meteo,groundwater,routing,currTimeStep): moving_fraction += pcr.max(0.0, old_fraction-new_fraction) moving_states += pcr.max(0.0, old_fraction-new_fraction) * vars(self.landCoverObj[coverType])[var] - + previous_state = pcr.scalar(0.0) rescaled_state = pcr.scalar(0.0) @@ -1449,10 +1382,10 @@ def update(self,meteo,groundwater,routing,currTimeStep): new_states = pcr.ifthenelse(new_fraction > 0.0, new_states, pcr.scalar(0.0)) vars(self.landCoverObj[coverType])[var] = new_states - + previous_state += old_fraction * old_states rescaled_state += new_fraction * new_states - + # check and make sure that previous_state == rescaled_state check_map = previous_state - rescaled_state a,b,c = vos.getMinMaxMean(check_map) @@ -1461,24 +1394,24 @@ def update(self,meteo,groundwater,routing,currTimeStep): logger.warning("Error in transfering states (due to dynamic in land cover fractions) ... Min %f Max %f Mean %f" %(a,b,c)) else: logger.info("Successful in transfering states (after change in land cover fractions) ... Min %f Max %f Mean %f" %(a,b,c)) - + # for the last day of the year, we have to save the previous land cover fractions (to be considered in the next time step) - if self.dynamicIrrigationArea and self.includeIrrigation and currTimeStep.isLastDayOfYear: + if self.dynamicIrrigationArea and self.includeIrrigation and currTimeStep.isLastDayOfYear: # save the current state of fracVegCover for coverType in self.coverTypes:\ self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].fracVegCover - + #- RvB: irrigation water efficiency # added here are the lines required to read in the water efficiency # irrigation water efficiency is updated at the start of the year and if self.includeIrrigation and (currTimeStep.doy == 1 or currTimeStep.timeStepPCR == 1): - logger.info("Setting irrigation water efficiency") - for coverType in self.coverTypes: - self.landCoverObj[coverType].updateIrrigationWaterEfficiency(currTimeStep) - + logger.info("Setting irrigation water efficiency") + for coverType in self.coverTypes: + self.landCoverObj[coverType].updateIrrigationWaterEfficiency(currTimeStep) + # calculate cell fraction influenced by capillary rise: self.capRiseFrac = self.calculateCapRiseFrac(groundwater,routing,currTimeStep) - + # get a dictionary containing livestock, domestic and industrial water demand, including their return flow fractions self.nonIrrigationWaterDemandDict = self.obtainNonIrrWaterDemand(routing, currTimeStep) @@ -1495,35 +1428,57 @@ def update(self,meteo,groundwater,routing,currTimeStep): vos.netcdf2PCRobjClone(self.desalinationWaterFile,'desalination_water_use',\ currTimeStep.fulldate, useDoy = 'monthly', cloneMapFileName = self.cloneMap), 0.0)) self.desalinationWaterUse = pcr.max(0.0, desalinationWaterUse) - else: + else: logger.debug("Monthly desalination water use is NOT included.") self.desalinationWaterUse = pcr.scalar(0.0) - # update (loop per each land cover type): - for coverType in self.coverTypes: + + + + + + + + # update/calculate water use + # - non irrigation demand (no loop over land cover types needed) + # self.non_irrigation_demand = pcr.scalar(0.0) + + # # - irrigation demand (loop over cover types, but irrigation only + # for coverType in self.coverTypes: (but irrigation only): + # self.irr_water_demand.calculateIrrigationDemand(meteo, groundwater, routing) + + + # update/calculate hydrological processes (loop per each land cover type): + for coverType in self.coverTypes: logger.info("Updating land cover: "+str(coverType)) - self.landCoverObj[coverType].updateLC(meteo,groundwater,routing,\ - self.capRiseFrac,\ - self.nonIrrigationWaterDemandDict,\ - self.swAbstractionFractionDict,\ - currTimeStep,\ - self.allocSegments,\ - self.desalinationWaterUse,\ - self.groundwater_pumping_region_ids,self.regionalAnnualGroundwaterAbstractionLimit,\ - self.wq_state, self.wq_threshold, self.consider_water_quality) - # TODO: Please organize how we will deal with water quality problems (e.g. self.inputBOD) - - # first, we set all aggregated values/variables to zero: + self.landCoverObj[coverType].updateLC(meteo,groundwater,routing, + self.capRiseFrac, + self.nonIrrigationWaterDemandDict, + self.swAbstractionFractionDict, + currTimeStep, + self.allocSegments, + self.desalinationWaterUse, + self.groundwater_pumping_region_ids, + self.regionalAnnualGroundwaterAbstractionLimit) + + + + + + + + + # first, we set all aggregated values/variables to zero: for var in self.aggrVars: vars(self)[var] = pcr.scalar(0.0) - # + # get or calculate the values of all aggregated values/variables for coverType in self.coverTypes: - # calculate the aggregrated or global landSurface values: + # calculate the aggregrated or global landSurface values: for var in self.aggrVars: vars(self)[var] += \ self.landCoverObj[coverType].fracVegCover * vars(self.landCoverObj[coverType])[var] - + # total storages (unit: m3) in the entire landSurface module if self.numberOfSoilLayers == 2: self.totalSto = \ self.snowCoverSWE + self.snowFreeWater + self.interceptStor +\ @@ -1537,11 +1492,10 @@ def update(self,meteo,groundwater,routing,currTimeStep): self.storUpp000005 + self.storUpp005030 +\ self.storLow030150 - # old-style reporting (this is useful for debugging) + # old-style reporting (this is useful for debugging) self.old_style_land_surface_reporting(currTimeStep) def old_style_land_surface_reporting(self,currTimeStep): - if self.report == True: timeStamp = datetime.datetime(currTimeStep.year,\ currTimeStep.month,\ diff --git a/model/merge_netcdf_30arcsec_europe_example_bash.sh b/model/merge_netcdf_30arcsec_europe_example_bash.sh deleted file mode 100644 index 22db983c7..000000000 --- a/model/merge_netcdf_30arcsec_europe_example_bash.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash - -set -x - -MAIN_OUTPUT_DIR="/lustre1/0/einf1079/edwin/pcrglobwb_output_europe/europe_30sec/version_2021-04-20/" - -python merge_netcdf_europe.py ${MAIN_OUTPUT_DIR} ${MAIN_OUTPUT_DIR}/europe_30sec/netcdf outMonthTotNC 1981-01-31 1984-12-31 runoff,totalRunoff,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,baseflow NETCDF4 False 12 europe_30sec europe_30sec & - -python merge_netcdf_europe.py ${MAIN_OUTPUT_DIR} ${MAIN_OUTPUT_DIR}/europe_30sec/netcdf outMonthAvgNC 1981-01-31 1984-12-31 discharge,temperature,snowCoverSWE,storUppTotal,storLowTotal,totalWaterStorageThickness,satDegUpp,satDegLow NETCDF4 False 12 europe_30sec europe_30sec & - -python merge_netcdf_europe.py ${MAIN_OUTPUT_DIR} ${MAIN_OUTPUT_DIR}/europe_30sec/netcdf outAnnuaTotNC 1981-12-31 1984-12-31 runoff,totalRunoff,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,baseflow NETCDF4 False 12 europe_30sec europe_30sec & - -wait - -#~ # for testing -#~ python merge_netcdf_europe.py ${MAIN_OUTPUT_DIR} ${MAIN_OUTPUT_DIR}/europe_30sec/netcdf outMonthAvgNC 1981-01-31 1981-03-31 discharge NETCDF4 False 12 europe_30sec europe_30sec - -set +x diff --git a/model/merge_netcdf_6_arcmin_example_bash.sh b/model/merge_netcdf_6_arcmin_example_bash.sh deleted file mode 100644 index 118786d7d..000000000 --- a/model/merge_netcdf_6_arcmin_example_bash.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -x - -MAIN_OUTPUT_DIR="/scratch/ms/copext/cyes/pcrglobwb_output_version_2020-08-10_example/first_test_54_clones/" - -python merge_netcdf_6_arcmin_ulysses.py ${MAIN_OUTPUT_DIR} ${MAIN_OUTPUT_DIR}/global/netcdf outDailyTotNC 1981-01-01 1981-01-31 ulyssesP,ulyssesET,ulyssesSWE,ulyssesQsm,ulyssesSM,ulyssesQrRunoff,ulyssesDischarge NETCDF4 False 1 Global - -set +x diff --git a/model/merge_netcdf_6_arcmin_ulysses.py b/model/merge_netcdf_6_arcmin_ulysses.py deleted file mode 100644 index 74e054746..000000000 --- a/model/merge_netcdf_6_arcmin_ulysses.py +++ /dev/null @@ -1,504 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import print_function - -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -#-extracts data from partial netCDF output via arrays - -#-modules -import os, sys -import time as tm -import numpy as np -import netCDF4 as nc -import datetime -import glob -from multiprocessing import Pool -import calendar -from dateutil.relativedelta import * - -# file cache to minimize/reduce opening/closing files. -filecache = dict() - -def calculate_monthdelta(date1, date2): - def is_last_day_of_the_month(date): - days_in_month = calendar.monthrange(date.year, date.month)[1] - return date.day == days_in_month - imaginary_day_2 = 31 if is_last_day_of_the_month(date2) else date2.day - monthdelta = ( - (date2.month - date1.month) + - (date2.year - date1.year) * 12 + - (-1 if date1.day > imaginary_day_2 else 0) - ) - return monthdelta - -def getMax(x,a): - m= float(a.max()) - if x == None: - return m - else: - return max(m,x) - -def getMin(x,a): - m= float(a.min()) - if x == None: - return m - else: - return min(m,x) - -def netcdfList(inputDir): - '''creates a dictionary of netcdf files''' - netcdfList = glob.glob(os.path.join(inputDir, '*.nc')) - print(inputDir) - print(os.path.join(inputDir, '*.nc')) - ll=[] - for ncFile in netcdfList: - ll.append(ncFile.split('/')[-1]) - return ll - -def ncFileNameDict(inputDirRoot, areas, ncFileName): - '''creates a dictionary of subdomains of pcrglob model outut''' - netcdfInputDict = {} - for key in range(1, len(areas)+1, 1): - value = os.path.join(inputDirRoot, areas[key-1], 'netcdf', ncFileName) - netcdfInputDict[key] = value - return netcdfInputDict - -def mergeNetCDF(inputTuple): - - ncName = inputTuple[0] - latMin = inputTuple[1] - latMax = inputTuple[2] - lonMin = inputTuple[3] - lonMax = inputTuple[4] - deltaLat = inputTuple[5] - deltaLon = inputTuple[6] - - startDate = inputTuple[7] - endDate = inputTuple[8] - - print('combining files for %s'%ncName) - scriptStartTime = tm.time() - - # - dictionary holding netCDFInput - netCDFInput = ncFileNameDict(inputDirRoot, areas, ncName) - - # - netDCF output file name - netCDFOutput = outputDir + "/" + ncName.split(".")[0] + "_" + startDate + "_to_" + endDate + ".nc" - - print(netCDFOutput) - - #~ ncFormat = 'NETCDF3_CLASSIC' - #~ ncFormat = 'NETCDF4' - ncFormat = inputTuple[9] - - # option to use zlib compression: - #~ using_zlib = True - #~ using_zlib = False # I decide not to compress (so that we can I analyze it quickly). - using_zlib = inputTuple[10] - if using_zlib == "True": using_zlib = True - - #-set dimensions, attributes, and dimensions per netCDF input data set - # and retrieve the resolution and definition of coordinates and calendar - attributes= {} - dimensions= {} - variables= {} - variableName = None - - calendar_used = {} - uniqueTimes = np.array([]) - - # defining time based on the given arguments - if startDate != None and endDate != None: - - # start time and end time - sd = str(startDate).split('-') - startTime = datetime.datetime(int(sd[0]), int(sd[1]), int(sd[2]), 0) - ed = str(endDate).split('-') - endTime = datetime.datetime(int(ed[0]), int(ed[1]), int(ed[2]), 0) - - print(list(netCDFInput.values())[0]) - - # open the first netcdf file to get time units and time calendar - ncFile = list(netCDFInput.values())[0] - print(ncFile) - f = nc.Dataset(ncFile) - time_units = f.variables['time'].units - time_calendar = f.variables['time'].calendar - - # temporal resolution - timeStepType = "daily" - if len(f.variables['time']) > 1: - if (f.variables['time'][1] - f.variables['time'][0]) > 25.0: timeStepType = "monthly" - if (f.variables['time'][1] - f.variables['time'][0]) > 305.0: timeStepType = "yearly" - else: - timeStepType = "single" - - f.close() - - if timeStepType == "daily": - number_of_days = (endTime - startTime).days + 1 - datetime_range = [startTime + datetime.timedelta(days = x) for x in range(0, number_of_days)] - - if timeStepType == "monthly": - number_of_months = calculate_monthdelta(startTime, endTime + datetime.timedelta(days = 1)) + 1 - datetime_range = [startTime + relativedelta(months =+x) for x in range(0, number_of_months)] - # make sure that datetime_range values always at the last day of the month: - for i in range(0, len(datetime_range)): - year_used = datetime_range[i].year - month_used = datetime_range[i].month - day_used = calendar.monthrange(year_used, month_used)[1] - datetime_range[i] = datetime.datetime(int(year_used), int(month_used), int(day_used), 0) - - if timeStepType == "yearly": - number_of_years = endTime.year - startTime.year + 1 - datetime_range = [startTime + relativedelta(years =+x) for x in range(0, number_of_years)] - # make sure that datetime_range values always at the last day of the year: - for i in range(0, len(datetime_range)): - year_used = datetime_range[i].year - month_used = 12 - day_used = 31 - datetime_range[i] = datetime.datetime(int(year_used), int(month_used), int(day_used), 0) - - if timeStepType == "single": - datetime_range = [startTime] - - # time variables that will be used (using numerical values) - uniqueTimes = nc.date2num(datetime_range, time_units, time_calendar) - - print(timeStepType) - print(datetime_range) - print(uniqueTimes) - - for ncFile in list(netCDFInput.values()): - - # open netCDF file - if ncFile in list(filecache.keys()): - rootgrp = filecache[ncFile] - print("Cached: ", ncFile) - else: - rootgrp = nc.Dataset(ncFile) - filecache[ncFile] = rootgrp - print("New: ", ncFile) - - # and get index - index = list(netCDFInput.keys())[list(netCDFInput.values()).index(ncFile)] - - # retrieve dimensions, atributes, variables, and missing value - dimensions[index]= rootgrp.dimensions.copy() - variables[index]= rootgrp.variables.copy() - attributes[index]= rootgrp.__dict__.copy() - - #-set new values - for key in list(dimensions[index].keys()): - if 'lat' in key.lower(): - latVar= key - if 'lon' in key.lower(): - lonVar= key - latMin= getMin(latMin,variables[index][latVar][:]) - latMax= getMax(latMax,variables[index][latVar][:]) - lonMin= getMin(lonMin,variables[index][lonVar][:]) - lonMax= getMax(lonMax,variables[index][lonVar][:]) - - #-assign calendar (used) - if 'time' in list(variables[index].keys()): - for name in variables[index]['time'].ncattrs(): - if name not in list(calendar_used.keys()): - calendar_used[name]= getattr(variables[index]['time'],name) - else: - if getattr(variables[index]['time'],name) != calendar_used[name]: - rootgrp.close() - sys.exit('calendars are incompatible') - #-time - if uniqueTimes.size == 0: - uniqueTimes= variables[index]['time'][:] - #~ else: - #~ uniqueTimes= np.unique(np.c_[uniqueTimes[:],variables[index]['time'][:]]) - uniqueTimes.sort() - keys= list(variables[index].keys()) - for key in list(dimensions[index].keys()): - if key in keys: - keys.remove(key) - key= keys[0] - if variableName == None: - variableName= key - else: - if key != variableName: - rootgrp.close() - sys.exit('variables are incompatible') - #-Missing Value - MV = rootgrp.variables[key]._FillValue - varUnits = rootgrp.variables[variableName].units - #-close file - rootgrp.close() - - #-create output netCDF - #~ longitudes= np.around(np.arange(lonMin,lonMax+deltaLon,deltaLon), decimals=4) - #~ latitudes= np.around(np.arange(latMax,latMin-deltaLat,-deltaLat), decimals=4) - - # - for the Uly project, we fixed the following - lonMin = round(lonMin, 2) - lonMax = round(lonMax, 2) - latMax = round(latMax, 2) - latMin = round(latMin, 2) - deltaLon = round(deltaLon, 2) - deltaLat = round(deltaLat, 2) - - longitudes= np.arange(lonMin,lonMax+deltaLon,deltaLon) - latitudes= np.arange(latMax,latMin-deltaLat,-deltaLat) - - #~ longitudes= np.linspace(lonMin,lonMax+deltaLon, int(round((lonMax+deltaLon - lonMin)/deltaLon))) - #~ latitudes= np.linspace(latMax,latMin-deltaLat, int(round((latMax - latMin+deltaLat)/deltaLat))) - - uniqueTimes= uniqueTimes.tolist() - - #-open file - rootgrp= nc.Dataset(netCDFOutput,'w',format= ncFormat) - - # - create time and set its attributes - date_time=rootgrp.createDimension('time',len(uniqueTimes)) - #~ date_time=rootgrp.createDimension('time', None) - date_time= rootgrp.createVariable('time','f8',('time',)) - for attr,value in list(calendar_used.items()): - setattr(date_time,attr,str(value)) - date_time[:]= uniqueTimes - - - #-create dimensions for longitudes and latitudes - rootgrp.createDimension('latitude',len(latitudes)) - rootgrp.createDimension('longitude',len(longitudes)) - lat= rootgrp.createVariable('latitude','f4',('latitude')) - lat.standard_name= 'Latitude' - lat.long_name= 'Latitude cell centres' - lon= rootgrp.createVariable('longitude','f4',('longitude')) - lon.standard_name= 'Longitude' - lon.long_name= 'Longitude cell centres' - - #-assing latitudes and longitudes to variables - lat[:]= latitudes - lon[:]= longitudes - - latitudes = np.around(latitudes, decimals=4) # TODO: Improve this. We need this one for selecting rows and columns. - longitudes = np.around(longitudes, decimals=4) # TODO: Improve this. We need this one for selecting rows and columns. - - - # - setting variable - if len(calendar_used) == 0: - varStructure= ('latitude','longitude') - else: - varStructure= ('time','latitude','longitude') - - variable = rootgrp.createVariable(variableName, 'f4', varStructure, fill_value = MV, zlib = using_zlib) - - # - set variable attributes and overall values - for index in list(attributes.keys()): - for name in variables[index][variableName].ncattrs(): - try: - setattr(variable,name,str(getattr(variables[index][variableName],name))) - except: - pass - for attr,value in list(attributes[index].items()): - setattr(rootgrp,attr,str(value)) - - #-write to file - rootgrp.sync() - rootgrp.close() - - #NOTE: this assumes it is a timed variable! - #-iterate over time steps and retrieve values - - print('nr of time steps = %s, nr of files = %s ' % (len(uniqueTimes), len(netCDFInput))) - i_time = 0 - for time in uniqueTimes[:]: - - - #~ print 'processing %s for time index %.0d' %(ncName, 1+ time - min(uniqueTimes)) - #~ print 'processing %s for time %.0d' %(ncName, time) - - i_time = i_time + 1 - print('processing %s %i from %i' %(ncName, i_time, len(uniqueTimes))) - - #-create empty field to fill - variableArray= np.ones((len(latitudes),len(longitudes)))*MV - - #-iterate over input netCDFs - for ncFile in list(netCDFInput.values()): - #-open netCDF file and get dictionary index - rootgrp= nc.Dataset(ncFile,'r',format= ncFormat) - index= list(netCDFInput.keys())[list(netCDFInput.values()).index(ncFile)] - #-retrieve posCnt and process - #-get row and column indices from lats and lons - for key in list(dimensions[index].keys()): - if 'lat' in key.lower(): - latVar= key - if 'lon' in key.lower(): - lonVar= key - latMaxNcFile = round(getMax(latMin,variables[index][latVar][:]),4) - latMinNcFile = round(getMin(latMax,variables[index][latVar][:]),4) - lonMinNcFile = round(getMin(lonMax,variables[index][lonVar][:]),4) - lonMaxNcFile = round(getMax(lonMin,variables[index][lonVar][:]),4) - - row0= np.where(latitudes == min(latMax,latMaxNcFile))[0][0] - row1= np.where(latitudes == max(latMin,latMinNcFile))[0][0]+1 - col0= np.where(longitudes == max(lonMin,lonMinNcFile))[0][0] - col1= np.where(longitudes == min(lonMax,lonMaxNcFile))[0][0]+1 - - posCnt= None - try: - - #~ # find the correct index (old method) - this is very slow - #~ posCnt= variables[index]['time'][:].tolist().index(time) - - # find the correct index (new method) - date_value = nc.num2date(time, rootgrp.variables['time'].units, rootgrp.variables['time'].calendar) - posCnt = nc.date2index(date_value, rootgrp.variables['time']) - - sampleArray= rootgrp.variables[variableName][posCnt,:,:] - sampleArray[sampleArray == variables[index][variableName]._FillValue]= MV - variableArray[row0:row1,col0:col1][variableArray[row0:row1,col0:col1] == MV]= \ - sampleArray[variableArray[row0:row1,col0:col1] == MV] - - print('time is present :' + str(date_value)) - - except: - if posCnt == None: - print('time not present') - else: - print('error in resampled') - #-close - rootgrp.close() - - #-write array to destination netCDF - posCnt= uniqueTimes.index(time) - rootgrp= nc.Dataset(netCDFOutput,'a',format= ncFormat) - rootgrp.variables[variableName][posCnt,:,:]= variableArray - variable.units = str(varUnits) - rootgrp.sync() - rootgrp.close() - - secs = int(tm.time() - scriptStartTime) - print("Processing %s took %s hh:mm:ss\n" % (ncName, str(datetime.timedelta(seconds=secs)))) - - -################################## -######## user input ############## -################################## - -# latitudes and longitudes: -#~ # - 5 arcmin -#~ deltaLat = 5.0/60.0 -#~ deltaLon = 5.0/60.0 -# - 6 arcmin -deltaLat = 0.1 -deltaLon = 0.1 - -#~ latMin = -90 + deltaLat / 2 -#~ latMax = 90 - deltaLat / 2 -latMin = -56 + deltaLat / 2 -latMax = 84 - deltaLat / 2 -lonMin = -180 + deltaLon / 2 -lonMax = 180 - deltaLon / 2 - -# ~ cyes@cca-login4:/lus/snx11062/scratch/ms/copext/cyes/data/pcrglobwb_input_ulysses/develop/global_06min/cloneMaps/global_land_mask/version_2020-08-11> cdo griddes land_mask.nc -# ~ # -# ~ # gridID 1 -# ~ # -# ~ gridtype = generic -# ~ gridsize = 5040000 -# ~ xsize = 3600 -# ~ ysize = 1400 -# ~ xname = longitude -# ~ yname = latitude -# ~ xfirst = -179.95 -# ~ xinc = 0.1 -# ~ yfirst = 83.95 -# ~ yinc = -0.1 -# ~ cdo griddes: Processed 3 variables [0.07s 25MB]. - - -# input directory: -inputDirRoot = sys.argv[1] - -outputDir = sys.argv[2] - -# making outputDir -try: - os.makedirs(outputDir) -except: - pass - -# file_type, options are: outDailyTot, outMonthTot, outMonthAvg, outMonthEnd, outAnnuaTot, outAnnuaAvg, outAnnuaEnd -file_type = str(sys.argv[3]) - -# starting and end dates -startDate = str(sys.argv[4]) -endDate = str(sys.argv[5]) - -# list of netcdf files that will be merged: -netcdfList = str(sys.argv[6]) -print(netcdfList) -netcdfList = list(set(netcdfList.split(","))) -if file_type == "outDailyTotNC": netcdfList = ['%s_dailyTot_output.nc'%var for var in netcdfList] -if file_type == "outMonthTotNC": netcdfList = ['%s_monthTot_output.nc'%var for var in netcdfList] -if file_type == "outMonthAvgNC": netcdfList = ['%s_monthAvg_output.nc'%var for var in netcdfList] -if file_type == "outMonthEndNC": netcdfList = ['%s_monthEnd_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaTotNC": netcdfList = ['%s_annuaTot_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaAvgNC": netcdfList = ['%s_annuaAvg_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaEndNC": netcdfList = ['%s_annuaEnd_output.nc'%var for var in netcdfList] - -if file_type == "outMonthMaxNC": netcdfList = ['%s_monthMax_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaMaxNC": netcdfList = ['%s_annuaMax_output.nc'%var for var in netcdfList] - -# netcdf format and zlib option: -ncFormat = str(sys.argv[7]) -using_zlib = str(sys.argv[8]) - -# maximum number of cores that will be used -max_number_of_cores = int(sys.argv[9]) - -# number of cores that will be used -ncores = min(len(netcdfList), max_number_of_cores) - -# clone areas -areas = str(sys.argv[10]) -if areas == "Global": - areas = ['M%07d'%i for i in range(1,72,1)] -else: - areas = list(set(areas.split(","))) - -# extent of the global map -if sys.argv[11] == "all_lats": - latMin = -90 + deltaLat / 2 - latMax = 90 - deltaLat / 2 - -#~ # for testing, we use only a single core -#~ mergeNetCDF((netcdfList[0], latMin, latMax, lonMin, lonMax, deltaLat, deltaLon, startDate, endDate, ncFormat, using_zlib)) - -ll = [] -for ncName in netcdfList: - ll.append((ncName, latMin, latMax, lonMin, lonMax, deltaLat, deltaLon, startDate, endDate, ncFormat, using_zlib)) -pool = Pool(processes = ncores) # start "ncores" of worker processes -pool.map(mergeNetCDF, ll) # multicore processing - -pool.terminate() -pool.join() diff --git a/model/merge_netcdf_aqueduct_2021.sh b/model/merge_netcdf_aqueduct_2021.sh deleted file mode 100644 index 903e5889b..000000000 --- a/model/merge_netcdf_aqueduct_2021.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -x - -python merge_netcdf_general.py /scratch/depfg/sutan101/pcrglobwb_aqueduct_2021/version_2021-05-03_updated_gmd_parameters /scratch/depfg/sutan101/pcrglobwb_aqueduct_2021/version_2021-05-03_updated_gmd_parameters/global/netcdf/ outMonthTotNC 1979-01-31 2016-12-31 actualET,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,precipitation,gwRecharge,surfaceWaterInf,totalEvaporation,totalPotentialEvaporation,referencePotET NETCDF4 True 48 53 all_lats & - -python merge_netcdf_general.py /scratch/depfg/sutan101/pcrglobwb_aqueduct_2021/version_2021-05-03_updated_gmd_parameters /scratch/depfg/sutan101/pcrglobwb_aqueduct_2021/version_2021-05-03_updated_gmd_parameters/global/netcdf/ outMonthAvgNC 1979-01-31 2016-12-31 discharge,temperature,dynamicFracWat,surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalActiveStorageThickness,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage NETCDF4 True 48 53 all_lats & - -wait - -set +x - - - diff --git a/model/merge_netcdf_europe.py b/model/merge_netcdf_europe.py deleted file mode 100644 index ec3597ab9..000000000 --- a/model/merge_netcdf_europe.py +++ /dev/null @@ -1,552 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import print_function - -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -#-extracts data from partial netCDF output via arrays - -#-modules -import os, sys -import time as tm -import numpy as np -import netCDF4 as nc -import datetime -import glob -from multiprocessing import Pool -import calendar -from dateutil.relativedelta import * - -# file cache to minimize/reduce opening/closing files. -filecache = dict() - -def calculate_monthdelta(date1, date2): - def is_last_day_of_the_month(date): - days_in_month = calendar.monthrange(date.year, date.month)[1] - return date.day == days_in_month - imaginary_day_2 = 31 if is_last_day_of_the_month(date2) else date2.day - monthdelta = ( - (date2.month - date1.month) + - (date2.year - date1.year) * 12 + - (-1 if date1.day > imaginary_day_2 else 0) - ) - return monthdelta - -def getMax(x,a): - m= float(a.max()) - if x == None: - return m - else: - return max(m,x) - -def getMin(x,a): - m= float(a.min()) - if x == None: - return m - else: - return min(m,x) - -def netcdfList(inputDir): - '''creates a dictionary of netcdf files''' - netcdfList = glob.glob(os.path.join(inputDir, '*.nc')) - print(inputDir) - print(os.path.join(inputDir, '*.nc')) - ll=[] - for ncFile in netcdfList: - ll.append(ncFile.split('/')[-1]) - return ll - -def ncFileNameDict(inputDirRoot, areas, ncFileName): - '''creates a dictionary of subdomains of pcrglob model outut''' - netcdfInputDict = {} - for key in range(1, len(areas)+1, 1): - value = os.path.join(inputDirRoot, areas[key-1], 'netcdf', ncFileName) - netcdfInputDict[key] = value - return netcdfInputDict - -def mergeNetCDF(inputTuple): - - ncName = inputTuple[0] - latMin = inputTuple[1] - latMax = inputTuple[2] - lonMin = inputTuple[3] - lonMax = inputTuple[4] - deltaLat = inputTuple[5] - deltaLon = inputTuple[6] - - startDate = inputTuple[7] - endDate = inputTuple[8] - - print('combining files for %s'%ncName) - scriptStartTime = tm.time() - - # - dictionary holding netCDFInput - netCDFInput = ncFileNameDict(inputDirRoot, areas, ncName) - - # - netDCF output file name - netCDFOutput = outputDir + "/" + ncName.split(".")[0] + "_" + startDate + "_to_" + endDate + ".nc" - - print(netCDFOutput) - - #~ ncFormat = 'NETCDF3_CLASSIC' - #~ ncFormat = 'NETCDF4' - ncFormat = inputTuple[9] - - # option to use zlib compression: - #~ using_zlib = True - #~ using_zlib = False # I decide not to compress (so that we can I analyze it quickly). - using_zlib = inputTuple[10] - if using_zlib == "True": using_zlib = True - - #-set dimensions, attributes, and dimensions per netCDF input data set - # and retrieve the resolution and definition of coordinates and calendar - attributes= {} - dimensions= {} - variables= {} - variableName = None - - calendar_used = {} - uniqueTimes = np.array([]) - - # defining time based on the given arguments - if startDate != None and endDate != None: - - # start time and end time - sd = str(startDate).split('-') - startTime = datetime.datetime(int(sd[0]), int(sd[1]), int(sd[2]), 0) - ed = str(endDate).split('-') - endTime = datetime.datetime(int(ed[0]), int(ed[1]), int(ed[2]), 0) - - print(list(netCDFInput.values())[0]) - - # open the first netcdf file to get time units and time calendar - ncFile = list(netCDFInput.values())[0] - print(ncFile) - f = nc.Dataset(ncFile) - time_units = f.variables['time'].units - time_calendar = f.variables['time'].calendar - - # temporal resolution - timeStepType = "daily" - if len(f.variables['time']) > 1: - if (f.variables['time'][1] - f.variables['time'][0]) > 25.0: timeStepType = "monthly" - if (f.variables['time'][1] - f.variables['time'][0]) > 305.0: timeStepType = "yearly" - else: - timeStepType = "single" - - f.close() - - if timeStepType == "daily": - number_of_days = (endTime - startTime).days + 1 - datetime_range = [startTime + datetime.timedelta(days = x) for x in range(0, number_of_days)] - - if timeStepType == "monthly": - number_of_months = calculate_monthdelta(startTime, endTime + datetime.timedelta(days = 1)) + 1 - datetime_range = [startTime + relativedelta(months =+x) for x in range(0, number_of_months)] - # make sure that datetime_range values always at the last day of the month: - for i in range(0, len(datetime_range)): - year_used = datetime_range[i].year - month_used = datetime_range[i].month - day_used = calendar.monthrange(year_used, month_used)[1] - datetime_range[i] = datetime.datetime(int(year_used), int(month_used), int(day_used), 0) - - if timeStepType == "yearly": - number_of_years = endTime.year - startTime.year + 1 - datetime_range = [startTime + relativedelta(years =+x) for x in range(0, number_of_years)] - # make sure that datetime_range values always at the last day of the year: - for i in range(0, len(datetime_range)): - year_used = datetime_range[i].year - month_used = 12 - day_used = 31 - datetime_range[i] = datetime.datetime(int(year_used), int(month_used), int(day_used), 0) - - if timeStepType == "single": - datetime_range = [startTime] - - # time variables that will be used (using numerical values) - uniqueTimes = nc.date2num(datetime_range, time_units, time_calendar) - - print(timeStepType) - print(datetime_range) - print(uniqueTimes) - - for ncFile in list(netCDFInput.values()): - - # open netCDF file - if ncFile in list(filecache.keys()): - rootgrp = filecache[ncFile] - print("Cached: ", ncFile) - else: - rootgrp = nc.Dataset(ncFile) - filecache[ncFile] = rootgrp - print("New: ", ncFile) - - # and get index - index = list(netCDFInput.keys())[list(netCDFInput.values()).index(ncFile)] - - # retrieve dimensions, atributes, variables, and missing value - dimensions[index]= rootgrp.dimensions.copy() - variables[index]= rootgrp.variables.copy() - attributes[index]= rootgrp.__dict__.copy() - - #-set new values - for key in list(dimensions[index].keys()): - if 'lat' in key.lower(): - latVar= key - if 'lon' in key.lower(): - lonVar= key - latMin= getMin(latMin,variables[index][latVar][:]) - latMax= getMax(latMax,variables[index][latVar][:]) - lonMin= getMin(lonMin,variables[index][lonVar][:]) - lonMax= getMax(lonMax,variables[index][lonVar][:]) - - #-assign calendar (used) - if 'time' in list(variables[index].keys()): - for name in variables[index]['time'].ncattrs(): - if name not in list(calendar_used.keys()): - calendar_used[name]= getattr(variables[index]['time'],name) - else: - if getattr(variables[index]['time'],name) != calendar_used[name]: - rootgrp.close() - sys.exit('calendars are incompatible') - #-time - if uniqueTimes.size == 0: - uniqueTimes= variables[index]['time'][:] - #~ else: - #~ uniqueTimes= np.unique(np.c_[uniqueTimes[:],variables[index]['time'][:]]) - uniqueTimes.sort() - keys= list(variables[index].keys()) - for key in list(dimensions[index].keys()): - if key in keys: - keys.remove(key) - key= keys[0] - if variableName == None: - variableName= key - else: - if key != variableName: - rootgrp.close() - sys.exit('variables are incompatible') - #-Missing Value - MV = rootgrp.variables[key]._FillValue - varUnits = rootgrp.variables[variableName].units - #-close file - rootgrp.close() - - #-create output netCDF - longitudes= np.around(np.arange(lonMin,lonMax+deltaLon,deltaLon), decimals=4) - latitudes= np.around(np.arange(latMax,latMin-deltaLat,-deltaLat), decimals=4) - - # ~ # - for the Uly project, we fixed the following - # ~ lonMin = round(lonMin, 2) - # ~ lonMax = round(lonMax, 2) - # ~ latMax = round(latMax, 2) - # ~ latMin = round(latMin, 2) - # ~ deltaLon = round(deltaLon, 2) - # ~ deltaLat = round(deltaLat, 2) - - # ~ longitudes= np.arange(lonMin,lonMax+deltaLon,deltaLon) - # ~ latitudes= np.arange(latMax,latMin-deltaLat,-deltaLat) - - #~ longitudes= np.linspace(lonMin,lonMax+deltaLon, int(round((lonMax+deltaLon - lonMin)/deltaLon))) - #~ latitudes= np.linspace(latMax,latMin-deltaLat, int(round((latMax - latMin+deltaLat)/deltaLat))) - - uniqueTimes= uniqueTimes.tolist() - - #-open file - rootgrp= nc.Dataset(netCDFOutput,'w',format= ncFormat) - - # - create time and set its attributes - date_time=rootgrp.createDimension('time',len(uniqueTimes)) - #~ date_time=rootgrp.createDimension('time', None) - date_time= rootgrp.createVariable('time','f8',('time',)) - for attr,value in list(calendar_used.items()): - setattr(date_time,attr,str(value)) - date_time[:]= uniqueTimes - - - #-create dimensions for longitudes and latitudes - rootgrp.createDimension('latitude',len(latitudes)) - rootgrp.createDimension('longitude',len(longitudes)) - lat= rootgrp.createVariable('latitude','f4',('latitude')) - lat.standard_name= 'Latitude' - lat.long_name= 'Latitude cell centres' - lon= rootgrp.createVariable('longitude','f4',('longitude')) - lon.standard_name= 'Longitude' - lon.long_name= 'Longitude cell centres' - - #-assing latitudes and longitudes to variables - lat[:]= latitudes - lon[:]= longitudes - - latitudes = np.around(latitudes, decimals=4) # TODO: Improve this. We need this one for selecting rows and columns. - longitudes = np.around(longitudes, decimals=4) # TODO: Improve this. We need this one for selecting rows and columns. - - - # - setting variable - if len(calendar_used) == 0: - varStructure= ('latitude','longitude') - else: - varStructure= ('time','latitude','longitude') - - variable = rootgrp.createVariable(variableName, 'f4', varStructure, fill_value = MV, zlib = using_zlib) - - # - set variable attributes and overall values - for index in list(attributes.keys()): - for name in variables[index][variableName].ncattrs(): - try: - setattr(variable,name,str(getattr(variables[index][variableName],name))) - except: - pass - for attr,value in list(attributes[index].items()): - setattr(rootgrp,attr,str(value)) - - #-write to file - rootgrp.sync() - rootgrp.close() - - #NOTE: this assumes it is a timed variable! - #-iterate over time steps and retrieve values - - print('nr of time steps = %s, nr of files = %s ' % (len(uniqueTimes), len(netCDFInput))) - i_time = 0 - for time in uniqueTimes[:]: - - - #~ print 'processing %s for time index %.0d' %(ncName, 1+ time - min(uniqueTimes)) - #~ print 'processing %s for time %.0d' %(ncName, time) - - i_time = i_time + 1 - print('processing %s %i from %i' %(ncName, i_time, len(uniqueTimes))) - - #-create empty field to fill - variableArray= np.ones((len(latitudes),len(longitudes)))*MV - - #-iterate over input netCDFs - for ncFile in list(netCDFInput.values()): - #-open netCDF file and get dictionary index - rootgrp= nc.Dataset(ncFile,'r',format= ncFormat) - index= list(netCDFInput.keys())[list(netCDFInput.values()).index(ncFile)] - #-retrieve posCnt and process - #-get row and column indices from lats and lons - for key in list(dimensions[index].keys()): - if 'lat' in key.lower(): - latVar= key - if 'lon' in key.lower(): - lonVar= key - latMaxNcFile = round(getMax(latMin,variables[index][latVar][:]),4) - latMinNcFile = round(getMin(latMax,variables[index][latVar][:]),4) - lonMinNcFile = round(getMin(lonMax,variables[index][lonVar][:]),4) - lonMaxNcFile = round(getMax(lonMin,variables[index][lonVar][:]),4) - - # ~ latMaxNcFile = round(getMax(latMin,variables[index][latVar][:]),20) - # ~ latMinNcFile = round(getMin(latMax,variables[index][latVar][:]),20) - # ~ lonMinNcFile = round(getMin(lonMax,variables[index][lonVar][:]),20) - # ~ lonMaxNcFile = round(getMax(lonMin,variables[index][lonVar][:]),20) - - # ~ print(latMaxNcFile) - # ~ print(latMinNcFile) - # ~ print(lonMinNcFile) - # ~ print(lonMaxNcFile) - - row0= int( np.where(latitudes == min(latMax,latMaxNcFile))[0][0] ) - row1= int( np.where(latitudes == max(latMin,latMinNcFile))[0][0]+1) - col0= int(np.where(longitudes == max(lonMin,lonMinNcFile))[0][0] ) - col1= int(np.where(longitudes == min(lonMax,lonMaxNcFile))[0][0]+1) - - # ~ minY = min(abs(latitudes - min(latMax,latMaxNcFile))) - # ~ row0 = int(np.where(abs(latitudes - min(latMax,latMaxNcFile)) == minY)[0]) - - # ~ maxY = min(abs(latitudes - max(latMax,latMaxNcFile))) - # ~ row1 = int(np.where(abs(latitudes - max(latMax,latMaxNcFile)) == maxY)[0]) + 1 - - # ~ maxX = min(abs(longitudes - max(lonMin,lonMinNcFile))) - # ~ col0 = int(np.where(abs(longitudes - max(lonMin,lonMinNcFile)) == maxX)[0]) - - # ~ minX = min(abs(longitudes - min(lonMin,lonMinNcFile))) - # ~ col1 = int(np.where(abs(longitudes - max(lonMin,lonMinNcFile)) == minX)[0]) + 1 - - # ~ print(row0) - # ~ print(row1) - # ~ print(col0) - # ~ print(col1) - - posCnt= None - try: - - # ~ # find the correct index (old method) - this is very slow - # ~ posCnt= variables[index]['time'][:].tolist().index(time) - - # find the correct index (new method) - date_value = nc.num2date(time, rootgrp.variables['time'].units, rootgrp.variables['time'].calendar) - posCnt = nc.date2index(date_value, rootgrp.variables['time']) - - print(date_value) - print(posCnt) - - sampleArray= rootgrp.variables[variableName][posCnt,:,:] - - print(sampleArray) - - sampleArray[sampleArray == variables[index][variableName]._FillValue]= MV - variableArray[row0:row1,col0:col1][variableArray[row0:row1,col0:col1] == MV]= \ - sampleArray[variableArray[row0:row1,col0:col1] == MV] - - print('time is present :' + str(date_value)) - - except: - if posCnt == None: - print('time not present') - else: - print('error in resampled') - #-close - rootgrp.close() - - #-write array to destination netCDF - posCnt= uniqueTimes.index(time) - rootgrp= nc.Dataset(netCDFOutput,'a',format= ncFormat) - rootgrp.variables[variableName][posCnt,:,:]= variableArray - variable.units = str(varUnits) - rootgrp.sync() - rootgrp.close() - - secs = int(tm.time() - scriptStartTime) - print("Processing %s took %s hh:mm:ss\n" % (ncName, str(datetime.timedelta(seconds=secs)))) - - -################################## -######## user input ############## -################################## - -# latitudes and longitudes: -#~ # - 5 arcmin -#~ deltaLat = 5.0/60.0 -#~ deltaLon = 5.0/60.0 -# - 6 arcmin -deltaLat = 0.1 -deltaLon = 0.1 - -#~ latMin = -90 + deltaLat / 2 -#~ latMax = 90 - deltaLat / 2 -latMin = -56 + deltaLat / 2 -latMax = 84 - deltaLat / 2 -lonMin = -180 + deltaLon / 2 -lonMax = 180 - deltaLon / 2 - - -# europe 30sec -deltaLat = 0.00833333333333333333333333333333333333333333333333333333333 -deltaLon = 0.00833333333333333333333333333333333333333333333333333333333 -latMin = 33 + deltaLat / 2 -latMax = 73 - deltaLat / 2 -lonMin = -11 + deltaLon / 2 -lonMax = 42 - deltaLon / 2 - -# ~ (pcrglobwb_py3_env_v20200309_with_cdsapi) edwinari@tcn858.bullx:/lustre1/0/einf1079/edwin/pcrglobwb_output_europe/europe_30sec/version_2021-04-20$ gdalinfo global_subdomains_final.map -# ~ Driver: PCRaster/PCRaster Raster File -# ~ Files: global_subdomains_final.map -# ~ Size is 6360, 4800 -# ~ Origin = (-11.000000000000000,73.000000000000000) -# ~ Pixel Size = (0.008333333333333,-0.008333333333333) -# ~ Metadata: - # ~ PCRASTER_VALUESCALE=VS_NOMINAL -# ~ Corner Coordinates: -# ~ Upper Left ( -11.0000000, 73.0000000) -# ~ Lower Left ( -11.0000000, 33.0000000) -# ~ Upper Right ( 42.0000000, 73.0000000) -# ~ Lower Right ( 42.0000000, 33.0000000) -# ~ Center ( 15.5000000, 53.0000000) -# ~ Band 1 Block=6360x1 Type=Int32, ColorInterp=Undefined - # ~ Min=1.000 Max=45.000 - # ~ NoData Value=-2147483647 - - - - -# input directory: -inputDirRoot = sys.argv[1] - -outputDir = sys.argv[2] - -# making outputDir -try: - os.makedirs(outputDir) -except: - pass - -# file_type, options are: outDailyTot, outMonthTot, outMonthAvg, outMonthEnd, outAnnuaTot, outAnnuaAvg, outAnnuaEnd -file_type = str(sys.argv[3]) - -# starting and end dates -startDate = str(sys.argv[4]) -endDate = str(sys.argv[5]) - -# list of netcdf files that will be merged: -netcdfList = str(sys.argv[6]) -print(netcdfList) -netcdfList = list(set(netcdfList.split(","))) -if file_type == "outDailyTotNC": netcdfList = ['%s_dailyTot_output.nc'%var for var in netcdfList] -if file_type == "outMonthTotNC": netcdfList = ['%s_monthTot_output.nc'%var for var in netcdfList] -if file_type == "outMonthAvgNC": netcdfList = ['%s_monthAvg_output.nc'%var for var in netcdfList] -if file_type == "outMonthEndNC": netcdfList = ['%s_monthEnd_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaTotNC": netcdfList = ['%s_annuaTot_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaAvgNC": netcdfList = ['%s_annuaAvg_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaEndNC": netcdfList = ['%s_annuaEnd_output.nc'%var for var in netcdfList] - -if file_type == "outMonthMaxNC": netcdfList = ['%s_monthMax_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaMaxNC": netcdfList = ['%s_annuaMax_output.nc'%var for var in netcdfList] - -# netcdf format and zlib option: -ncFormat = str(sys.argv[7]) -using_zlib = str(sys.argv[8]) - -# maximum number of cores that will be used -max_number_of_cores = int(sys.argv[9]) - -# number of cores that will be used -ncores = min(len(netcdfList), max_number_of_cores) - -# clone areas -areas = str(sys.argv[10]) -if areas == "Global": - areas = ['M%07d'%i for i in range(1,72,1)] -elif areas == "europe_30sec": - areas = ['M%07d'%i for i in range(1,46,1)] -else: - areas = list(set(areas.split(","))) - -# extent of the global map -if sys.argv[11] == "all_lats": - latMin = -90 + deltaLat / 2 - latMax = 90 - deltaLat / 2 - -#~ # for testing, we use only a single core -#~ mergeNetCDF((netcdfList[0], latMin, latMax, lonMin, lonMax, deltaLat, deltaLon, startDate, endDate, ncFormat, using_zlib)) - -ll = [] -for ncName in netcdfList: - ll.append((ncName, latMin, latMax, lonMin, lonMax, deltaLat, deltaLon, startDate, endDate, ncFormat, using_zlib)) -pool = Pool(processes = ncores) # start "ncores" of worker processes -pool.map(mergeNetCDF, ll) # multicore processing - -pool.terminate() -pool.join() diff --git a/model/merge_netcdf_general.py b/model/merge_netcdf_general.py deleted file mode 100644 index 92dad9c84..000000000 --- a/model/merge_netcdf_general.py +++ /dev/null @@ -1,573 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import print_function - -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -#-extracts data from partial netCDF output via arrays - -#-modules -import os, sys -import time as tm -import numpy as np -import netCDF4 as nc -import datetime -import glob -from multiprocessing import Pool -import calendar -from dateutil.relativedelta import * - -# file cache to minimize/reduce opening/closing files. -filecache = dict() - -def calculate_monthdelta(date1, date2): - def is_last_day_of_the_month(date): - days_in_month = calendar.monthrange(date.year, date.month)[1] - return date.day == days_in_month - imaginary_day_2 = 31 if is_last_day_of_the_month(date2) else date2.day - monthdelta = ( - (date2.month - date1.month) + - (date2.year - date1.year) * 12 + - (-1 if date1.day > imaginary_day_2 else 0) - ) - return monthdelta - -def getMax(x,a): - m= float(a.max()) - if x == None: - return m - else: - return max(m,x) - -def getMin(x,a): - m= float(a.min()) - if x == None: - return m - else: - return min(m,x) - -def netcdfList(inputDir): - '''creates a dictionary of netcdf files''' - netcdfList = glob.glob(os.path.join(inputDir, '*.nc')) - print(inputDir) - print(os.path.join(inputDir, '*.nc')) - ll=[] - for ncFile in netcdfList: - ll.append(ncFile.split('/')[-1]) - return ll - -def ncFileNameDict(inputDirRoot, areas, ncFileName): - '''creates a dictionary of subdomains of pcrglob model outut''' - netcdfInputDict = {} - for key in range(1, len(areas)+1, 1): - value = os.path.join(inputDirRoot, areas[key-1], 'netcdf', ncFileName) - netcdfInputDict[key] = value - return netcdfInputDict - -def mergeNetCDF(inputTuple): - - ncName = inputTuple[0] - latMin = inputTuple[1] - latMax = inputTuple[2] - lonMin = inputTuple[3] - lonMax = inputTuple[4] - deltaLat = inputTuple[5] - deltaLon = inputTuple[6] - - startDate = inputTuple[7] - endDate = inputTuple[8] - - print('combining files for %s'%ncName) - scriptStartTime = tm.time() - - # - dictionary holding netCDFInput - netCDFInput = ncFileNameDict(inputDirRoot, areas, ncName) - - # - netDCF output file name - netCDFOutput = outputDir + "/" + ncName.split(".")[0] + "_" + startDate + "_to_" + endDate + ".nc" - - print(netCDFOutput) - - #~ ncFormat = 'NETCDF3_CLASSIC' - #~ ncFormat = 'NETCDF4' - ncFormat = inputTuple[9] - - # option to use zlib compression: - #~ using_zlib = True - #~ using_zlib = False # I decide not to compress (so that we can I analyze it quickly). - using_zlib = inputTuple[10] - if using_zlib == "True": using_zlib = True - - #-set dimensions, attributes, and dimensions per netCDF input data set - # and retrieve the resolution and definition of coordinates and calendar - attributes= {} - dimensions= {} - variables= {} - variableName = None - - calendar_used = {} - uniqueTimes = np.array([]) - - # defining time based on the given arguments - if startDate != None and endDate != None: - - # start time and end time - sd = str(startDate).split('-') - startTime = datetime.datetime(int(sd[0]), int(sd[1]), int(sd[2]), 0) - ed = str(endDate).split('-') - endTime = datetime.datetime(int(ed[0]), int(ed[1]), int(ed[2]), 0) - - print(list(netCDFInput.values())[0]) - - # open the first netcdf file to get time units and time calendar - ncFile = list(netCDFInput.values())[0] - print(ncFile) - f = nc.Dataset(ncFile) - time_units = f.variables['time'].units - time_calendar = f.variables['time'].calendar - - # temporal resolution - timeStepType = "daily" - if len(f.variables['time']) > 1: - if (f.variables['time'][1] - f.variables['time'][0]) > 25.0: timeStepType = "monthly" - if (f.variables['time'][1] - f.variables['time'][0]) > 305.0: timeStepType = "yearly" - else: - timeStepType = "single" - - f.close() - - if timeStepType == "daily": - number_of_days = (endTime - startTime).days + 1 - datetime_range = [startTime + datetime.timedelta(days = x) for x in range(0, number_of_days)] - - if timeStepType == "monthly": - number_of_months = calculate_monthdelta(startTime, endTime + datetime.timedelta(days = 1)) + 1 - datetime_range = [startTime + relativedelta(months =+x) for x in range(0, number_of_months)] - # make sure that datetime_range values always at the last day of the month: - for i in range(0, len(datetime_range)): - year_used = datetime_range[i].year - month_used = datetime_range[i].month - day_used = calendar.monthrange(year_used, month_used)[1] - datetime_range[i] = datetime.datetime(int(year_used), int(month_used), int(day_used), 0) - - if timeStepType == "yearly": - number_of_years = endTime.year - startTime.year + 1 - datetime_range = [startTime + relativedelta(years =+x) for x in range(0, number_of_years)] - # make sure that datetime_range values always at the last day of the year: - for i in range(0, len(datetime_range)): - year_used = datetime_range[i].year - month_used = 12 - day_used = 31 - datetime_range[i] = datetime.datetime(int(year_used), int(month_used), int(day_used), 0) - - if timeStepType == "single": - datetime_range = [startTime] - - # time variables that will be used (using numerical values) - uniqueTimes = nc.date2num(datetime_range, time_units, time_calendar) - - print(timeStepType) - print(datetime_range) - print(uniqueTimes) - - for ncFile in list(netCDFInput.values()): - - # open netCDF file - if ncFile in list(filecache.keys()): - rootgrp = filecache[ncFile] - print("Cached: ", ncFile) - else: - rootgrp = nc.Dataset(ncFile) - filecache[ncFile] = rootgrp - print("New: ", ncFile) - - # and get index - index = list(netCDFInput.keys())[list(netCDFInput.values()).index(ncFile)] - - # retrieve dimensions, atributes, variables, and missing value - dimensions[index]= rootgrp.dimensions.copy() - variables[index]= rootgrp.variables.copy() - attributes[index]= rootgrp.__dict__.copy() - - #-set new values - for key in list(dimensions[index].keys()): - if 'lat' in key.lower(): - latVar= key - if 'lon' in key.lower(): - lonVar= key - latMin= getMin(latMin,variables[index][latVar][:]) - latMax= getMax(latMax,variables[index][latVar][:]) - lonMin= getMin(lonMin,variables[index][lonVar][:]) - lonMax= getMax(lonMax,variables[index][lonVar][:]) - - #-assign calendar (used) - if 'time' in list(variables[index].keys()): - for name in variables[index]['time'].ncattrs(): - if name not in list(calendar_used.keys()): - calendar_used[name]= getattr(variables[index]['time'],name) - else: - if getattr(variables[index]['time'],name) != calendar_used[name]: - rootgrp.close() - sys.exit('calendars are incompatible') - #-time - if uniqueTimes.size == 0: - uniqueTimes= variables[index]['time'][:] - #~ else: - #~ uniqueTimes= np.unique(np.c_[uniqueTimes[:],variables[index]['time'][:]]) - uniqueTimes.sort() - keys= list(variables[index].keys()) - for key in list(dimensions[index].keys()): - if key in keys: - keys.remove(key) - key= keys[0] - if variableName == None: - variableName= key - else: - if key != variableName: - rootgrp.close() - sys.exit('variables are incompatible') - #-Missing Value - MV = rootgrp.variables[key]._FillValue - varUnits = rootgrp.variables[variableName].units - #-close file - rootgrp.close() - - #-create output netCDF - longitudes= np.around(np.arange(lonMin,lonMax+deltaLon,deltaLon), decimals=4) - latitudes= np.around(np.arange(latMax,latMin-deltaLat,-deltaLat), decimals=4) - - # ~ # - for the Uly project, we fixed the following - # ~ lonMin = round(lonMin, 2) - # ~ lonMax = round(lonMax, 2) - # ~ latMax = round(latMax, 2) - # ~ latMin = round(latMin, 2) - # ~ deltaLon = round(deltaLon, 2) - # ~ deltaLat = round(deltaLat, 2) - - # ~ longitudes= np.arange(lonMin,lonMax+deltaLon,deltaLon) - # ~ latitudes= np.arange(latMax,latMin-deltaLat,-deltaLat) - - #~ longitudes= np.linspace(lonMin,lonMax+deltaLon, int(round((lonMax+deltaLon - lonMin)/deltaLon))) - #~ latitudes= np.linspace(latMax,latMin-deltaLat, int(round((latMax - latMin+deltaLat)/deltaLat))) - - uniqueTimes= uniqueTimes.tolist() - - #-open file - rootgrp= nc.Dataset(netCDFOutput,'w',format= ncFormat) - - # - create time and set its attributes - date_time=rootgrp.createDimension('time',len(uniqueTimes)) - #~ date_time=rootgrp.createDimension('time', None) - date_time= rootgrp.createVariable('time','f8',('time',)) - for attr,value in list(calendar_used.items()): - setattr(date_time,attr,str(value)) - date_time[:]= uniqueTimes - - - #-create dimensions for longitudes and latitudes - rootgrp.createDimension('latitude',len(latitudes)) - rootgrp.createDimension('longitude',len(longitudes)) - lat= rootgrp.createVariable('latitude','f4',('latitude')) - lat.standard_name= 'Latitude' - lat.long_name= 'Latitude cell centres' - lon= rootgrp.createVariable('longitude','f4',('longitude')) - lon.standard_name= 'Longitude' - lon.long_name= 'Longitude cell centres' - - #-assing latitudes and longitudes to variables - lat[:]= latitudes - lon[:]= longitudes - - latitudes = np.around(latitudes, decimals=4) # TODO: Improve this. We need this one for selecting rows and columns. - longitudes = np.around(longitudes, decimals=4) # TODO: Improve this. We need this one for selecting rows and columns. - - - # - setting variable - if len(calendar_used) == 0: - varStructure= ('latitude','longitude') - else: - varStructure= ('time','latitude','longitude') - - variable = rootgrp.createVariable(variableName, 'f4', varStructure, fill_value = MV, zlib = using_zlib) - - # - set variable attributes and overall values - for index in list(attributes.keys()): - for name in variables[index][variableName].ncattrs(): - try: - setattr(variable,name,str(getattr(variables[index][variableName],name))) - except: - pass - for attr,value in list(attributes[index].items()): - setattr(rootgrp,attr,str(value)) - - #-write to file - rootgrp.sync() - rootgrp.close() - - #NOTE: this assumes it is a timed variable! - #-iterate over time steps and retrieve values - - print('nr of time steps = %s, nr of files = %s ' % (len(uniqueTimes), len(netCDFInput))) - i_time = 0 - for time in uniqueTimes[:]: - - - #~ print 'processing %s for time index %.0d' %(ncName, 1+ time - min(uniqueTimes)) - #~ print 'processing %s for time %.0d' %(ncName, time) - - i_time = i_time + 1 - print('processing %s %i from %i' %(ncName, i_time, len(uniqueTimes))) - - #-create empty field to fill - variableArray= np.ones((len(latitudes),len(longitudes)))*MV - - #-iterate over input netCDFs - for ncFile in list(netCDFInput.values()): - #-open netCDF file and get dictionary index - rootgrp= nc.Dataset(ncFile,'r',format= ncFormat) - index= list(netCDFInput.keys())[list(netCDFInput.values()).index(ncFile)] - #-retrieve posCnt and process - #-get row and column indices from lats and lons - for key in list(dimensions[index].keys()): - if 'lat' in key.lower(): - latVar= key - if 'lon' in key.lower(): - lonVar= key - latMaxNcFile = round(getMax(latMin,variables[index][latVar][:]),4) - latMinNcFile = round(getMin(latMax,variables[index][latVar][:]),4) - lonMinNcFile = round(getMin(lonMax,variables[index][lonVar][:]),4) - lonMaxNcFile = round(getMax(lonMin,variables[index][lonVar][:]),4) - - # ~ latMaxNcFile = round(getMax(latMin,variables[index][latVar][:]),20) - # ~ latMinNcFile = round(getMin(latMax,variables[index][latVar][:]),20) - # ~ lonMinNcFile = round(getMin(lonMax,variables[index][lonVar][:]),20) - # ~ lonMaxNcFile = round(getMax(lonMin,variables[index][lonVar][:]),20) - - # ~ print(latMaxNcFile) - # ~ print(latMinNcFile) - # ~ print(lonMinNcFile) - # ~ print(lonMaxNcFile) - - row0= int( np.where(latitudes == min(latMax,latMaxNcFile))[0][0] ) - row1= int( np.where(latitudes == max(latMin,latMinNcFile))[0][0]+1) - col0= int(np.where(longitudes == max(lonMin,lonMinNcFile))[0][0] ) - col1= int(np.where(longitudes == min(lonMax,lonMaxNcFile))[0][0]+1) - - # ~ minY = min(abs(latitudes - min(latMax,latMaxNcFile))) - # ~ row0 = int(np.where(abs(latitudes - min(latMax,latMaxNcFile)) == minY)[0]) - - # ~ maxY = min(abs(latitudes - max(latMax,latMaxNcFile))) - # ~ row1 = int(np.where(abs(latitudes - max(latMax,latMaxNcFile)) == maxY)[0]) + 1 - - # ~ maxX = min(abs(longitudes - max(lonMin,lonMinNcFile))) - # ~ col0 = int(np.where(abs(longitudes - max(lonMin,lonMinNcFile)) == maxX)[0]) - - # ~ minX = min(abs(longitudes - min(lonMin,lonMinNcFile))) - # ~ col1 = int(np.where(abs(longitudes - max(lonMin,lonMinNcFile)) == minX)[0]) + 1 - - # ~ print(row0) - # ~ print(row1) - # ~ print(col0) - # ~ print(col1) - - posCnt= None - try: - - # ~ # find the correct index (old method) - this is very slow - # ~ posCnt= variables[index]['time'][:].tolist().index(time) - - # find the correct index (new method) - date_value = nc.num2date(time, rootgrp.variables['time'].units, rootgrp.variables['time'].calendar) - posCnt = nc.date2index(date_value, rootgrp.variables['time']) - - print(date_value) - print(posCnt) - - sampleArray= rootgrp.variables[variableName][posCnt,:,:] - - print(sampleArray) - - sampleArray[sampleArray == variables[index][variableName]._FillValue]= MV - variableArray[row0:row1,col0:col1][variableArray[row0:row1,col0:col1] == MV]= \ - sampleArray[variableArray[row0:row1,col0:col1] == MV] - - print('time is present :' + str(date_value)) - - except: - if posCnt == None: - print('time not present') - else: - print('error in resampled') - #-close - rootgrp.close() - - #-write array to destination netCDF - posCnt= uniqueTimes.index(time) - rootgrp= nc.Dataset(netCDFOutput,'a',format= ncFormat) - rootgrp.variables[variableName][posCnt,:,:]= variableArray - variable.units = str(varUnits) - rootgrp.sync() - rootgrp.close() - - secs = int(tm.time() - scriptStartTime) - print("Processing %s took %s hh:mm:ss\n" % (ncName, str(datetime.timedelta(seconds=secs)))) - - -################################## -######## user input ############## -################################## - -# latitudes and longitudes: -# - 5 arcmin -deltaLat = 5.0/60.0 -deltaLon = 5.0/60.0 -# ~ # - 6 arcmin -# ~ deltaLat = 0.1 -# ~ deltaLon = 0.1 - -latMin = -90 + deltaLat / 2 -latMax = 90 - deltaLat / 2 -lonMin = -180 + deltaLon / 2 -lonMax = 180 - deltaLon / 2 - -# ~ # - uly extent map -# ~ latMin = -56 + deltaLat / 2 -# ~ latMax = 84 - deltaLat / 2 - - -# ~ # europe 30sec -# ~ deltaLat = 0.00833333333333333333333333333333333333333333333333333333333 -# ~ deltaLon = 0.00833333333333333333333333333333333333333333333333333333333 -# ~ latMin = 33 + deltaLat / 2 -# ~ latMax = 73 - deltaLat / 2 -# ~ lonMin = -11 + deltaLon / 2 -# ~ lonMax = 42 - deltaLon / 2 - -# ~ (pcrglobwb_py3_env_v20200309_with_cdsapi) edwinari@tcn858.bullx:/lustre1/0/einf1079/edwin/pcrglobwb_output_europe/europe_30sec/version_2021-04-20$ gdalinfo global_subdomains_final.map -# ~ Driver: PCRaster/PCRaster Raster File -# ~ Files: global_subdomains_final.map -# ~ Size is 6360, 4800 -# ~ Origin = (-11.000000000000000,73.000000000000000) -# ~ Pixel Size = (0.008333333333333,-0.008333333333333) -# ~ Metadata: - # ~ PCRASTER_VALUESCALE=VS_NOMINAL -# ~ Corner Coordinates: -# ~ Upper Left ( -11.0000000, 73.0000000) -# ~ Lower Left ( -11.0000000, 33.0000000) -# ~ Upper Right ( 42.0000000, 73.0000000) -# ~ Lower Right ( 42.0000000, 33.0000000) -# ~ Center ( 15.5000000, 53.0000000) -# ~ Band 1 Block=6360x1 Type=Int32, ColorInterp=Undefined - # ~ Min=1.000 Max=45.000 - # ~ NoData Value=-2147483647 - - - - -# input directory: -inputDirRoot = sys.argv[1] - -outputDir = sys.argv[2] - -# making outputDir -try: - os.makedirs(outputDir) -except: - pass - -# file_type, options are: outDailyTot, outMonthTot, outMonthAvg, outMonthEnd, outAnnuaTot, outAnnuaAvg, outAnnuaEnd -file_type = str(sys.argv[3]) - -# starting and end dates -startDate = str(sys.argv[4]) -endDate = str(sys.argv[5]) - -# list of netcdf files that will be merged: -netcdfList = str(sys.argv[6]) -print(netcdfList) -netcdfList = list(set(netcdfList.split(","))) -if file_type == "outDailyTotNC": netcdfList = ['%s_dailyTot_output.nc'%var for var in netcdfList] -if file_type == "outMonthTotNC": netcdfList = ['%s_monthTot_output.nc'%var for var in netcdfList] -if file_type == "outMonthAvgNC": netcdfList = ['%s_monthAvg_output.nc'%var for var in netcdfList] -if file_type == "outMonthEndNC": netcdfList = ['%s_monthEnd_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaTotNC": netcdfList = ['%s_annuaTot_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaAvgNC": netcdfList = ['%s_annuaAvg_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaEndNC": netcdfList = ['%s_annuaEnd_output.nc'%var for var in netcdfList] - -if file_type == "outMonthMaxNC": netcdfList = ['%s_monthMax_output.nc'%var for var in netcdfList] -if file_type == "outAnnuaMaxNC": netcdfList = ['%s_annuaMax_output.nc'%var for var in netcdfList] - -# netcdf format and zlib option: -ncFormat = str(sys.argv[7]) -using_zlib = str(sys.argv[8]) - -# maximum number of cores that will be used -max_number_of_cores = int(sys.argv[9]) - -# number of cores that will be used -ncores = min(len(netcdfList), max_number_of_cores) - -# ~ # clone areas -# ~ areas = str(sys.argv[10]) -# ~ if areas == "Global": - # ~ areas = ['M%07d'%i for i in range(1,72,1)] -# ~ elif areas == "europe_30sec": - # ~ areas = ['M%07d'%i for i in range(1,46,1)] -# ~ else: - # ~ areas = list(set(areas.split(","))) - -# number of clone areas -number_of_clones = int(sys.argv[10]) -areas = ['M%07d'%i for i in range(1, number_of_clones + 1, 1)] - - -# extent of the clone map -if sys.argv[11] == "all_lats": - latMin = -90 + deltaLat / 2 - latMax = 90 - deltaLat / 2 - -# clonemap defined from the system argument = -if sys.argv[11] == "defined": - cellsize_in_arcsec = float(sys.argv[12]) - xmin = float(sys.argv[13]) - ymin = float(sys.argv[14]) - xmax = float(sys.argv[15]) - ymax = float(sys.argv[16]) - lonMin = xmin + float(sys.argv[12]) / (2. * 3600.) - latMin = ymin + float(sys.argv[12]) / (2. * 3600.) - lonMax = xmax - float(sys.argv[12]) / (2. * 3600.) - latMax = ymax - float(sys.argv[12]) / (2. * 3600.) - -#~ # for testing, we use only a single core -#~ mergeNetCDF((netcdfList[0], latMin, latMax, lonMin, lonMax, deltaLat, deltaLon, startDate, endDate, ncFormat, using_zlib)) - -ll = [] -for ncName in netcdfList: - ll.append((ncName, latMin, latMax, lonMin, lonMax, deltaLat, deltaLon, startDate, endDate, ncFormat, using_zlib)) -pool = Pool(processes = ncores) # start "ncores" of worker processes -pool.map(mergeNetCDF, ll) # multicore processing - -pool.terminate() -pool.join() - -sys.exit() diff --git a/model/merge_pcraster_maps.py b/model/merge_pcraster_maps.py deleted file mode 100644 index 6483508c5..000000000 --- a/model/merge_pcraster_maps.py +++ /dev/null @@ -1,324 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import print_function - -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Edwin Husni Sutanudjaja (24 June 2014): This script is to integrate several pcraster maps into one global map. -# - Used mainly for making a set of initial conditions for 5 arc min runs. - -#-modules -import os -import sys -import subprocess -import time as tm -import numpy as np -import datetime -import glob -from multiprocessing import Pool -from pcraster import setclone, Scalar, readmap, report, pcr2numpy, numpy2pcr - -def getMax(x,a): - if not isinstance(a,np.ndarray): - a= np.array(a) - m= float(a.max()) - if x == None: - return m - else: - return max(m,x) - -def getMin(x,a): - if not isinstance(a,np.ndarray): - a= np.array(a) - m= float(a.min()) - if x == None: - return m - else: - return min(m,x) - -def getFileList(inputDir, filePattern): - '''creates a dictionary of files meeting the pattern specified''' - fileNameList = glob.glob(os.path.join(inputDir, filePattern)) - ll= {} - for fileName in fileNameList: - ll[os.path.split(fileName)[-1]]= fileName - return ll - -def getMapAttributesALL(cloneMap): - co= ['mapattr -p %s ' %(cloneMap)] - cOut,err= subprocess.Popen(co, stdout=subprocess.PIPE,stderr=open('/dev/null'),shell=True).communicate() - if err !=None or cOut == []: - print("Something wrong with mattattr in virtualOS, maybe clone Map does not exist ? ") - sys.exit() - mapAttr = {'cellsize': float(cOut.split()[7]) ,\ - 'rows' : float(cOut.split()[3]) ,\ - 'cols' : float(cOut.split()[5]) ,\ - 'xUL' : float(cOut.split()[17]),\ - 'yUL' : float(cOut.split()[19])} - return mapAttr - -def checkResolution(c1,c2): - '''Check resolution''' - s1= str(c1) - s2= str(c2) - if len(s1) < len(s2): - s= s1 - else: - s= s2 - p= s.find('.') - if p != -1: - nd= len(s)-(p+1) - else: - nd= 0 - c1= round(c1,nd) - c2= round(c2,nd) - if c1 != c2: print('resolutions %s, %s differ' % (s1,s2)) - return c1 == c2, nd - -def getPosition(x,values,nd): - '''Returns the position of value x in the array of values with the number of digits specified ''' - values= np.abs(values[:]-x) - x= values.min() - pos= np.where(values == x) - if pos[0].size > 0 and x <= 1./nd: - return pos[0][0] - else: - return None - -def checkRowPosition(r0,r1): - '''' Returns the sorted row positions''' - if r0 > r1: - return r1, r0 - else: - return r0, r1 - -def joinMaps(inputTuple): - '''Merges maps starting from an input tuple that specifies the output map name, the number of rows\ - and the number rows, columns, ULL X and Y coordinates, cell length and the missing value identifer and a list of input maps''' - outputFileName= inputTuple[0] - nrRows= inputTuple[1] - nrCols= inputTuple[2] - xMin= inputTuple[3] - yMax= inputTuple[4] - cellLength= inputTuple[5] - MV= inputTuple[6] - fileNames= inputTuple[7] - cloneFileName= inputTuple[8] - #-echo to screen - print('combining files for %s' % outputFileName, end=' ') - #-get extent - xMax= xMin+nrCols*cellLength - yMin= yMax-nrRows*cellLength - xCoordinates= xMin+np.arange(nrCols+1)*cellLength - yCoordinates= yMin+np.arange(nrRows+1)*cellLength - yCoordinates= np.flipud(yCoordinates) - print('between %.2f, %.2f and %.2f, %.2f' % (xMin,yMin,xMax,yMax)) - #-set output array - variableArray= np.ones((nrRows,nrCols))*MV - #-iterate over maps - for fileName in fileNames: - print(fileName) - attributeClone= getMapAttributesALL(fileName) - cellLengthClone= attributeClone['cellsize'] - rowsClone= attributeClone['rows'] - colsClone= attributeClone['cols'] - xULClone= attributeClone['xUL'] - yULClone= attributeClone['yUL'] - # check whether both maps have the same attributes and process - process, nd= checkResolution(cellLength,cellLengthClone) - if process: - #-get coordinates and locations - sampleXMin= xULClone - sampleXMax= xULClone+colsClone*cellLengthClone - sampleYMin= yULClone-rowsClone*cellLengthClone - sampleYMax= yULClone - sampleXCoordinates= sampleXMin+np.arange(colsClone+1)*cellLengthClone - sampleYCoordinates= sampleYMin+np.arange(rowsClone+1)*cellLengthClone - sampleYCoordinates= np.flipud(sampleYCoordinates) - sampleXMin= getMax(xMin,sampleXMin) - sampleXMax= getMin(xMax,sampleXMax) - sampleYMin= getMax(yMin,sampleYMin) - sampleYMax= getMin(yMax,sampleYMax) - sampleRow0= getPosition(sampleYMin,sampleYCoordinates,nd) - sampleRow1= getPosition(sampleYMax,sampleYCoordinates,nd) - sampleCol0= getPosition(sampleXMin,sampleXCoordinates,nd) - sampleCol1= getPosition(sampleXMax,sampleXCoordinates,nd) - sampleRow0, sampleRow1= checkRowPosition(sampleRow0,sampleRow1) - variableRow0= getPosition(sampleYMin,yCoordinates,nd) - variableRow1= getPosition(sampleYMax,yCoordinates,nd) - variableCol0= getPosition(sampleXMin,xCoordinates,nd) - variableCol1= getPosition(sampleXMax,xCoordinates,nd) - variableRow0,variableRow1= checkRowPosition(variableRow0,variableRow1) - #-read sample array - setclone(fileName) - sampleArray= pcr2numpy(readmap(fileName),MV) - sampleNrRows, sampleNrCols= sampleArray.shape - #-create mask - mask= (variableArray[variableRow0:variableRow1,variableCol0:variableCol1] == MV) &\ - (sampleArray[sampleRow0:sampleRow1,sampleCol0:sampleCol1] != MV) - #-add values - print(' adding values in %d, %d rows, columns from (x, y) %.3f, %.3f and %.3f, %.3f to position (row, col) %d, %d and %d, %d' %\ - (sampleNrRows, sampleNrCols,sampleXMin,sampleYMin,sampleXMax,sampleYMax,variableRow0,variableCol0,variableRow1,variableCol1)) - variableArray[variableRow0:variableRow1,variableCol0:variableCol1][mask]= \ - sampleArray[sampleRow0:sampleRow1,sampleCol0:sampleCol1][mask] - else: - print('%s does not match resolution and is not processed' % fileName) - #-report output map - setclone(cloneFileName) - report(numpy2pcr(Scalar,variableArray,MV),outputFileName) - -################################## -######## user input ############## -################################## - -MV = 1e20 - -# chosen date -year = 1960 -chosenDate = datetime.date(int(year),12,31) # datetime.date(1979,12,31) -try: - chosenDate = str(sys.argv[1]) -except: - pass - -# map coordinates and resolution -deltaLat= 5.0/60.0 -deltaLon= 5.0/60.0 -latMin= -90. -latMax= 90.0 -lonMin= -180. -lonMax= 180.0 - -inputDirRoot = '' -try: - inputDirRoot = str(sys.argv[2]) -except: - pass - -outputDir = inputDirRoot+"/global/maps/" -try: - outputDir = sys.argv[3] - if sys.argv[3] == "default": outputDir = inputDirRoot + "/global/maps/" - if sys.argv[3] == "maps" : outputDir = inputDirRoot + "/global/maps/" - if sys.argv[3] == "states" : outputDir = inputDirRoot + "/global/states/" -except: - outputDir = str(sys.argv[3]) -try: - os.makedirs(outputDir) -except: - pass - -ncores = 8 -try: - ncores = int(sys.argv[4]) -except: - pass - -number_of_clone_maps = 53 -try: - number_of_clone_maps = int(sys.argv[5]) -except: - pass -areas = ['M%02d'%i for i in range(1,number_of_clone_maps+1,1)] - -if sys.argv[5] == "Global": areas = ['M%02d'%i for i in range(1,number_of_clone_maps+1,1)] -if sys.argv[5] == "Global_uly": areas = ['M%07d'%i for i in range(1,number_of_clone_maps+1,1)] - -#~ # set clone maps based on the system argument -#~ areas = ["M47","M48"] ### only fot TEST CASE - -#~ try: - #~ areas = str(sys.argv[5]) - #~ areas = list(set(areas.split(","))) - #~ if areas[0] == "Global": areas = ['M%02d'%i for i in range(1,number_of_clone_maps+1,1)] -#~ except: - #~ pass -#~ -#~ try: - #~ areas = str(sys.argv[5]) - #~ areas = list(set(areas.split(","))) - #~ print(areas) - #~ if areas[0] == "Global_uly" or areas == "Global_uly": areas = ['M%07'%i for i in range(1,number_of_clone_maps+1,1)] -#~ except: - #~ pass -#~ -#~ print(areas) - -#-main script -#-get clone -nrRows= int((latMax-latMin)/deltaLat) -nrCols= int((lonMax-lonMin)/deltaLon) - -tempCloneMap = outputDir+'/temp_clone.map' -command= 'mapattr -s -R %d -C %d -P "yb2t" -B -x %f -y %f -l %f %s' %\ - (nrRows,nrCols,lonMin,latMax,deltaLat,tempCloneMap) -os.system(command) -setclone(tempCloneMap) - -#~ print areas -#~ print areas[0] - -# input files where unmerged maps are saved -inputDir = os.path.join(inputDirRoot,areas[0], 'maps') -if sys.argv[3] == "default": inputDir = os.path.join(inputDirRoot,areas[0], 'maps') -if sys.argv[3] == "maps" : inputDir = os.path.join(inputDirRoot,areas[0], 'maps') -if sys.argv[3] == "states" : inputDir = os.path.join(inputDirRoot,areas[0], 'states') -files = getFileList(inputDir, '*%s.map' % chosenDate) - - -ncores = min(len(files), ncores) -print() -print() -print('Using %d cores to process' % ncores, end=' ') -print() -print() - -for fileName in list(files.keys()): - #~ print fileName, - files[fileName]= {} - ll= [] - outputFileName= os.path.join(outputDir,fileName) - for area in areas: - #~ print area - inputFileName= os.path.join(inputDirRoot, area, 'maps', fileName) - if sys.argv[3] == "default": inputFileName = os.path.join(inputDirRoot, area, 'maps', fileName) - if sys.argv[3] == "maps" : inputFileName = os.path.join(inputDirRoot, area, 'maps', fileName) - if sys.argv[3] == "states" : inputFileName = os.path.join(inputDirRoot, area, 'states', fileName) - ll.append(inputFileName) - files[fileName]= tuple((outputFileName,nrRows,nrCols,lonMin,latMax,deltaLat,MV,ll[:],tempCloneMap)) - -#~ # this is for testing -#~ joinMaps(files[fileName]) - -print() -print() -pool = Pool(processes=ncores) # start "ncores" of worker processes -pool.map(joinMaps,list(files.values())) -print() -print() - -#-remove temporary file -os.remove(tempCloneMap) -print(' all done') -print() -print() diff --git a/model/merge_pcraster_maps_6_arcmin_example_bash.sh b/model/merge_pcraster_maps_6_arcmin_example_bash.sh deleted file mode 100644 index d33e7fc4c..000000000 --- a/model/merge_pcraster_maps_6_arcmin_example_bash.sh +++ /dev/null @@ -1,9 +0,0 @@ -#!/bin/bash - -set -x - -MAIN_OUTPUT_DIR="/scratch/ms/copext/cyes/pcrglobwb_output_version_2020-08-14/" - -python merge_pcraster_maps_6_arcmin_ulysses.py 1995-12-31 ${MAIN_OUTPUT_DIR} states 8 Global 54 False - -set +x diff --git a/model/merge_pcraster_maps_6_arcmin_ulysses.py b/model/merge_pcraster_maps_6_arcmin_ulysses.py deleted file mode 100644 index de3329167..000000000 --- a/model/merge_pcraster_maps_6_arcmin_ulysses.py +++ /dev/null @@ -1,354 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import print_function - -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Edwin Husni Sutanudjaja (24 June 2014): This script is to integrate several pcraster maps into one global map. -# - Used mainly for making a set of initial conditions for 5 arc min runs. - -#-modules -import os -import sys -import subprocess -import time as tm -import numpy as np -import datetime -import glob -from multiprocessing import Pool -from pcraster import setclone, Scalar, readmap, report, pcr2numpy, numpy2pcr - -def getMax(x,a): - if not isinstance(a,np.ndarray): - a= np.array(a) - m= float(a.max()) - if x == None: - return m - else: - return max(m,x) - -def getMin(x,a): - if not isinstance(a,np.ndarray): - a= np.array(a) - m= float(a.min()) - if x == None: - return m - else: - return min(m,x) - -def getFileList(inputDir, filePattern): - '''creates a dictionary of files meeting the pattern specified''' - fileNameList = glob.glob(os.path.join(inputDir, filePattern)) - ll= {} - for fileName in fileNameList: - ll[os.path.split(fileName)[-1]]= fileName - return ll - -def getMapAttributesALL(cloneMap): - co= ['mapattr -p %s ' %(cloneMap)] - cOut,err= subprocess.Popen(co, stdout=subprocess.PIPE,stderr=open('/dev/null'),shell=True).communicate() - if err !=None or cOut == []: - print("Something wrong with mattattr in virtualOS, maybe clone Map does not exist ? ") - sys.exit() - mapAttr = {'cellsize': float(cOut.split()[7]) ,\ - 'rows' : float(cOut.split()[3]) ,\ - 'cols' : float(cOut.split()[5]) ,\ - 'xUL' : float(cOut.split()[17]),\ - 'yUL' : float(cOut.split()[19])} - return mapAttr - -def checkResolution(c1,c2): - '''Check resolution''' - s1= str(c1) - s2= str(c2) - if len(s1) < len(s2): - s= s1 - else: - s= s2 - p= s.find('.') - if p != -1: - nd= len(s)-(p+1) - else: - nd= 0 - c1= round(c1,nd) - c2= round(c2,nd) - if c1 != c2: print('resolutions %s, %s differ' % (s1,s2)) - return c1 == c2, nd - -def getPosition(x,values,nd): - '''Returns the position of value x in the array of values with the number of digits specified ''' - values= np.abs(values[:]-x) - x= values.min() - pos= np.where(values == x) - if pos[0].size > 0 and x <= 1./nd: - return pos[0][0] - else: - return None - -def checkRowPosition(r0,r1): - '''' Returns the sorted row positions''' - if r0 > r1: - return r1, r0 - else: - return r0, r1 - -def joinMaps(inputTuple): - '''Merges maps starting from an input tuple that specifies the output map name, the number of rows\ - and the number rows, columns, ULL X and Y coordinates, cell length and the missing value identifer and a list of input maps''' - outputFileName= inputTuple[0] - nrRows= inputTuple[1] - nrCols= inputTuple[2] - xMin= inputTuple[3] - yMax= inputTuple[4] - cellLength= inputTuple[5] - MV= inputTuple[6] - fileNames= inputTuple[7] - cloneFileName= inputTuple[8] - #-echo to screen - print('combining files for %s' % outputFileName, end=' ') - #-get extent - xMax= xMin+nrCols*cellLength - yMin= yMax-nrRows*cellLength - xCoordinates= xMin+np.arange(nrCols+1)*cellLength - yCoordinates= yMin+np.arange(nrRows+1)*cellLength - yCoordinates= np.flipud(yCoordinates) - print('between %.2f, %.2f and %.2f, %.2f' % (xMin,yMin,xMax,yMax)) - #-set output array - variableArray= np.ones((nrRows,nrCols))*MV - #-iterate over maps - for fileName in fileNames: - print(fileName) - attributeClone= getMapAttributesALL(fileName) - cellLengthClone= attributeClone['cellsize'] - rowsClone= attributeClone['rows'] - colsClone= attributeClone['cols'] - xULClone= attributeClone['xUL'] - yULClone= attributeClone['yUL'] - # check whether both maps have the same attributes and process - process, nd= checkResolution(cellLength,cellLengthClone) - if process: - #-get coordinates and locations - sampleXMin= xULClone - sampleXMax= xULClone+colsClone*cellLengthClone - sampleYMin= yULClone-rowsClone*cellLengthClone - sampleYMax= yULClone - sampleXCoordinates= sampleXMin+np.arange(colsClone+1)*cellLengthClone - sampleYCoordinates= sampleYMin+np.arange(rowsClone+1)*cellLengthClone - sampleYCoordinates= np.flipud(sampleYCoordinates) - sampleXMin= getMax(xMin,sampleXMin) - sampleXMax= getMin(xMax,sampleXMax) - sampleYMin= getMax(yMin,sampleYMin) - sampleYMax= getMin(yMax,sampleYMax) - sampleRow0= getPosition(sampleYMin,sampleYCoordinates,nd) - sampleRow1= getPosition(sampleYMax,sampleYCoordinates,nd) - sampleCol0= getPosition(sampleXMin,sampleXCoordinates,nd) - sampleCol1= getPosition(sampleXMax,sampleXCoordinates,nd) - sampleRow0, sampleRow1= checkRowPosition(sampleRow0,sampleRow1) - variableRow0= getPosition(sampleYMin,yCoordinates,nd) - variableRow1= getPosition(sampleYMax,yCoordinates,nd) - variableCol0= getPosition(sampleXMin,xCoordinates,nd) - variableCol1= getPosition(sampleXMax,xCoordinates,nd) - variableRow0,variableRow1= checkRowPosition(variableRow0,variableRow1) - #-read sample array - setclone(fileName) - sampleArray= pcr2numpy(readmap(fileName),MV) - sampleNrRows, sampleNrCols= sampleArray.shape - #-create mask - mask= (variableArray[variableRow0:variableRow1,variableCol0:variableCol1] == MV) &\ - (sampleArray[sampleRow0:sampleRow1,sampleCol0:sampleCol1] != MV) - #-add values - print(' adding values in %d, %d rows, columns from (x, y) %.3f, %.3f and %.3f, %.3f to position (row, col) %d, %d and %d, %d' %\ - (sampleNrRows, sampleNrCols,sampleXMin,sampleYMin,sampleXMax,sampleYMax,variableRow0,variableCol0,variableRow1,variableCol1)) - variableArray[variableRow0:variableRow1,variableCol0:variableCol1][mask]= \ - sampleArray[sampleRow0:sampleRow1,sampleCol0:sampleCol1][mask] - else: - print('%s does not match resolution and is not processed' % fileName) - #-report output map - setclone(cloneFileName) - report(numpy2pcr(Scalar,variableArray,MV),outputFileName) - -################################## -######## user input ############## -################################## - -MV = 1e20 - -# chosen date -year = 1960 -chosenDate = datetime.date(int(year),12,31) # datetime.date(1979,12,31) -try: - chosenDate = str(sys.argv[1]) -except: - pass - -#~ # map coordinates and resolution -#~ deltaLat= 5.0/60.0 -#~ deltaLon= 5.0/60.0 -#~ latMin= -90. -#~ latMax= 90.0 -#~ lonMin= -180. -#~ lonMax= 180.0 - -# map coordinates and resolution -deltaLat= 0.1 -deltaLon= 0.1 -latMin= -90. -latMax= 90.0 -lonMin= -180. -lonMax= 180.0 - -# map coordinates and resolution for the Ulysses project (1400 rows) -# -# Corner Coordinates: -# Upper Left (-180.0000000, 84.0000000) -# Lower Left (-180.0000000, -56.0000000) -# Upper Right ( 180.0000000, 84.0000000) -# Lower Right ( 180.0000000, -56.0000000) -# Center ( -0.0000000, 14.0000000) -# -deltaLat = 0.1 -deltaLon = 0.1 -latMin = -56.0 -latMax = 84.0 -lonMin = -180.0 -lonMax = 180.0 - - -inputDirRoot = '' -try: - inputDirRoot = str(sys.argv[2]) -except: - pass - -outputDir = inputDirRoot+"/global/maps/" -try: - outputDir = sys.argv[3] - if sys.argv[3] == "default": outputDir = inputDirRoot + "/global/maps/" - if sys.argv[3] == "maps" : outputDir = inputDirRoot + "/global/maps/" - if sys.argv[3] == "states" : outputDir = inputDirRoot + "/global/states/" -except: - outputDir = str(sys.argv[3]) -try: - os.makedirs(outputDir) -except: - pass - -ncores = 8 -try: - ncores = int(sys.argv[4]) -except: - pass - -number_of_clone_maps = 53 -try: - number_of_clone_maps = int(sys.argv[5]) -except: - pass -#~ areas = ['M%02d'%i for i in range(1,number_of_clone_maps+1,1)] -areas = ['M%07i'%i for i in range(1,number_of_clone_maps+1,1)] - -# set clone maps based on the system argument -#~ areas = ["M47","M48"] ### only fot TEST CASE -try: - areas = str(sys.argv[5]) - areas = list(set(areas.split(","))) - if areas[0] == "Global": - try: - number_of_clone_maps = int(sys.argv[6]) - except: - pass - #~ areas = ['M%02d'%i for i in range(1,number_of_clone_maps+1,1)] - areas = ['M%07i'%i for i in range(1,number_of_clone_maps+1,1)] -except: - pass - -print(areas) - -# option to ignore empty -ignoreNonExistingMaps = False -ignoreNonExistingMaps = str(sys.argv[7]) == "ignoreNonExistingMaps" - -#-main script -#-get clone -nrRows= int((latMax-latMin)/deltaLat) -nrCols= int((lonMax-lonMin)/deltaLon) - -tempCloneMap = outputDir+'/temp_clone.map' -command= 'mapattr -s -R %d -C %d -P "yb2t" -B -x %f -y %f -l %f %s' %\ - (nrRows,nrCols,lonMin,latMax,deltaLat,tempCloneMap) -os.system(command) -setclone(tempCloneMap) - -#~ print areas -#~ print areas[0] - -# input files where unmerged maps are saved -inputDir = os.path.join(inputDirRoot,areas[0], 'maps') -if sys.argv[3] == "default": inputDir = os.path.join(inputDirRoot,areas[0], 'maps') -if sys.argv[3] == "maps" : inputDir = os.path.join(inputDirRoot,areas[0], 'maps') -if sys.argv[3] == "states" : inputDir = os.path.join(inputDirRoot,areas[0], 'states') -files = getFileList(inputDir, '*%s.map' % chosenDate) - - -ncores = min(len(files), ncores) -print() -print() -print('Using %d cores to process' % ncores, end=' ') -print() -print() - -for fileName in list(files.keys()): - #~ print fileName, - files[fileName]= {} - ll= [] - outputFileName= os.path.join(outputDir,fileName) - for area in areas: - #~ print area - inputFileName= os.path.join(inputDirRoot, area, 'maps', fileName) - if sys.argv[3] == "default": inputFileName = os.path.join(inputDirRoot, area, 'maps', fileName) - if sys.argv[3] == "maps" : inputFileName = os.path.join(inputDirRoot, area, 'maps', fileName) - if sys.argv[3] == "states" : inputFileName = os.path.join(inputDirRoot, area, 'states', fileName) - if ignoreNonExistingMaps == False: - ll.append(inputFileName) - else: - if os.path.exists(inputFileName): ll.append(inputFileName) - files[fileName]= tuple((outputFileName,nrRows,nrCols,lonMin,latMax,deltaLat,MV,ll[:],tempCloneMap)) - -#~ # this is for testing -#~ joinMaps(files[fileName]) - -print() -print() -pool = Pool(processes=ncores) # start "ncores" of worker processes -pool.map(joinMaps,list(files.values())) -print() -print() - -#-remove temporary file -os.remove(tempCloneMap) -print(' all done') -print() -print() - -pool.terminate() -pool.join() diff --git a/model/merge_pcraster_maps_general.py b/model/merge_pcraster_maps_general.py deleted file mode 100644 index d3fc47cf6..000000000 --- a/model/merge_pcraster_maps_general.py +++ /dev/null @@ -1,353 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from __future__ import print_function - -# -# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model -# -# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, -# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, -# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, -# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens -# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# Edwin Husni Sutanudjaja (24 June 2014): This script is to integrate several pcraster maps into one global map. -# - Used mainly for making a set of initial conditions for 5 arc min runs. - -#-modules -import os -import sys -import subprocess -import time as tm -import numpy as np -import datetime -import glob -from multiprocessing import Pool -from pcraster import setclone, Scalar, readmap, report, pcr2numpy, numpy2pcr - -def getMax(x,a): - if not isinstance(a,np.ndarray): - a= np.array(a) - m= float(a.max()) - if x == None: - return m - else: - return max(m,x) - -def getMin(x,a): - if not isinstance(a,np.ndarray): - a= np.array(a) - m= float(a.min()) - if x == None: - return m - else: - return min(m,x) - -def getFileList(inputDir, filePattern): - '''creates a dictionary of files meeting the pattern specified''' - fileNameList = glob.glob(os.path.join(inputDir, filePattern)) - ll= {} - for fileName in fileNameList: - ll[os.path.split(fileName)[-1]]= fileName - return ll - -def getMapAttributesALL(cloneMap): - co= ['mapattr -p %s ' %(cloneMap)] - cOut,err= subprocess.Popen(co, stdout=subprocess.PIPE,stderr=open('/dev/null'),shell=True).communicate() - if err !=None or cOut == []: - print("Something wrong with mattattr in virtualOS, maybe clone Map does not exist ? ") - sys.exit() - mapAttr = {'cellsize': float(cOut.split()[7]) ,\ - 'rows' : float(cOut.split()[3]) ,\ - 'cols' : float(cOut.split()[5]) ,\ - 'xUL' : float(cOut.split()[17]),\ - 'yUL' : float(cOut.split()[19])} - return mapAttr - -def checkResolution(c1,c2): - '''Check resolution''' - s1= str(c1) - s2= str(c2) - if len(s1) < len(s2): - s= s1 - else: - s= s2 - p= s.find('.') - if p != -1: - nd= len(s)-(p+1) - else: - nd= 0 - c1= round(c1,nd) - c2= round(c2,nd) - if c1 != c2: print('resolutions %s, %s differ' % (s1,s2)) - return c1 == c2, nd - -def getPosition(x,values,nd): - '''Returns the position of value x in the array of values with the number of digits specified ''' - values= np.abs(values[:]-x) - x= values.min() - pos= np.where(values == x) - if pos[0].size > 0 and x <= 1./nd: - return pos[0][0] - else: - return None - -def checkRowPosition(r0,r1): - '''' Returns the sorted row positions''' - if r0 > r1: - return r1, r0 - else: - return r0, r1 - -def joinMaps(inputTuple): - '''Merges maps starting from an input tuple that specifies the output map name, the number of rows\ - and the number rows, columns, ULL X and Y coordinates, cell length and the missing value identifer and a list of input maps''' - outputFileName= inputTuple[0] - nrRows= inputTuple[1] - nrCols= inputTuple[2] - xMin= inputTuple[3] - yMax= inputTuple[4] - cellLength= inputTuple[5] - MV= inputTuple[6] - fileNames= inputTuple[7] - cloneFileName= inputTuple[8] - #-echo to screen - print('combining files for %s' % outputFileName, end=' ') - #-get extent - xMax= xMin+nrCols*cellLength - yMin= yMax-nrRows*cellLength - xCoordinates= xMin+np.arange(nrCols+1)*cellLength - yCoordinates= yMin+np.arange(nrRows+1)*cellLength - yCoordinates= np.flipud(yCoordinates) - print('between %.2f, %.2f and %.2f, %.2f' % (xMin,yMin,xMax,yMax)) - #-set output array - variableArray= np.ones((nrRows,nrCols))*MV - #-iterate over maps - for fileName in fileNames: - print(fileName) - attributeClone= getMapAttributesALL(fileName) - cellLengthClone= attributeClone['cellsize'] - rowsClone= attributeClone['rows'] - colsClone= attributeClone['cols'] - xULClone= attributeClone['xUL'] - yULClone= attributeClone['yUL'] - # check whether both maps have the same attributes and process - process, nd= checkResolution(cellLength,cellLengthClone) - if process: - #-get coordinates and locations - sampleXMin= xULClone - sampleXMax= xULClone+colsClone*cellLengthClone - sampleYMin= yULClone-rowsClone*cellLengthClone - sampleYMax= yULClone - sampleXCoordinates= sampleXMin+np.arange(colsClone+1)*cellLengthClone - sampleYCoordinates= sampleYMin+np.arange(rowsClone+1)*cellLengthClone - sampleYCoordinates= np.flipud(sampleYCoordinates) - sampleXMin= getMax(xMin,sampleXMin) - sampleXMax= getMin(xMax,sampleXMax) - sampleYMin= getMax(yMin,sampleYMin) - sampleYMax= getMin(yMax,sampleYMax) - sampleRow0= getPosition(sampleYMin,sampleYCoordinates,nd) - sampleRow1= getPosition(sampleYMax,sampleYCoordinates,nd) - sampleCol0= getPosition(sampleXMin,sampleXCoordinates,nd) - sampleCol1= getPosition(sampleXMax,sampleXCoordinates,nd) - sampleRow0, sampleRow1= checkRowPosition(sampleRow0,sampleRow1) - variableRow0= getPosition(sampleYMin,yCoordinates,nd) - variableRow1= getPosition(sampleYMax,yCoordinates,nd) - variableCol0= getPosition(sampleXMin,xCoordinates,nd) - variableCol1= getPosition(sampleXMax,xCoordinates,nd) - variableRow0,variableRow1= checkRowPosition(variableRow0,variableRow1) - #-read sample array - setclone(fileName) - sampleArray= pcr2numpy(readmap(fileName),MV) - sampleNrRows, sampleNrCols= sampleArray.shape - #-create mask - mask= (variableArray[variableRow0:variableRow1,variableCol0:variableCol1] == MV) &\ - (sampleArray[sampleRow0:sampleRow1,sampleCol0:sampleCol1] != MV) - #-add values - print(' adding values in %d, %d rows, columns from (x, y) %.3f, %.3f and %.3f, %.3f to position (row, col) %d, %d and %d, %d' %\ - (sampleNrRows, sampleNrCols,sampleXMin,sampleYMin,sampleXMax,sampleYMax,variableRow0,variableCol0,variableRow1,variableCol1)) - variableArray[variableRow0:variableRow1,variableCol0:variableCol1][mask]= \ - sampleArray[sampleRow0:sampleRow1,sampleCol0:sampleCol1][mask] - else: - print('%s does not match resolution and is not processed' % fileName) - #-report output map - setclone(cloneFileName) - report(numpy2pcr(Scalar,variableArray,MV),outputFileName) - -################################## -######## user input ############## -################################## - -MV = 1e20 - -# chosen date -year = 1960 -chosenDate = datetime.date(int(year),12,31) # datetime.date(1979,12,31) -try: - chosenDate = str(sys.argv[1]) -except: - pass - -# map coordinates and resolution -deltaLat= 5.0/60.0 -deltaLon= 5.0/60.0 -latMin= -90. -latMax= 90.0 -lonMin= -180. -lonMax= 180.0 - -inputDirRoot = '' -try: - inputDirRoot = str(sys.argv[2]) -except: - pass - -outputDir = inputDirRoot+"/global/maps/" -try: - outputDir = sys.argv[3] - if sys.argv[3] == "default": outputDir = inputDirRoot + "/global/maps/" - if sys.argv[3] == "maps" : outputDir = inputDirRoot + "/global/maps/" - if sys.argv[3] == "states" : outputDir = inputDirRoot + "/global/states/" -except: - outputDir = str(sys.argv[3]) -try: - os.makedirs(outputDir) -except: - pass - -ncores = 8 -try: - ncores = int(sys.argv[4]) -except: - pass - -# ~ number_of_clone_maps = 53 -# ~ try: - # ~ number_of_clone_maps = int(sys.argv[5]) -# ~ except: - # ~ pass -# ~ areas = ['M%02d'%i for i in range(1,number_of_clone_maps+1,1)] - -# ~ if sys.argv[5] == "Global": areas = ['M%02d'%i for i in range(1,number_of_clone_maps+1,1)] -# ~ if sys.argv[5] == "Global_uly": areas = ['M%07d'%i for i in range(1,number_of_clone_maps+1,1)] - -# number of clone areas -number_of_clones = int(sys.argv[5]) -areas = ['M%07d'%i for i in range(1, number_of_clones + 1, 1)] - - -# clonemap defined from the system argument = -if sys.argv[6] == "defined": - cellsize_in_arcsec = float(sys.argv[7]) - xmin = float(sys.argv[8]) - ymin = float(sys.argv[9]) - xmax = float(sys.argv[10]) - ymax = float(sys.argv[11]) - deltaLat = float(sys.argv[7]) / (3600.) - deltaLon = deltaLat - lonMin = xmin - latMin = ymin - lonMax = xmax - latMax = ymax - - - -#~ # set clone maps based on the system argument -#~ areas = ["M47","M48"] ### only fot TEST CASE - -#~ try: - #~ areas = str(sys.argv[5]) - #~ areas = list(set(areas.split(","))) - #~ if areas[0] == "Global": areas = ['M%02d'%i for i in range(1,number_of_clone_maps+1,1)] -#~ except: - #~ pass -#~ -#~ try: - #~ areas = str(sys.argv[5]) - #~ areas = list(set(areas.split(","))) - #~ print(areas) - #~ if areas[0] == "Global_uly" or areas == "Global_uly": areas = ['M%07'%i for i in range(1,number_of_clone_maps+1,1)] -#~ except: - #~ pass -#~ -#~ print(areas) - -#-main script -#-get clone -nrRows= int((latMax-latMin)/deltaLat) -nrCols= int((lonMax-lonMin)/deltaLon) - -tempCloneMap = outputDir+'/temp_clone.map' -command= 'mapattr -s -R %d -C %d -P "yb2t" -B -x %f -y %f -l %f %s' %\ - (nrRows,nrCols,lonMin,latMax,deltaLat,tempCloneMap) -os.system(command) -setclone(tempCloneMap) - -#~ print areas -#~ print areas[0] - -# input files where unmerged maps are saved -inputDir = os.path.join(inputDirRoot,areas[0], 'maps') -if sys.argv[3] == "default": inputDir = os.path.join(inputDirRoot,areas[0], 'maps') -if sys.argv[3] == "maps" : inputDir = os.path.join(inputDirRoot,areas[0], 'maps') -if sys.argv[3] == "states" : inputDir = os.path.join(inputDirRoot,areas[0], 'states') -files = getFileList(inputDir, '*%s.map' % chosenDate) - - -ncores = min(len(files), ncores) -print() -print() -print('Using %d cores to process' % ncores, end=' ') -print() -print() - -for fileName in list(files.keys()): - #~ print fileName, - files[fileName]= {} - ll= [] - outputFileName= os.path.join(outputDir,fileName) - for area in areas: - #~ print area - inputFileName= os.path.join(inputDirRoot, area, 'maps', fileName) - if sys.argv[3] == "default": inputFileName = os.path.join(inputDirRoot, area, 'maps', fileName) - if sys.argv[3] == "maps" : inputFileName = os.path.join(inputDirRoot, area, 'maps', fileName) - if sys.argv[3] == "states" : inputFileName = os.path.join(inputDirRoot, area, 'states', fileName) - ll.append(inputFileName) - files[fileName]= tuple((outputFileName,nrRows,nrCols,lonMin,latMax,deltaLat,MV,ll[:],tempCloneMap)) - -# ~ # this is for testing -# ~ joinMaps(files[fileName]) - -print() -print() -pool = Pool(processes=ncores) # start "ncores" of worker processes -pool.map(joinMaps,list(files.values())) -print() -print() - -pool.terminate() -pool.join() - -#-remove temporary file -os.remove(tempCloneMap) - -print() -print() -print(' all done') -print() -print() - -sys.exit() diff --git a/model/surfaceWaterQuality.py b/model/surfaceWaterQuality.py deleted file mode 100644 index 6bedefb0f..000000000 --- a/model/surfaceWaterQuality.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# - -import re -import types - -import netCDF4 as nc -import pcraster as pcr - -import logging -logger = logging.getLogger(__name__) - -import virtualOS as vos - -def surface_water_allocation_based_on_quality(availableSurfaceWater, sectoralSurfaceWaterDemand, waterQualityStates, waterQualityThresholds, - surfaceWaterPriority, usingAllocSegments, allocSegments, cellArea, segmentArea, landmask, - prioritizeLocalSourceToMeetWaterDemand, currTimeStep): - ''' - Where: - availableSurfaceWater: maximum value between 0 and routed flow in channel - sectoralSurfaceWaterDemand: surface water demand estimations for every sector (irrigation, domestic, industrial, livestock) - waterQualityStates: surface water quality constituents' concentrations from DynQual (water temperature, BOD, salinity/TDS, fecal coliforms) - waterQualityThresholds: surface water quality concentration thresholds per constituent and sector - ''' - - # initial values - # - list of water demand sectors - waterDemandSectors = list(waterQualityThresholds.keys()) # i.e., irrigation, domestic, industrial, livestock - waterDemandSectorsPriority = ['domestic','industrial','livestock','irrigation'] - - # - list of water quality constituents - waterQualityConstituents = list(waterQualityThresholds[waterDemandSectors[0]].keys()) # i.e., water temperature, BOD, salinity/TDS, fecal coliforms - - # - amount of water that is abstracted from the source: initializing dataset - totalActualSurfaceWaterAbstract = 0.0 - - # - remaining water demand and satisfied water demand: initializing dictionaries - waterDemandRemaining = {} - waterDemandSatisfied = {} - for sector in waterDemandSectors: - waterDemandRemaining[sector] = sectoralSurfaceWaterDemand[sector] - waterDemandSatisfied[sector] = 0.0 - - # - replacing None values in waterQualityThresholds dictionary with unreachable threshold (1e100) - for sector in waterDemandSectors: - waterQualityThresholds[sector] = {k:v if v is not None else 1e100 for k,v in waterQualityThresholds[sector].items()} - - # looping for every sector - for sectorPriority in waterDemandSectorsPriority: - # sectoral surface water demend - surfaceWaterDemand = sectoralSurfaceWaterDemand[sectorPriority] - - # water quality concentrations - waterQualityConcentrationSWT = waterQualityStates['sw_temperature'] - waterQualityConcentrationBOD = waterQualityStates['bio_o2_demand'] - waterQualityConcentrationTDS = waterQualityStates['tot_dis_solid'] - waterQualityConcentrationFC = waterQualityStates['fecal_coliform'] - - # water quality thresholds - waterQualityThresholdsSWT = waterQualityThresholds[sectorPriority]['sw_temperature'] - waterQualityThresholdsBOD = waterQualityThresholds[sectorPriority]['bio_o2_demand'] - waterQualityThresholdsTDS = waterQualityThresholds[sectorPriority]['tot_dis_solid'] - waterQualityThresholdsFC = waterQualityThresholds[sectorPriority]['fecal_coliform'] - - # actual water available depending on its quality (threshold) - availableSurfaceWaterWithQuality = pcr.ifthenelse((waterQualityConcentrationSWT < waterQualityThresholdsSWT) & - (waterQualityConcentrationBOD < waterQualityThresholdsBOD) & - (waterQualityConcentrationTDS < waterQualityThresholdsTDS) & - (waterQualityConcentrationFC < waterQualityThresholdsFC), - availableSurfaceWater, 0.0) - - # total remaining water demand - #totalSurfaceWaterDemand = 0.0 - #for sector in waterDemandSectors: - # totalSurfaceWaterDemand = totalSurfaceWaterDemand + waterDemandRemaining[sector] - - # water allocation: water abtracted and allocated per pixel - msg = "Allocating water that is above water quality thresholds for the sector " + sectorPriority - logger.debug(msg) - - if usingAllocSegments: # using zone/segment at which supply network is defined - logger.debug("Allocation of surface water abstraction.") - - volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ - vos.waterAbstractionAndAllocation( \ - water_demand_volume = surfaceWaterDemand*cellArea, - available_water_volume = availableSurfaceWaterWithQuality, - allocation_zones = allocSegments, - zone_area = segmentArea, - high_volume_treshold = None, - debug_water_balance = True, - extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), - landmask = landmask, - ignore_small_values = False, - prioritizing_local_source = prioritizeLocalSourceToMeetWaterDemand) - - actSurfaceWaterAbstract = volActSurfaceWaterAbstract / cellArea - allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / cellArea - - else: - logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") - actSurfaceWaterAbstract = pcr.min(availableSurfaceWaterWithQuality/cellArea, surfaceWaterDemand) # unit: m - allocSurfaceWaterAbstract = actSurfaceWaterAbstract # unit: m - - # masking values - # - outgoing water: amount of water abstracted from the source (e.g. river, reservoir pixels) - actSurfaceWaterAbstract = pcr.ifthen(landmask, actSurfaceWaterAbstract) - - # - incoming water: amount of water allocated to pixels with demand (e.g. pixels with irrigation areas) - allocSurfaceWaterAbstract = pcr.ifthen(landmask, allocSurfaceWaterAbstract) - - # water balance on the fly - # - tracking the total amount of water that is abstracted from the source - totalActualSurfaceWaterAbstract = totalActualSurfaceWaterAbstract + actSurfaceWaterAbstract - - # - calculating remaining water available - availableSurfaceWater = availableSurfaceWater - actSurfaceWaterAbstract - - # - tracking the water demand: satisficed and remaining - waterDemandRemaining[sectorPriority] = waterDemandRemaining[sectorPriority] - allocSurfaceWaterAbstract - waterDemandSatisfied[sectorPriority] = waterDemandSatisfied[sectorPriority] + allocSurfaceWaterAbstract - - return totalActualSurfaceWaterAbstract, waterDemandSatisfied \ No newline at end of file diff --git a/model/waterUse.py b/model/waterUse.py index e69de29bb..bfefe4abc 100644 --- a/model/waterUse.py +++ b/model/waterUse.py @@ -0,0 +1,1241 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pcraster as pcr +import virtualOS as vos +from ncConverter import * + +import logging +logger = logging.getLogger(__name__) + +#................................................................................................................................................. + +def waterAbstractionAndAllocation(water_demand_volume, + available_water_volume, + allocation_zones, + zone_area = None, + high_volume_treshold = None, + debug_water_balance = True, + extra_info_for_water_balance_reporting = "", + landmask = None, + ignore_small_values = False, + prioritizing_local_source = True): + + logger.debug("Allocation of abstraction.") + + if landmask is not None: + water_demand_volume = pcr.ifthen(landmask, pcr.cover(water_demand_volume, 0.0)) + available_water_volume = pcr.ifthen(landmask, pcr.cover(available_water_volume, 0.0)) + allocation_zones = pcr.ifthen(landmask, allocation_zones) + + # satistify demand with local sources: + localAllocation = pcr.scalar(0.0) + localAbstraction = pcr.scalar(0.0) + cellVolDemand = pcr.max(0.0, water_demand_volume) + cellAvlWater = pcr.max(0.0, available_water_volume) + + if prioritizing_local_source: + logger.debug("Allocation of abstraction - first, satisfy demand with local source.") + + # demand volume in each cell (unit: m3) + if landmask is not None: + cellVolDemand = pcr.ifthen(landmask, pcr.cover(cellVolDemand, 0.0)) + + # total available water volume in each cell + if landmask is not None: + cellAvlWater = pcr.ifthen(landmask, pcr.cover(cellAvlWater, 0.0)) + + # first, satisfy demand with local source + localAllocation = pcr.max(0.0, pcr.min(cellVolDemand, cellAvlWater)) + localAbstraction = localAllocation * 1.0 + + logger.debug("Allocation of abstraction - satisfy demand with neighbour sources.") + + # the remaining demand and available water + cellVolDemand = pcr.max(0.0, cellVolDemand - localAllocation ) + cellAvlWater = pcr.max(0.0, cellAvlWater - localAbstraction) + + # ignoring small values of water availability + if ignore_small_values: available_water_volume = pcr.max(0.0, pcr.rounddown(available_water_volume)) + + # demand volume in each cell (unit: m3) + cellVolDemand = pcr.max(0.0, cellVolDemand) + if landmask is not None: + cellVolDemand = pcr.ifthen(landmask, pcr.cover(cellVolDemand, 0.0)) + + # total demand volume in each zone/segment (unit: m3) + zoneVolDemand = pcr.areatotal(cellVolDemand, allocation_zones) + + # avoid very high values of available water + cellAvlWater = pcr.min(cellAvlWater, zoneVolDemand) + + # total available water volume in each cell + cellAvlWater = pcr.max(0.0, cellAvlWater) + if landmask is not None: + cellAvlWater = pcr.ifthen(landmask, pcr.cover(cellAvlWater, 0.0)) + + # total available water volume in each zone/segment (unit: m3) + zoneAvlWater = pcr.areatotal(cellAvlWater, allocation_zones) + + # total actual water abstraction volume in each zone/segment (unit: m3) + # - limited to available water + zoneAbstraction = pcr.min(zoneAvlWater, zoneVolDemand) + + # actual water abstraction volume in each cell (unit: m3) + cellAbstraction = vos.getValDivZero(\ + cellAvlWater, zoneAvlWater, vos.smallNumber) * zoneAbstraction + cellAbstraction = pcr.min(cellAbstraction, cellAvlWater) + + # to minimize numerical errors + if high_volume_treshold is not None: + # mask: 0 for small volumes ; 1 for large volumes (e.g. lakes and reservoirs) + mask = pcr.cover(\ + pcr.ifthen(cellAbstraction > high_volume_treshold, pcr.boolean(1)), pcr.boolean(0)) + zoneAbstraction = pcr.areatotal( + pcr.ifthenelse(mask, 0.0, cellAbstraction), allocation_zones) + zoneAbstraction += pcr.areatotal( + pcr.ifthenelse(mask, cellAbstraction, 0.0), allocation_zones) + + # allocation water to meet water demand (unit: m3) + cellAllocation = vos.getValDivZero(\ + cellVolDemand, zoneVolDemand, vos.smallNumber) * zoneAbstraction + cellAllocation = pcr.min(cellAllocation, cellVolDemand) + + # adding local abstraction and local allocation + cellAbstraction = cellAbstraction + localAbstraction + cellAllocation = cellAllocation + localAllocation + + if debug_water_balance and zone_area is not None: + + vos.waterBalanceCheck([pcr.cover(pcr.areatotal(cellAbstraction, allocation_zones)/zone_area, 0.0)],\ + [pcr.cover(pcr.areatotal(cellAllocation , allocation_zones)/zone_area, 0.0)],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)],\ + 'abstraction - allocation per zone/segment (PS: Error here may be caused by rounding error.)' ,\ + True,\ + extra_info_for_water_balance_reporting,threshold=1e-4) + + return cellAbstraction, cellAllocation + + +#..................................................................................................................................................... + + +class WaterUse(object): + + + #................................................................................................................................................. + + + def __init__(self, ls, iniItems, routing, currTimeStep): + # nonIrrGrossDemandDict, + # swAbstractionFractionDict, + # groundwater, + # routing, + # allocSegments, + # currTimeStep, + # desalinationWaterUse, + # groundwater_pumping_region_ids, + # regionalAnnualGroundwaterAbstractionLimit): + + object.__init__(self) + + # if lc.naturalisedConditions == True: + # self.naturalisedConditions() + # else: + # pass + # self.calculateWaterDemand(lc, + # nonIrrGrossDemandDict, + # swAbstractionFractionDict, + # groundwater, + # routing, + # allocSegments, + # currTimeStep, + # desalinationWaterUse, + # groundwater_pumping_region_ids, + # regionalAnnualGroundwaterAbstractionLimit) + + + #................................................................................................................................................. + + + def waterDemandOptions(self,iniItems): + # domestic water demand (unit: m/day) + self.domesticWaterDemandOption = False + if iniItems.landSurfaceOptions['includeDomesticWaterDemand'] == "True": + logger.info("Domestic water demand is included in the calculation.") + self.domesticWaterDemandOption = True + else: + logger.info("Domestic water demand is NOT included in the calculation.") + + if self.domesticWaterDemandOption: + self.domesticWaterDemandFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['domesticWaterDemandFile'],self.inputDir,False) + + # industry water demand (unit: m/day) + self.industryWaterDemandOption = False + if iniItems.landSurfaceOptions['includeIndustryWaterDemand'] == "True": + logger.info("Industry water demand is included in the calculation.") + self.industryWaterDemandOption = True + else: + logger.info("Industry water demand is NOT included in the calculation.") + + if self.industryWaterDemandOption: + self.industryWaterDemandFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['industryWaterDemandFile'],self.inputDir,False) + + # livestock water demand (unit: m/day) + self.livestockWaterDemandOption = False + if iniItems.landSurfaceOptions['includeLivestockWaterDemand'] == "True": + logger.info("Livestock water demand is included in the calculation.") + self.livestockWaterDemandOption = True + else: + logger.info("Livestock water demand is NOT included in the calculation.") + + if self.livestockWaterDemandOption: + self.livestockWaterDemandFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['livestockWaterDemandFile'],self.inputDir,False) + + # historical irrigation area (unit: hectar) + self.dynamicIrrigationArea = False + if iniItems.landSurfaceOptions['historicalIrrigationArea'] != "None": + logger.info("Using the dynamicIrrigationArea option. Extent of irrigation areas is based on the file provided in the 'historicalIrrigationArea'.") + self.dynamicIrrigationArea = True + + if self.dynamicIrrigationArea: + self.dynamicIrrigationAreaFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['historicalIrrigationArea'],self.inputDir,False) + + # irrigation efficiency map (in percentage) # TODO: Using the time series of efficiency (considering historical technological development) + self.irrigationEfficiency = vos.readPCRmapClone(\ + iniItems.landSurfaceOptions['irrigationEfficiency'], + self.cloneMap,self.tmpDir,self.inputDir) + + extrapolate = True + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": + extrapolate = False + + if extrapolate: + # extrapolate efficiency map: # TODO: Make a better extrapolation algorithm (considering cell size, etc.). + + window_size = 1.25 * pcr.clone().cellSize() + window_size = min(window_size, min(pcr.clone().nrRows(), pcr.clone().nrCols())*pcr.clone().cellSize()) + try: + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 0.75)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.00)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.50)) + except: + pass + + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, 1.0) + self.irrigationEfficiency = pcr.max(0.1, self.irrigationEfficiency) + self.irrigationEfficiency = pcr.ifthen(self.landmask, self.irrigationEfficiency) + + # desalination water supply option + self.includeDesalination = False + if iniItems.landSurfaceOptions['desalinationWater'] not in ["None", "False"]: + logger.info("Monthly desalination water is included.") + self.includeDesalination = True + self.desalinationWaterFile = vos.getFullPath(iniItems.landSurfaceOptions['desalinationWater'], self.inputDir) + else: + logger.info("Monthly desalination water is NOT included.") + + # zones at which water allocation (surface and groundwater allocation) is determined + self.usingAllocSegments = False + self.allocSegments = None + if iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'] != "None": + self.usingAllocSegments = True + + self.allocSegments = vos.readPCRmapClone(\ + iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'], + self.cloneMap,self.tmpDir,self.inputDir,isLddMap=False,cover=None,isNomMap=True) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + self.allocSegments = pcr.clump(self.allocSegments) + + # extrapolate values to cover landmask + extrapolate = True + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": + extrapolate = False + if extrapolate: + self.allocSegments = pcr.cover(self.allocSegments, \ + pcr.windowmajority(self.allocSegments, 0.5)) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # clump it and cover the rests with cell ids + self.allocSegments = pcr.clump(self.allocSegments) + cell_ids = pcr.mapmaximum(pcr.scalar(self.allocSegments)) + pcr.scalar(100.0) + pcr.uniqueid(pcr.boolean(1.0)) + self.allocSegments = pcr.cover(self.allocSegments, pcr.nominal(cell_ids)) + self.allocSegments = pcr.clump(self.allocSegments) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # cell area (unit: m2) + cellArea = vos.readPCRmapClone(\ + iniItems.routingOptions['cellAreaMap'], + self.cloneMap,self.tmpDir,self.inputDir) + cellArea = pcr.ifthen(self.landmask, cellArea) + + # zonal/segment area (unit: m2) + self.segmentArea = pcr.areatotal(pcr.cover(cellArea, 0.0), self.allocSegments) + self.segmentArea = pcr.ifthen(self.landmask, self.segmentArea) + + else: + logger.info("If there is any, water demand is satisfied by local source only.") + + + #................................................................................................................................................. + + + def naturalisedConditions(self): + # naturalised conditions: no water abstracctions from the water system + self.totalPotentialGrossDemand = pcr.scalar(0.0) + self.nonIrrGrossDemand = pcr.scalar(0.0) + self.irrGrossDemand = pcr.scalar(0.0) + self.irrGrossDemandPaddy = pcr.scalar(0.0) + self.irrGrossDemandNonPaddy = pcr.scalar(0.0) + self.desalinationAbstraction = pcr.scalar(0.0) + self.desalinationAllocation = pcr.scalar(0.0) + self.actSurfaceWaterAbstract = pcr.scalar(0.0) + self.allocSurfaceWaterAbstract = pcr.scalar(0.0) + self.nonFossilGroundwaterAbs = pcr.scalar(0.0) + self.allocNonFossilGroundwater = pcr.scalar(0.0) + self.fossilGroundwaterAbstr = pcr.scalar(0.0) + self.fossilGroundwaterAlloc = pcr.scalar(0.0) + self.totalGroundwaterAbstraction = pcr.scalar(0.0) + self.totalGroundwaterAllocation = pcr.scalar(0.0) + self.reducedCapRise = pcr.scalar(0.0) + + self.totalPotentialMaximumGrossDemand = pcr.scalar(0.0) + self.totalPotentialMaximumNonIrrGrossDemand = pcr.scalar(0.0) + self.totalPotentialMaximumIrrGrossDemand = pcr.scalar(0.0) + self.totalPotentialMaximumIrrGrossDemandPaddy = pcr.scalar(0.0) + self.totalPotentialMaximumIrrGrossDemandNonPaddy = pcr.scalar(0.0) + + self.domesticWaterWithdrawal = pcr.scalar(0.0) + self.industryWaterWithdrawal = pcr.scalar(0.0) + self.livestockWaterWithdrawal = pcr.scalar(0.0) + + self.nonIrrReturnFlow = pcr.scalar(0.0) + self.irrigationEfficiencyUsed = pcr.scalar(1.0) + + + #................................................................................................................................................. + + + def obtainNonIrrWaterDemand(self,routing,currTimeStep): + # get NON-Irrigation GROSS water demand and its return flow fraction + + # domestic water demand + if currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1: + if self.domesticWaterDemandOption: + # + if self.domesticWaterDemandFile.endswith(vos.netcdf_suffixes): + # + self.domesticGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.domesticWaterDemandFile,\ + 'domesticGrossDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + # + self.domesticNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.domesticWaterDemandFile,\ + 'domesticNettoDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + else: + string_month = str(currTimeStep.month) + if currTimeStep.month < 10: string_month = "0"+str(currTimeStep.month) + grossFileName = self.domesticWaterDemandFile+"w"+str(currTimeStep.year)+".0"+string_month + self.domesticGrossDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(grossFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + nettoFileName = self.domesticWaterDemandFile+"n"+str(currTimeStep.year)+".0"+string_month + self.domesticNettoDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(nettoFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + else: + self.domesticGrossDemand = pcr.spatial(pcr.scalar(0.0)) + self.domesticNettoDemand = pcr.spatial(pcr.scalar(0.0)) + logger.debug("Domestic water demand is NOT included.") + + # gross and netto domestic water demand in m/day + self.domesticGrossDemand = pcr.cover(self.domesticGrossDemand,0.0) + self.domesticNettoDemand = pcr.cover(self.domesticNettoDemand,0.0) + self.domesticNettoDemand = pcr.min(self.domesticGrossDemand, self.domesticNettoDemand) + + # industry water demand + if currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1: + if self.industryWaterDemandOption: + # + if self.industryWaterDemandFile.endswith(vos.netcdf_suffixes): + # + self.industryGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.industryWaterDemandFile,\ + 'industryGrossDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + # + self.industryNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.industryWaterDemandFile,\ + 'industryNettoDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + else: + grossFileName = self.industryWaterDemandFile+"w"+str(currTimeStep.year)+".map" + self.industryGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.readPCRmapClone(grossFileName,self.cloneMap,self.tmpDir), 0.0)) + nettoFileName = self.industryWaterDemandFile+"n"+str(currTimeStep.year)+".map" + self.industryNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.readPCRmapClone(nettoFileName,self.cloneMap,self.tmpDir), 0.0)) + else: + self.industryGrossDemand = pcr.spatial(pcr.scalar(0.0)) + self.industryNettoDemand = pcr.spatial(pcr.scalar(0.0)) + logger.debug("Industry water demand is NOT included.") + + # gross and netto industrial water demand in m/day + self.industryGrossDemand = pcr.cover(self.industryGrossDemand,0.0) + self.industryNettoDemand = pcr.cover(self.industryNettoDemand,0.0) + self.industryNettoDemand = pcr.min(self.industryGrossDemand, self.industryNettoDemand) + + # livestock water demand + if currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1: + if self.livestockWaterDemandOption: + # + if self.livestockWaterDemandFile.endswith(vos.netcdf_suffixes): + # + self.livestockGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.livestockWaterDemandFile,\ + 'livestockGrossDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + # + self.livestockNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.livestockWaterDemandFile,\ + 'livestockNettoDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + else: + string_month = str(currTimeStep.month) + if currTimeStep.month < 10: string_month = "0"+str(currTimeStep.month) + grossFileName = self.livestockWaterDemandFile+"w"+str(currTimeStep.year)+".0"+string_month + self.livestockGrossDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(grossFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + nettoFileName = self.livestockWaterDemandFile+"n"+str(currTimeStep.year)+".0"+string_month + self.livestockNettoDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(nettoFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + else: + self.livestockGrossDemand = pcr.spatial(pcr.scalar(0.0)) + self.livestockNettoDemand = pcr.spatial(pcr.scalar(0.0)) + logger.debug("Livestock water demand is NOT included.") + + # gross and netto livestock water demand in m/day + self.livestockGrossDemand = pcr.cover(self.livestockGrossDemand,0.0) + self.livestockNettoDemand = pcr.cover(self.livestockNettoDemand,0.0) + self.livestockNettoDemand = pcr.min(self.livestockGrossDemand, self.livestockNettoDemand) + + # GROSS domestic, industrial and livestock water demands (unit: m/day) + self.domesticGrossDemand = pcr.ifthen(self.landmask, self.domesticGrossDemand ) + self.domesticNettoDemand = pcr.ifthen(self.landmask, self.domesticNettoDemand ) + self.industryGrossDemand = pcr.ifthen(self.landmask, self.industryGrossDemand ) + self.industryNettoDemand = pcr.ifthen(self.landmask, self.industryNettoDemand ) + self.livestockGrossDemand = pcr.ifthen(self.landmask, self.livestockGrossDemand) + self.livestockNettoDemand = pcr.ifthen(self.landmask, self.livestockNettoDemand) + + # RETURN FLOW fractions for domestic, industrial and livestock water demands (unit: fraction/percentage) + self.domesticReturnFlowFraction = pcr.min(1.0, pcr.max(0.0, 1.0 - vos.getValDivZero(self.domesticNettoDemand, self.domesticGrossDemand))) + self.industryReturnFlowFraction = pcr.min(1.0, pcr.max(0.0, 1.0 - vos.getValDivZero(self.industryNettoDemand, self.industryGrossDemand))) + self.livestockReturnFlowFraction = pcr.min(1.0, pcr.max(0.0, 1.0 - vos.getValDivZero(self.livestockNettoDemand, self.livestockGrossDemand))) + + # make a dictionary summarizing potential demand (potential withdrawal) and its return flow fraction + nonIrrigationWaterDemandDict = {} + nonIrrigationWaterDemandDict['potential_demand'] = {} + nonIrrigationWaterDemandDict['potential_demand']['domestic'] = self.domesticGrossDemand + nonIrrigationWaterDemandDict['potential_demand']['industry'] = self.industryGrossDemand + nonIrrigationWaterDemandDict['potential_demand']['livestock'] = self.livestockGrossDemand + nonIrrigationWaterDemandDict['return_flow_fraction'] = {} + nonIrrigationWaterDemandDict['return_flow_fraction']['domestic'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.domesticReturnFlowFraction *1000.)/1000.), 1.0) + nonIrrigationWaterDemandDict['return_flow_fraction']['industry'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.industryReturnFlowFraction *1000.)/1000.), 1.0) + nonIrrigationWaterDemandDict['return_flow_fraction']['livestock'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.livestockReturnFlowFraction*1000.)/1000.), 1.0) + + return nonIrrigationWaterDemandDict + + #................................................................................................................................................. + + + # def calculateIrrigationDemand(self, lc): + # ''' + # irrigation water demand (unit: m/day) for paddy and non-paddy + # ''' + # self.irrGrossDemand = pcr.scalar(0.) + # if (lc.name == 'irrPaddy' or lc.name == 'irr_paddy') and lc.includeIrrigation: + # self.irrGrossDemand = \ + # pcr.ifthenelse(lc.cropKC > 0.75, + # pcr.max(0.0, + # lc.minTopWaterLayer - (lc.topWaterLayer )), + # 0.0) + # # a function of: - cropKC (evaporation and transpiration), + # # - topWaterLayer (water available in the irrigation field) + + # if (lc.name == 'irrNonPaddy' or lc.name == 'irr_non_paddy' or lc.name == "irr_non_paddy_crops") and lc.includeIrrigation: + # adjDeplFactor = \ + # pcr.max(0.1, + # pcr.min(0.8, + # (lc.cropDeplFactor + 0.04*(5.-lc.totalPotET*1000.)))) + # # original formula based on Allen et al. (1998) + # # see: http://www.fao.org/docrep/x0490e/x0490e0e.htm# + + # # irrigation demand (to fill the entire totAvlWater, maintaining the field capacity, + # # but with the correction of totAvlWater based on the rooting depth) + # # - as the proxy of rooting depth, we use crop coefficient + # self.irrigation_factor = pcr.ifthenelse(lc.cropKC > 0.0,\ + # pcr.min(1.0, lc.cropKC / 1.0), 0.0) + # self.irrGrossDemand = \ + # pcr.ifthenelse( lc.cropKC > 0.20, \ + # pcr.ifthenelse( lc.readAvlWater < \ + # adjDeplFactor*self.irrigation_factor*lc.totAvlWater, \ + # pcr.max(0.0, lc.totAvlWater*self.irrigation_factor-lc.readAvlWater),0.),0.) + + # # irrigation demand is implemented only if there is deficit in transpiration and/or evaporation + # deficit_factor = 1.00 + # evaporationDeficit = pcr.max(0.0, (lc.potBareSoilEvap + lc.potTranspiration)*deficit_factor -\ + # lc.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True)) + # transpirationDeficit = pcr.max(0.0, + # lc.potTranspiration*deficit_factor -\ + # lc.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True, returnTotalTranspirationOnly = True)) + # deficit = pcr.max(evaporationDeficit, transpirationDeficit) + + # # treshold to initiate irrigation + # deficit_treshold = 0.20 * lc.totalPotET + # need_irrigation = pcr.ifthenelse(deficit > deficit_treshold, pcr.boolean(1),\ + # pcr.ifthenelse(lc.soilWaterStorage == 0.000, pcr.boolean(1), pcr.boolean(0))) + # need_irrigation = pcr.cover(need_irrigation, pcr.boolean(0.0)) + + # self.irrGrossDemand = pcr.ifthenelse(need_irrigation, self.irrGrossDemand, 0.0) + + # # demand is limited by potential evaporation for the next coming days + # # - objective: to avoid too high and unrealistic demand + # max_irrigation_interval = 15.0 + # min_irrigation_interval = 7.0 + # irrigation_interval = pcr.min(max_irrigation_interval, \ + # pcr.max(min_irrigation_interval, \ + # pcr.ifthenelse(lc.totalPotET > 0.0, \ + # pcr.roundup((self.irrGrossDemand + pcr.max(lc.readAvlWater, lc.soilWaterStorage))/ lc.totalPotET), 1.0))) + # # - irrigation demand - limited by potential evaporation for the next coming days + # self.irrGrossDemand = pcr.min(pcr.max(0.0,\ + # lc.totalPotET * irrigation_interval - pcr.max(lc.readAvlWater, lc.soilWaterStorage)),\ + # self.irrGrossDemand) + + # # assume that smart farmers do not irrigate higher than infiltration capacities + # if lc.numberOfLayers == 2: self.irrGrossDemand = pcr.min(self.irrGrossDemand, lc.parameters.kSatUpp) + # if lc.numberOfLayers == 3: self.irrGrossDemand = pcr.min(self.irrGrossDemand, lc.parameters.kSatUpp000005) + + # # irrigation efficiency, minimum demand for start irrigating and maximum value to cap excessive demand + # if lc.includeIrrigation: + + # # irrigation efficiency # TODO: Improve the concept of irrigation efficiency + # self.irrigationEfficiencyUsed = pcr.min(1.0, pcr.max(0.10, lc.irrigationEfficiency)) + # # demand, including its inefficiency + # self.irrGrossDemand = pcr.cover(self.irrGrossDemand / pcr.min(1.0, self.irrigationEfficiencyUsed), 0.0) + + # # the following irrigation demand is not limited to available water + # self.irrGrossDemand = pcr.ifthen(lc.landmask, self.irrGrossDemand) + + # # reduce irrGrossDemand by netLqWaterToSoil + # self.irrGrossDemand = pcr.max(0.0, self.irrGrossDemand - lc.netLqWaterToSoil) + + # # minimum demand for start irrigating + # minimum_demand = 0.005 # unit: m/day # TODO: set the minimum demand in the ini/configuration file. + # if lc.name == 'irrPaddy' or lc.name == 'irr_paddy': + # minimum_demand = pcr.min(lc.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. + # self.irrGrossDemand = pcr.ifthenelse(self.irrGrossDemand > minimum_demand, \ + # self.irrGrossDemand , 0.0) + + # maximum_demand = 0.025 # unit: m/day # TODO: set the maximum demand in the ini/configuration file. + # if lc.name == 'irrPaddy' or lc.name == 'irr_paddy': + # maximum_demand = pcr.min(lc.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. + # self.irrGrossDemand = pcr.min(maximum_demand, self.irrGrossDemand) + + # # ignore small irrigation demand (less than 1 mm) + # self.irrGrossDemand = pcr.rounddown( self.irrGrossDemand *1000.)/1000. + + # # irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? + # self.irrGrossDemand = pcr.ifthenelse(lc.fracVegCover > 0.0, self.irrGrossDemand, 0.0) + + # # total irrigation gross demand (m) per cover types (not limited by available water) + # self.totalPotentialMaximumIrrGrossDemandPaddy = 0.0 + # self.totalPotentialMaximumIrrGrossDemandNonPaddy = 0.0 + # if lc.name == 'irrPaddy' or lc.name == 'irr_paddy': + # self.totalPotentialMaximumIrrGrossDemandPaddy = self.irrGrossDemand + # if lc.name == 'irrNonPaddy' or lc.name == 'irr_non_paddy' or lc.name == 'irr_non_paddy_crops': + # self.totalPotentialMaximumIrrGrossDemandNonPaddy = self.irrGrossDemand + + + #................................................................................................................................................. + + + # def calculateWaterDemand(self, lc, + # nonIrrGrossDemandDict, + # swAbstractionFractionDict, + # groundwater, + # routing, + # allocSegments, + # currTimeStep, + # desalinationWaterUse, + # groundwater_pumping_region_ids, + # regionalAnnualGroundwaterAbstractionLimit): + + # # irrigation demand + # self.calculateIrrigationDemand(lc) + + # # non irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? + # nonIrrGrossDemandDict['potential_demand']['domestic'] = pcr.ifthenelse(lc.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] , 0.0) + # nonIrrGrossDemandDict['potential_demand']['industry'] = pcr.ifthenelse(lc.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['industry'] , 0.0) + # nonIrrGrossDemandDict['potential_demand']['livestock'] = pcr.ifthenelse(lc.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['livestock'], 0.0) + + # # non irrigation water demand, including the livestock (not limited by available water) + # self.nonIrrGrossDemand = nonIrrGrossDemandDict['potential_demand']['domestic'] +\ + # nonIrrGrossDemandDict['potential_demand']['industry'] +\ + # nonIrrGrossDemandDict['potential_demand']['livestock'] + + # # total irrigation and livestock demand (not limited by available water) + # totalIrrigationLivestockDemand = self.irrGrossDemand + nonIrrGrossDemandDict['potential_demand']['livestock'] + + # # totalGrossDemand (m): irrigation and non irrigation (not limited by available water) - these values will not be reduced + # self.totalPotentialMaximumGrossDemand = self.irrGrossDemand + self.nonIrrGrossDemand + # # - irrigation (excluding livestock) + # self.totalPotentialMaximumIrrGrossDemand = self.irrGrossDemand + # # - non irrigation (including livestock) + # self.totalPotentialMaximumNonIrrGrossDemand = self.nonIrrGrossDemand + + # # the following value will be reduced by available/accesible water + # self.totalPotentialGrossDemand = self.totalPotentialMaximumGrossDemand + + # #................................................................................................................... + # # Abstraction and Allocation of DESALINATED WATER + # # - desalination water to satisfy water demand + # if lc.usingAllocSegments: # using zone/segments at which networks are defined (as defined in the landSurface options) + # logger.debug("Allocation of supply from desalination water.") + + # volDesalinationAbstraction, volDesalinationAllocation = \ + # waterAbstractionAndAllocation(\ + # water_demand_volume = self.totalPotentialGrossDemand*routing.cellArea,\ + # available_water_volume = pcr.max(0.00, desalinationWaterUse*routing.cellArea),\ + # allocation_zones = allocSegments,\ + # zone_area = lc.segmentArea,\ + # high_volume_treshold = None,\ + # debug_water_balance = True,\ + # extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + # landmask = lc.landmask, + # ignore_small_values = False, + # prioritizing_local_source = lc.prioritizeLocalSourceToMeetWaterDemand) + + # self.desalinationAbstraction = volDesalinationAbstraction / routing.cellArea + # self.desalinationAllocation = volDesalinationAllocation / routing.cellArea + + # else: + # logger.debug("Supply from desalination water is only for satisfying local demand (no network).") + # self.desalinationAbstraction = pcr.min(desalinationWaterUse, self.totalPotentialGrossDemand) + # self.desalinationAllocation = self.desalinationAbstraction + + # self.desalinationAbstraction = pcr.ifthen(lc.landmask, self.desalinationAbstraction) + # self.desalinationAllocation = pcr.ifthen(lc.landmask, self.desalinationAllocation) + + # # water demand that have been satisfied (unit: m/day) - after desalination + # # - for irrigation (excluding livestock) + # satisfiedIrrigationDemand = vos.getValDivZero(self.irrGrossDemand, self.totalPotentialGrossDemand) * self.desalinationAllocation + # # - for domestic, industry and livestock + # satisfiedNonIrrDemand = pcr.max(0.00, self.desalinationAllocation - satisfiedIrrigationDemand) + # # - for domestic + # satisfiedDomesticDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['domestic'], + # self.totalPotentialMaximumNonIrrGrossDemand) + # # - for industry + # satisfiedIndustryDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['industry'], + # self.totalPotentialMaximumNonIrrGrossDemand) + # # - for livestock + # satisfiedLivestockDemand = pcr.max(0.0, satisfiedNonIrrDemand - satisfiedDomesticDemand - satisfiedIndustryDemand) + + # # total remaining gross demand (m/day) after desalination + # ################################################################################################################################ + # self.totalGrossDemandAfterDesalination = pcr.max(0.0, self.totalPotentialGrossDemand - self.desalinationAllocation) + # # the remaining water demand per sector + # # - for domestic + # remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # # - for industry + # remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # # - for livestock + # remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # # - for irrigation (excluding livestock) + # remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # # - total for livestock and irrigation + # remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # # - total for industrial and domestic (excluding livestock) + # remainingIndustrialDomestic = pcr.max(0.0, self.totalGrossDemandAfterDesalination - remainingIrrigationLivestock) + + # # Abstraction and Allocation of SURFACE WATER + # ############################################################################################################################## + # # calculate the estimate of surface water demand (considering by swAbstractionFractionDict) + # # - for industrial and domestic + # swAbstractionFraction_industrial_domestic = pcr.min(swAbstractionFractionDict['max_for_non_irrigation'],\ + # swAbstractionFractionDict['estimate']) + # if swAbstractionFractionDict['non_irrigation'] is not None: + # swAbstractionFraction_industrial_domestic = swAbstractionFractionDict['non_irrigation'] + + # surface_water_demand_estimate = swAbstractionFraction_industrial_domestic * remainingIndustrialDomestic + # # - for irrigation and livestock + # surface_water_irrigation_demand_estimate = swAbstractionFractionDict['irrigation'] * remainingIrrigationLivestock + # # - surface water source as priority if groundwater irrigation fraction is relatively low + # surface_water_irrigation_demand_estimate = \ + # pcr.ifthenelse(swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_maximize_irrigation_surface_water'],\ + # remainingIrrigationLivestock, surface_water_irrigation_demand_estimate) + # # - update estimate of surface water demand withdrawal (unit: m/day) + # surface_water_demand_estimate += surface_water_irrigation_demand_estimate + # # - prioritize surface water use in non productive aquifers that have limited groundwater supply + # surface_water_demand_estimate = pcr.ifthenelse(groundwater.productive_aquifer, surface_water_demand_estimate,\ + # pcr.max(0.0, remainingIrrigationLivestock - \ + # pcr.min(groundwater.avgAllocationShort, groundwater.avgAllocation))) + # # - maximize/optimize surface water use in areas with the overestimation of groundwater supply + # surface_water_demand_estimate += pcr.max(0.0, pcr.max(groundwater.avgAllocationShort, groundwater.avgAllocation) -\ + # (1.0 - swAbstractionFractionDict['irrigation']) * totalIrrigationLivestockDemand -\ + # (1.0 - swAbstractionFraction_industrial_domestic) * (self.totalPotentialMaximumGrossDemand - totalIrrigationLivestockDemand)) + # # + # # total demand (unit: m/day) that should be allocated from surface water + # # (corrected/limited by swAbstractionFractionDict and limited by the remaining demand) + # surface_water_demand_estimate = pcr.min(self.totalGrossDemandAfterDesalination, surface_water_demand_estimate) + # correctedRemainingIrrigationLivestock = pcr.min(surface_water_demand_estimate, remainingIrrigationLivestock) + # correctedRemainingIndustrialDomestic = pcr.min(remainingIndustrialDomestic,\ + # pcr.max(0.0, surface_water_demand_estimate - remainingIrrigationLivestock)) + # correctedSurfaceWaterDemandEstimate = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic + # surface_water_demand = correctedSurfaceWaterDemandEstimate + # # + # # if surface water abstraction as the first priority + # if lc.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination + # # + # if lc.usingAllocSegments: # using zone/segment at which supply network is defined + # # + # logger.debug("Allocation of surface water abstraction.") + # # + # volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ + # vos.waterAbstractionAndAllocation( + # water_demand_volume = surface_water_demand*routing.cellArea,\ + # available_water_volume = pcr.max(0.00, routing.readAvlChannelStorage),\ + # allocation_zones = allocSegments,\ + # zone_area = lc.segmentArea,\ + # high_volume_treshold = None,\ + # debug_water_balance = True,\ + # extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + # landmask = lc.landmask, + # ignore_small_values = False, + # prioritizing_local_source = lc.prioritizeLocalSourceToMeetWaterDemand) + + # self.actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea + # self.allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea + # # + # else: + # logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") + # self.actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ + # surface_water_demand) # unit: m + # self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m + # # + # self.actSurfaceWaterAbstract = pcr.ifthen(lc.landmask, self.actSurfaceWaterAbstract) + # self.allocSurfaceWaterAbstract = pcr.ifthen(lc.landmask, self.allocSurfaceWaterAbstract) + # ################################################################################################################################ + # # - end of Abstraction and Allocation of SURFACE WATER + + + # # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply + # ################################################################################################################################ + # # - for irrigation and livestock water demand + # satisfiedIrrigationLivestockDemandFromSurfaceWater = self.allocSurfaceWaterAbstract * \ + # vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedSurfaceWaterDemandEstimate) + # # - for irrigation water demand, but not including livestock + # satisfiedIrrigationDemandFromSurfaceWater = satisfiedIrrigationLivestockDemandFromSurfaceWater * \ + # vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + # satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater + # # - for non irrigation water demand: livestock, domestic and industry + # satisfiedNonIrrDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract - satisfiedIrrigationDemandFromSurfaceWater) + # satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater + # # - for livestock + # satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromSurfaceWater - \ + # satisfiedIrrigationDemandFromSurfaceWater) + # # - for industrial and domestic demand (excluding livestock) + # satisfiedIndustrialDomesticDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract -\ + # satisfiedIrrigationLivestockDemandFromSurfaceWater) + # # - for domestic + # satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingDomestic, \ + # remainingIndustrialDomestic) + # # - for industry + # satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingIndustry, \ + # remainingIndustrialDomestic) + + # ###################################################################################################################### + # # water demand (unit: m) that must be satisfied by groundwater abstraction (not limited to available water) + # self.potGroundwaterAbstract = pcr.max(0.0, self.totalGrossDemandAfterDesalination - self.allocSurfaceWaterAbstract) + # ###################################################################################################################### + # # water demand per sector + # # - for domestic + # remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # # - for industry + # remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # # - for livestock + # remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # # - for irrigation (excluding livestock) + # remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # # - total for livestock and irrigation + # remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # # - total for industrial and domestic (excluding livestock) + # remainingIndustrialDomestic = remainingIndustry + remainingDomestic + + + # # Abstraction and Allocation of GROUNDWATER (fossil and non fossil) + # ######################################################################################################################### + # # estimating groundwater water demand: + # # - demand for industrial and domestic sectors + # # (all remaining demand for these sectors should be satisfied) + # groundwater_demand_estimate = remainingIndustrialDomestic + # # - demand for irrigation and livestock sectors + # # (only part of them will be satisfied, as they may be too high due to the uncertainty in the irrigation scheme) + # irrigationLivestockGroundwaterDemand = pcr.min(remainingIrrigationLivestock, \ + # pcr.max(0.0, \ + # (1.0 - swAbstractionFractionDict['irrigation'])*totalIrrigationLivestockDemand)) + # groundwater_demand_estimate += irrigationLivestockGroundwaterDemand + + # ##################################################################################################### + # # water demand that must be satisfied by groundwater abstraction (not limited to available water) + # self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, groundwater_demand_estimate) + # ##################################################################################################### + + # # constraining groundwater abstraction with the regional annual pumping capacity + # if groundwater.limitRegionalAnnualGroundwaterAbstraction: + + # logger.debug('Total groundwater abstraction is limited by regional annual pumping capacity.') + + # # estimate of total groundwater abstraction (m3) from the last 365 days: + # tolerating_days = 0. + # annualGroundwaterAbstraction = groundwater.avgAbstraction * routing.cellArea *\ + # pcr.min(pcr.max(0.0, 365.0 - tolerating_days), routing.timestepsToAvgDischarge) + # # total groundwater abstraction (m3) from the last 365 days at the regional scale + # regionalAnnualGroundwaterAbstraction = pcr.areatotal(pcr.cover(annualGroundwaterAbstraction, 0.0), groundwater_pumping_region_ids) + + # #~ # reduction factor to reduce groundwater abstraction/demand + # #~ reductionFactorForPotGroundwaterAbstract = pcr.cover(\ + # #~ pcr.ifthenelse(regionalAnnualGroundwaterAbstractionLimit > 0.0, + # #~ pcr.max(0.000, regionalAnnualGroundwaterAbstractionLimit -\ + # #~ regionalAnnualGroundwaterAbstraction) / + # #~ regionalAnnualGroundwaterAbstractionLimit , 0.0), 0.0) + + # #~ # reduced potential groundwater abstraction (after pumping capacity) + # #~ self.potGroundwaterAbstract = pcr.min(1.00, reductionFactorForPotGroundwaterAbstract) * self.potGroundwaterAbstract + + # #~ # alternative: reduced potential groundwater abstraction (after pumping capacity) and considering the average recharge (baseflow) + # #~ potGroundwaterAbstract = pcr.min(1.00, reductionFactorForPotGroundwaterAbstract) * self.potGroundwaterAbstract + # #~ self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, + # #~ potGroundwaterAbstract + pcr.max(0.0, routing.avgBaseflow / routing.cellArea)) + + # ################## NEW METHOD ################################################################################################################# + # # the remaining pumping capacity (unit: m3) at the regional scale + # remainingRegionalAnnualGroundwaterAbstractionLimit = pcr.max(0.0, regionalAnnualGroundwaterAbstractionLimit - \ + # regionalAnnualGroundwaterAbstraction) + # # considering safety factor (residence time in day-1) + # remainingRegionalAnnualGroundwaterAbstractionLimit *= 0.33 + + # # the remaining pumping capacity (unit: m3) limited by self.potGroundwaterAbstract (at the regional scale) + # remainingRegionalAnnualGroundwaterAbstractionLimit = pcr.min(remainingRegionalAnnualGroundwaterAbstractionLimit,\ + # pcr.areatotal(self.potGroundwaterAbstract * routing.cellArea, groundwater_pumping_region_ids)) + + # # the remaining pumping capacity (unit: m3) at the pixel scale - downscaled using self.potGroundwaterAbstract + # remainingPixelAnnualGroundwaterAbstractionLimit = remainingRegionalAnnualGroundwaterAbstractionLimit * \ + # vos.getValDivZero(self.potGroundwaterAbstract * routing.cellArea, pcr.areatotal(self.potGroundwaterAbstract * routing.cellArea, groundwater_pumping_region_ids)) + + # # reduced (after pumping capacity) potential groundwater abstraction/demand (unit: m) and considering the average recharge (baseflow) + # self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, \ + # remainingPixelAnnualGroundwaterAbstractionLimit/routing.cellArea + pcr.max(0.0, routing.avgBaseflow / routing.cellArea)) + # ################## end of NEW METHOD (but still under development) ########################################################################################################## + + # #~ # Shall we will always try to fulfil the industrial and domestic demand? + # #~ self.potGroundwaterAbstract = pcr.max(remainingIndustrialDomestic, self.potGroundwaterAbstract) + + # else: + # logger.debug('NO LIMIT for regional groundwater (annual) pumping. It may result too high groundwater abstraction.') + + # # Abstraction and Allocation of NON-FOSSIL GROUNDWATER + # # ############################################################################################################################# + # # available storGroundwater (non fossil groundwater) that can be accessed (unit: m) + # readAvlStorGroundwater = pcr.cover(pcr.max(0.00, groundwater.storGroundwater), 0.0) + # # - considering maximum daily groundwater abstraction + # readAvlStorGroundwater = pcr.min(readAvlStorGroundwater, groundwater.maximumDailyGroundwaterAbstraction) + # # - ignore groundwater storage in non-productive aquifer + # readAvlStorGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, readAvlStorGroundwater, 0.0) + + # # for non-productive aquifer, reduce readAvlStorGroundwater to the current recharge/baseflow rate + # readAvlStorGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, \ + # readAvlStorGroundwater, pcr.min(readAvlStorGroundwater, pcr.max(routing.avgBaseflow, 0.0))) + + # # avoid the condition that the entire groundwater volume abstracted instantaneously + # readAvlStorGroundwater *= 0.75 + + # if groundwater.usingAllocSegments: + + # logger.debug('Allocation of non fossil groundwater abstraction.') + + # # TODO: considering aquifer productivity while doing the allocation (e.g. using aquifer transmissivity/conductivity) + + # # non fossil groundwater abstraction and allocation in volume (unit: m3) + # volActGroundwaterAbstract, volAllocGroundwaterAbstract = \ + # vos.waterAbstractionAndAllocation( + # water_demand_volume = self.potGroundwaterAbstract*routing.cellArea,\ + # available_water_volume = pcr.max(0.00, readAvlStorGroundwater*routing.cellArea),\ + # allocation_zones = groundwater.allocSegments,\ + # zone_area = groundwater.segmentArea,\ + # high_volume_treshold = None,\ + # debug_water_balance = True,\ + # extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + # landmask = lc.landmask, + # ignore_small_values = False, + # prioritizing_local_source = lc.prioritizeLocalSourceToMeetWaterDemand) + + # # non fossil groundwater abstraction and allocation in meter + # self.nonFossilGroundwaterAbs = volActGroundwaterAbstract / routing.cellArea + # self.allocNonFossilGroundwater = volAllocGroundwaterAbstract/ routing.cellArea + + # else: + + # logger.debug('Non fossil groundwater abstraction is only for satisfying local demand.') + # self.nonFossilGroundwaterAbs = pcr.min(readAvlStorGroundwater, self.potGroundwaterAbstract) + # self.allocNonFossilGroundwater = self.nonFossilGroundwaterAbs + # ################################################################################################################################ + # # - end of Abstraction and Allocation of NON FOSSIL GROUNDWATER + + # ################################################################################################################################ + # # variable to reduce capillary rise in order to ensure there is always enough water to supply non fossil groundwater abstraction + # self.reducedCapRise = self.nonFossilGroundwaterAbs + # # TODO: Check do we need this for runs with MODFLOW ??? + # ################################################################################################################################ + + + # # water demand that have been satisfied (unit: m/day) - after desalination, surface water and non-fossil groundwater supply + # ################################################################################################################################ + # # - for irrigation and livestock water demand + # satisfiedIrrigationLivestockDemandFromNonFossilGroundwater = self.allocNonFossilGroundwater * \ + # vos.getValDivZero(irrigationLivestockGroundwaterDemand, groundwater_demand_estimate) + # # - for irrigation water demand, but not including livestock + # satisfiedIrrigationDemandFromNonFossilGroundwater = satisfiedIrrigationLivestockDemandFromNonFossilGroundwater * \ + # vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + # satisfiedIrrigationDemand += satisfiedIrrigationDemandFromNonFossilGroundwater + # # - for non irrigation water demand: livestock, domestic and industry + # satisfiedNonIrrDemandFromNonFossilGroundwater = pcr.max(0.0, self.allocNonFossilGroundwater - satisfiedIrrigationLivestockDemandFromNonFossilGroundwater) + # satisfiedNonIrrDemand += satisfiedNonIrrDemandFromNonFossilGroundwater + # # - for livestock + # satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromNonFossilGroundwater - \ + # satisfiedIrrigationDemandFromNonFossilGroundwater) + # # - for industrial and domestic demand (excluding livestock) + # satisfiedIndustrialDomesticDemandFromNonFossilGroundwater = pcr.max(0.0, self.allocNonFossilGroundwater -\ + # satisfiedIrrigationLivestockDemandFromNonFossilGroundwater) + # # - for domestic + # satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromNonFossilGroundwater * vos.getValDivZero(remainingDomestic, remainingIndustrialDomestic) + # # - for industry + # satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromNonFossilGroundwater * vos.getValDivZero(remainingIndustry, remainingIndustrialDomestic) + + # ###################################################################################################################### + # ###################################################################################################################### + # # water demand that must be satisfied by fossil groundwater abstraction (unit: m, not limited to available water) + # self.potFossilGroundwaterAbstract = pcr.max(0.0, self.potGroundwaterAbstract - \ + # self.allocNonFossilGroundwater) + # ###################################################################################################################### + # ###################################################################################################################### + + # # For a run using MODFLOW, the concept of fossil groundwater abstraction is abandoned (lc.limitAbstraction == True): + # if groundwater.useMODFLOW or lc.limitAbstraction: + # logger.debug('Fossil groundwater abstractions are NOT allowed') + # self.fossilGroundwaterAbstr = pcr.scalar(0.0) + # self.fossilGroundwaterAlloc = pcr.scalar(0.0) + + # # Abstraction and Allocation of FOSSIL GROUNDWATER + # # ##################################################################################################################################### + + # if lc.limitAbstraction == False: # TODO: For runs without any water use, we can exclude this. + + # logger.debug('Fossil groundwater abstractions are allowed.') + + # # the remaining water demand (m/day) for all sectors - NOT limited to self.potFossilGroundwaterAbstract + # ##################################################################################################################### + # # - for domestic + # remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # # - for industry + # remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # # - for livestock + # remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # # - for irrigation (excluding livestock) + # remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # # - total for livestock and irrigation + # remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # # - total for industrial and domestic (excluding livestock) + # remainingIndustrialDomestic = remainingIndustry + remainingDomestic + # # - remaining total demand + # remainingTotalDemand = remainingIrrigationLivestock + remainingIndustrialDomestic + + # # constraining fossil groundwater abstraction with regional pumping capacity + # if groundwater.limitRegionalAnnualGroundwaterAbstraction and lc.limitAbstraction == False: + + # logger.debug('Fossil groundwater abstraction is allowed, BUT limited by the regional annual pumping capacity.') + + # # estimate of total groundwater abstraction (m3) from the last 365 days: + # # - considering abstraction from non fossil groundwater + # annualGroundwaterAbstraction += self.nonFossilGroundwaterAbs*routing.cellArea + # # at the regional scale + # regionalAnnualGroundwaterAbstraction = pcr.areatotal(pcr.cover(annualGroundwaterAbstraction, 0.0), groundwater_pumping_region_ids) + + # # fossil groundwater demand/asbtraction reduced by pumping capacity (unit: m/day) + # # - safety factor to avoid the remaining limit abstracted at once (due to overestimation of groundwater demand) + # safety_factor_for_fossil_abstraction = 1.00 + # self.potFossilGroundwaterAbstract *= pcr.min(1.00,\ + # pcr.cover(\ + # pcr.ifthenelse(regionalAnnualGroundwaterAbstractionLimit > 0.0, + # pcr.max(0.000, regionalAnnualGroundwaterAbstractionLimit * safety_factor_for_fossil_abstraction-\ + # regionalAnnualGroundwaterAbstraction) / + # regionalAnnualGroundwaterAbstractionLimit , 0.0), 0.0)) + + # #~ # Shall we will always try to fulfil the remaining industrial and domestic demand? + # #~ self.potFossilGroundwaterAbstract = pcr.max(remainingIndustrialDomestic, self.potFossilGroundwaterAbstract) + + # if lc.limitAbstraction == False: # TODO: For runs without any water use, we can exclude this. + + # ############################################################################################################################### + # # estimate the remaining total demand (unit: m/day) LIMITED to self.potFossilGroundwaterAbstract + # ############################################################################################################################### + + # correctedRemainingTotalDemand = pcr.min(self.potFossilGroundwaterAbstract, remainingTotalDemand) + + # # the remaining industrial and domestic demand and livestock (unit: m/day) limited to self.potFossilGroundwaterAbstract + # # - no correction, we will always try to fulfil these demands + # correctedRemainingIndustrialDomesticLivestock = pcr.min(remainingIndustrialDomestic + remainingLivestock, correctedRemainingTotalDemand) + + # # the remaining irrigation demand limited to self.potFossilGroundwaterAbstract + # correctedRemainingIrrigation = pcr.min(remainingIrrigation, \ + # pcr.max(0.0, correctedRemainingTotalDemand - correctedRemainingIndustrialDomesticLivestock)) + # # - ignore small irrigation demand (less than 1 mm) + # correctedRemainingIrrigation = pcr.rounddown(correctedRemainingIrrigation*1000.)/1000. + + # # the (corrected) remaining total demand (limited to self.potFossilGroundwaterAbstract) + # correctedRemainingTotalDemand = correctedRemainingIndustrialDomesticLivestock + correctedRemainingIrrigation + + # # the (corrected) remaining industrial and domestic demand (excluding livestock) + # correctedRemainingIndustrialDomestic = pcr.min(remainingIndustrialDomestic, correctedRemainingTotalDemand) + + # # the remaining irrigation and livestock water demand limited to self.potFossilGroundwaterAbstract + # correctedRemainingIrrigationLivestock = pcr.min(remainingIrrigationLivestock, \ + # pcr.max(0.0, correctedRemainingTotalDemand - correctedRemainingIndustrialDomestic)) + + # # the (corrected) remaining total demand (unit: m/day) limited to self.potFossilGroundwaterAbstract + # correctedRemainingTotalDemand = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic + + # # TODO: Do the water balance check: correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic <= self.potFossilGroundwaterAbstract + + # # constrain the irrigation groundwater demand with groundwater source fraction + # correctedRemainingIrrigationLivestock = pcr.min((1.0 - swAbstractionFractionDict['irrigation']) * remainingIrrigationLivestock,\ + # correctedRemainingIrrigationLivestock) + # correctedRemainingIrrigationLivestock = pcr.max(0.0,\ + # pcr.min(correctedRemainingIrrigationLivestock,\ + # pcr.max(0.0, totalIrrigationLivestockDemand) * (1.0 - swAbstractionFractionDict['irrigation']) - satisfiedIrrigationDemandFromNonFossilGroundwater)) + + # # ignore fossil groundwater abstraction in irrigation areas dominated by swAbstractionFractionDict['irrigation'] + # correctedRemainingIrrigationLivestock = pcr.ifthenelse(\ + # swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_minimize_fossil_groundwater_irrigation'], 0.0,\ + # correctedRemainingIrrigationLivestock) + + # # reduce the fossil irrigation and livestock demands with enough supply of non fossil groundwater (in order to minimize unrealistic areas of fossil groundwater abstraction) + # # - supply from the average recharge (baseflow) and non fossil groundwater allocation + # nonFossilGroundwaterSupply = pcr.max(pcr.max(0.0, routing.avgBaseflow) / routing.cellArea, \ + # groundwater.avgNonFossilAllocationShort, groundwater.avgNonFossilAllocation) + # # - irrigation supply from the non fossil groundwater + # nonFossilIrrigationGroundwaterSupply = nonFossilGroundwaterSupply * vos.getValDivZero(remainingIrrigationLivestock, remainingTotalDemand) + # # - the corrected/reduced irrigation and livestock demand + # correctedRemainingIrrigationLivestock = pcr.max(0.0, correctedRemainingIrrigationLivestock - nonFossilIrrigationGroundwaterSupply) + + # # the corrected remaining total demand (unit: m/day) + # correctedRemainingTotalDemand = correctedRemainingIndustrialDomestic + correctedRemainingIrrigationLivestock + + # ############################################################################################################################### + + # # water demand that must be satisfied by fossil groundwater abstraction + # self.potFossilGroundwaterAbstract = pcr.min(self.potFossilGroundwaterAbstract, correctedRemainingTotalDemand) + + # if groundwater.limitFossilGroundwaterAbstraction == False and lc.limitAbstraction == False: + + # # Note: If limitFossilGroundwaterAbstraction == False, + # # allocation of fossil groundwater abstraction is not needed. + # msg = 'Fossil groundwater abstractions are without limit for satisfying local demand. ' + # msg = 'Allocation for fossil groundwater abstraction is NOT needed/implemented. ' + # msg += 'However, the fossil groundwater abstraction rate still consider the maximumDailyGroundwaterAbstraction.' + # logger.debug(msg) + + # # fossil groundwater abstraction (unit: m/day) + # self.fossilGroundwaterAbstr = self.potFossilGroundwaterAbstract + # self.fossilGroundwaterAbstr = \ + # pcr.min(\ + # pcr.max(0.0, groundwater.maximumDailyGroundwaterAbstraction - self.nonFossilGroundwaterAbs), self.fossilGroundwaterAbstr) + + # # fossil groundwater allocation (unit: m/day) + # self.fossilGroundwaterAlloc = self.fossilGroundwaterAbstr + + # if groundwater.limitFossilGroundwaterAbstraction and lc.limitAbstraction == False: + + # logger.debug('Fossil groundwater abstractions are allowed, but with limit.') + + # # accesible fossil groundwater (unit: m/day) + # readAvlFossilGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, groundwater.storGroundwaterFossil, 0.0) + # # - residence time (day-1) or safety factor (to avoid 'unrealistic' zero fossil groundwater) + # readAvlFossilGroundwater *= 0.10 + # # - considering maximum daily groundwater abstraction + # readAvlFossilGroundwater = pcr.min(readAvlFossilGroundwater, groundwater.maximumDailyFossilGroundwaterAbstraction, \ + # pcr.max(0.0, groundwater.maximumDailyGroundwaterAbstraction - self.nonFossilGroundwaterAbs)) + # readAvlFossilGroundwater = pcr.max(pcr.cover(readAvlFossilGroundwater, 0.0), 0.0) + + # if groundwater.usingAllocSegments: + + # logger.debug('Allocation of fossil groundwater abstraction.') + + # # TODO: considering aquifer productivity while doing the allocation. + + # # fossil groundwater abstraction and allocation in volume (unit: m3) + # volActGroundwaterAbstract, volAllocGroundwaterAbstract = \ + # vos.waterAbstractionAndAllocation( + # water_demand_volume = self.potFossilGroundwaterAbstract*routing.cellArea,\ + # available_water_volume = pcr.max(0.00, readAvlFossilGroundwater*routing.cellArea),\ + # allocation_zones = groundwater.allocSegments,\ + # zone_area = groundwater.segmentArea,\ + # high_volume_treshold = None,\ + # debug_water_balance = True,\ + # extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + # landmask = lc.landmask, + # ignore_small_values = False, + # prioritizing_local_source = lc.prioritizeLocalSourceToMeetWaterDemand) + + # # fossil groundwater abstraction and allocation in meter + # self.fossilGroundwaterAbstr = volActGroundwaterAbstract /routing.cellArea + # self.fossilGroundwaterAlloc = volAllocGroundwaterAbstract/routing.cellArea + + # else: + + # logger.debug('Fossil groundwater abstraction is only for satisfying local demand. NO Allocation for fossil groundwater abstraction.') + + # self.fossilGroundwaterAbstr = pcr.min(pcr.max(0.0, readAvlFossilGroundwater), self.potFossilGroundwaterAbstract) + # self.fossilGroundwaterAlloc = self.fossilGroundwaterAbstr + + + # # water demand that have been satisfied (m/day) - after desalination, surface water, non fossil groundwater & fossil groundwater + # ################################################################################################################################ + + # # from fossil groundwater, we should prioritize domestic and industrial water demand + # prioritizeFossilGroundwaterForDomesticIndutrial = False # TODO: Define this in the configuration file. + + # if prioritizeFossilGroundwaterForDomesticIndutrial: + + # # - first priority: for industrial and domestic demand (excluding livestock) + # satisfiedIndustrialDomesticDemandFromFossilGroundwater = pcr.min(self.fossilGroundwaterAlloc, \ + # remainingIndustrialDomestic) + # # - for domestic + # satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingDomestic, \ + # remainingIndustrialDomestic) + # # - for industry + # satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingIndustry, \ + # remainingIndustrialDomestic) + # # - for irrigation and livestock demand + # satisfiedIrrigationLivestockDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - \ + # satisfiedIndustrialDomesticDemandFromFossilGroundwater) + # # - for irrigation + # satisfiedIrrigationDemand += satisfiedIrrigationLivestockDemandFromFossilGroundwater * vos.getValDivZero(remainingIrrigation, \ + # remainingIrrigationLivestock) + # # - for livestock + # satisfiedLivestockDemand += satisfiedIrrigationLivestockDemandFromFossilGroundwater * vos.getValDivZero(remainingLivestock, \ + # remainingIrrigationLivestock) + + # else: + + # # Distribute fossil water proportionaly based on the amount of each sector + + # # - for irrigation and livestock water demand + # satisfiedIrrigationLivestockDemandFromFossilGroundwater = self.fossilGroundwaterAlloc * \ + # vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedRemainingTotalDemand) + # # - for irrigation water demand, but not including livestock + # satisfiedIrrigationDemandFromFossilGroundwater = satisfiedIrrigationLivestockDemandFromFossilGroundwater * \ + # vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + # satisfiedIrrigationDemand += satisfiedIrrigationDemandFromFossilGroundwater + # # - for non irrigation water demand: livestock, domestic and industry + # satisfiedNonIrrDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - satisfiedIrrigationDemandFromFossilGroundwater) + # satisfiedNonIrrDemand += satisfiedNonIrrDemandFromFossilGroundwater + # # - for livestock + # satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromFossilGroundwater - \ + # satisfiedIrrigationDemandFromFossilGroundwater) + # # - for industrial and domestic demand (excluding livestock) + # satisfiedIndustrialDomesticDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - \ + # satisfiedIrrigationLivestockDemandFromFossilGroundwater) + # # - for domestic + # satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingDomestic, \ + # remainingIndustrialDomestic) + # # - for industry + # satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingIndustry, \ + # remainingIndustrialDomestic) + + # # water demand limited to available/allocated water + # self.totalPotentialGrossDemand = self.fossilGroundwaterAlloc +\ + # self.allocNonFossilGroundwater +\ + # self.allocSurfaceWaterAbstract +\ + # self.desalinationAllocation + + # # total groundwater abstraction and allocation (unit: m/day) + # self.totalGroundwaterAllocation = self.allocNonFossilGroundwater + self.fossilGroundwaterAlloc + # self.totalGroundwaterAbstraction = self.fossilGroundwaterAbstr + self.nonFossilGroundwaterAbs + + # # irrigation water demand (excluding livestock) limited to available/allocated water (unit: m/day) + # self.irrGrossDemand = satisfiedIrrigationDemand # not including livestock + + # # irrigation gross demand (m) per cover type (limited by available water) + # self.irrGrossDemandPaddy = 0.0 + # self.irrGrossDemandNonPaddy = 0.0 + # if lc.name == 'irrPaddy' or lc.name == "irr_paddy": self.irrGrossDemandPaddy = self.irrGrossDemand + # if lc.name == 'irrNonPaddy' or lc.name == "irr_non_paddy" or lc.name == "irr_non_paddy_crops": self.irrGrossDemandNonPaddy = self.irrGrossDemand + + # # non irrigation water demand (including livestock) limited to available/allocated water (unit: m/day) + # self.nonIrrGrossDemand = pcr.max(0.0, \ + # self.totalPotentialGrossDemand - self.irrGrossDemand) # livestock, domestic and industry + # self.domesticWaterWithdrawal = satisfiedDomesticDemand + # self.industryWaterWithdrawal = satisfiedIndustryDemand + # self.livestockWaterWithdrawal = satisfiedLivestockDemand + + # # return flow (unit: m/day) from non irrigation withdrawal (from domestic, industry and livestock) + # self.nonIrrReturnFlow = nonIrrGrossDemandDict['return_flow_fraction']['domestic'] * self.domesticWaterWithdrawal +\ + # nonIrrGrossDemandDict['return_flow_fraction']['industry'] * self.industryWaterWithdrawal +\ + # nonIrrGrossDemandDict['return_flow_fraction']['livestock']* self.livestockWaterWithdrawal + # # - ignore very small return flow (less than 0.1 mm) + # self.nonIrrReturnFlow = pcr.rounddown(self.nonIrrReturnFlow * 10000.)/10000. + # self.nonIrrReturnFlow = pcr.min(self.nonIrrReturnFlow, self.nonIrrGrossDemand) + + # if lc.debugWaterBalance: + # vos.waterBalanceCheck([self.irrGrossDemand,\ + # self.nonIrrGrossDemand],\ + # [self.totalPotentialGrossDemand],\ + # [pcr.scalar(0.0)],\ + # [pcr.scalar(0.0)] ,\ + # 'waterAllocationForAllSectors',True,\ + # currTimeStep.fulldate,threshold=1e-4) + # vos.waterBalanceCheck([self.domesticWaterWithdrawal,\ + # self.industryWaterWithdrawal,\ + # self.livestockWaterWithdrawal],\ + # [self.nonIrrGrossDemand],\ + # [pcr.scalar(0.0)],\ + # [pcr.scalar(0.0)] ,\ + # 'waterAllocationForNonIrrigationSectors',True,\ + # currTimeStep.fulldate,threshold=1e-4) + # vos.waterBalanceCheck([self.irrGrossDemand,\ + # self.domesticWaterWithdrawal,\ + # self.industryWaterWithdrawal,\ + # self.livestockWaterWithdrawal],\ + # [self.totalPotentialGrossDemand],\ + # [pcr.scalar(0.0)],\ + # [pcr.scalar(0.0)] ,\ + # 'waterAllocationPerSector',True,\ + # currTimeStep.fulldate,threshold=1e-4) + + # # TODO: Perform water balance checks for all sources: desalination, surface water, non-fossil groundwater and fossil groundwater From dc4ed582d6135534f734a43851a925a3b93d8aab Mon Sep 17 00:00:00 2001 From: Gabriel Cardenas Belleza Date: Thu, 28 Sep 2023 10:11:22 +0200 Subject: [PATCH 31/31] this commit is only for backup that we may not need it --- config/setup_05min_wateruse_test.ini | 444 +++ model/landCoverGC.py | 2375 ++++++++++++++++ model/landCoverOrig.py | 3875 ++++++++++++++++++++++++++ model/landSurfaceEdwin.py | 1723 ++++++++++++ model/landSurfaceOrig.py | 1711 ++++++++++++ model/waterUseLandCover.py | 980 +++++++ model/waterUse_v1.py | 888 ++++++ 7 files changed, 11996 insertions(+) create mode 100644 config/setup_05min_wateruse_test.ini create mode 100644 model/landCoverGC.py create mode 100644 model/landCoverOrig.py create mode 100644 model/landSurfaceEdwin.py create mode 100644 model/landSurfaceOrig.py create mode 100644 model/waterUseLandCover.py create mode 100644 model/waterUse_v1.py diff --git a/config/setup_05min_wateruse_test.ini b/config/setup_05min_wateruse_test.ini new file mode 100644 index 000000000..81e57618c --- /dev/null +++ b/config/setup_05min_wateruse_test.ini @@ -0,0 +1,444 @@ +[globalOptions] +# Please set the pcrglobwb output directory (outputDir) in an absolute path. +# - Please make sure that you have access to it. +outputDir = /scratch/carde003/WaterUse/deleteme + +# Please set the clone map file (cloneMap), which defines the spatial resolution and extent of your study area. +# - Please make sure that the file is stored locally in your computing machine. +# - The file must be in the pcraster format. +cloneMap = /scratch/carde003/SWQ/ChaoPhraya/mask_chaophraya.map + +# Set the input directory map in an absolute path. The input forcing and parameter directories and files will be relative to this. +# - The following is an example using files from the opendap server. +#~ inputDir = https://opendap.4tu.nl/thredds/dodsC/data2/pcrglobwb/version_2019_11_beta/pcrglobwb2_input/ +# - The following is an example using input files stored locally in your computing machine. +#~ inputDir = /quanta1/home/hydrowld/data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta/pcrglobwb2_input/ +# - on velocity +inputDir = /data/hydroworld/pcrglobwb2_input_release/version_2019_11_beta_extended/pcrglobwb2_input/ + +# The area/landmask of interest: +# If None, area/landmask is limited for cells with ldd value. +landmask = /scratch/carde003/SWQ/ChaoPhraya/mask_chaophraya.map + +# netcdf attributes for output files: +institution = Department of Physical Geography, Utrecht University +title = PCR-GLOBWB 2 output, with human factors (non-natural) +description = PCR-GLOBWB run with human factors (non-natural) at 5 arcmin resolution + +# Format: YYYY-MM-DD ; The model runs on daily time step. +startTime = 2010-01-01 +endTime = 2010-01-31 + +# spinning up options: +maxSpinUpsInYears = 0 +minConvForSoilSto = 0.0 +minConvForGwatSto = 0.0 +minConvForChanSto = 0.0 +minConvForTotlSto = 0.0 + + +[meteoOptions] +# Set the forcing temperature and precipitation files (relative to inputDir) +precipitationNC = global_30min/meteo/forcing/daily_precipitation_cru_era-interim_1979_to_2010.nc +temperatureNC = global_30min/meteo/forcing/daily_temperature_cru_era-interim_1979_to_2010.nc + +# Method to calculate referencePotETP (reference potential evaporation+transpiration) +# options are "Hamon" and "Input" ; If "Input", the netcdf input file must be given: +referenceETPotMethod = Input +refETPotFileNC = global_30min/meteo/forcing/daily_referencePotET_cru_era-interim_1979_to_2010.nc + + +[meteoDownscalingOptions] +# This section is for a 5 arcmin run, for downscaling meteorological forcing at 30 arcmin to 5 arcmin. +downscalePrecipitation = False +downscaleTemperature = True +downscaleReferenceETPot = False + +# Downscaling (based on the digital elevation model): +# the downscaling will be performed by providing the "cellIds" (meteoDownscaleIds) of lower resolution cells. +meteoDownscaleIds = global_05min/meteo/downscaling_from_30min/uniqueIds_30min.nc +highResolutionDEM = global_05min/meteo/downscaling_from_30min/gtopo05min.nc + +# lapse rates: +temperLapseRateNC = global_05min/meteo/downscaling_from_30min/temperature_slope.nc +precipLapseRateNC = global_05min/meteo/downscaling_from_30min/precipitation_slope.nc + +# downscaling criteria (TODO: remove these): +temperatCorrelNC = global_05min/meteo/downscaling_from_30min/temperature_correl.nc +precipitCorrelNC = global_05min/meteo/downscaling_from_30min/precipitation_correl.nc + +# windows length (unit: arc-degree) for smoothing/averaging forcing data (not recommended): +smoothingWindowsLength = 0 + + +[landSurfaceOptions] +debugWaterBalance = True +numberOfUpperSoilLayers = 2 + +# Soil and parameters +# - they are used for all land cover types, unless they are defined in certain land cover type options +# (e.g. different/various soil types for agriculture areas) +topographyNC = global_05min/landSurface/topography/topography_parameters_5_arcmin_october_2015.nc +soilPropertiesNC = global_05min/landSurface/soil/soilProperties5ArcMin.nc + +# Irrigation properties +includeIrrigation = True + +# - netcdf time series for historical expansion of irrigation areas (unit: hectares). +# (note: The resolution of this map must be consisten with the resolution of cellArea) +historicalIrrigationArea = global_05min/waterUse/irrigation/irrigated_areas/irrigationArea05ArcMin.nc + +# - pcraster map/value defining irrigation efficiency (dimensionless) - optional +irrigationEfficiency = global_30min/waterUse/irrigation/irrigation_efficiency/efficiency.nc + +# Water demands: domestic, industrial and livestock water demand data (unit must be in m.day-1) +naturalisedConditions = None +includeDomesticWaterDemand = True +includeIndustryWaterDemand = True +includeLivestockWaterDemand = True + +domesticWaterDemandFile = global_05min/waterUse/waterDemand/domestic/domestic_water_demand_version_april_2015.nc +industryWaterDemandFile = global_05min/waterUse/waterDemand/industry/industry_water_demand_version_april_2015.nc +livestockWaterDemandFile = global_05min/waterUse/waterDemand/livestock/livestock_water_demand_version_april_2015.nc + +# Desalination water supply (maximum/potential/capacity) +desalinationWater = global_05min/waterUse/desalination/desalination_water_version_april_2015.nc + +# Pooling zones: zone IDs (scale) at which allocations of groundwater and surface water (as well as desalinated water) are performed +allocationSegmentsForGroundSurfaceWater = global_05min/waterUse/abstraction_zones/abstraction_zones_60min_05min.nc + +# pcraster maps defining the partitioning of groundwater - surface water source +# - predefined surface water - groundwater partitioning for irrigation demand (e.g. based on Siebert, Global Map of Irrigation Areas version 5) +irrigationSurfaceWaterAbstractionFractionData = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_SWFRAC.nc +# -- quality map +irrigationSurfaceWaterAbstractionFractionDataQuality = global_05min/waterUse/source_partitioning/surface_water_fraction_for_irrigation/AEI_QUAL.nc + +# - threshold values defining the preference for surface water source for irrigation purpose +# -- treshold to maximize surface water irrigation use (cells with irrSurfaceWaterAbstractionFraction above this will prioritize irrigation surface water use) +treshold_to_maximize_irrigation_surface_water = 0.50 +# -- treshold to minimize fossil water withdrawal for irrigation (cells with irrSurfaceWaterAbstractionFraction below this have no fossil withdrawal for irrigation) +treshold_to_minimize_fossil_groundwater_irrigation = 0.70 + +# - predefined surface water - groundwater partitioning for non irrigation demand (e.g. based on McDonald, 2014) +maximumNonIrrigationSurfaceWaterAbstractionFractionData = global_30min/waterUse/source_partitioning/surface_water_fraction_for_non_irrigation/max_city_sw_fraction.nc + + +[forestOptions] +name = forest +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 + +cropCoefficientNC = global_05min/landSurface/landCover/naturalTall/cropCoefficientForest.nc +interceptCapNC = global_05min/landSurface/landCover/naturalTall/interceptCapInputForest.nc +coverFractionNC = global_05min/landSurface/landCover/naturalTall/coverFractionInputForest.nc + +landCoverMapsNC = None + +# If NC file is not provided, we have to provide the following pcraster maps: +fracVegCover = global_05min/landSurface/landCover/naturalTall/vegf_tall.nc +minSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/minf_tall_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/naturalTall/maxf_tall.nc +rootFraction1 = global_05min/landSurface/landCover/naturalTall/rfrac1_tall.nc +rootFraction2 = global_05min/landSurface/landCover/naturalTall/rfrac2_tall.nc +maxRootDepth = 1.0 +# Note: The maxRootDepth is not used for non irrigated land cover type. + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_forest_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_forest_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_forest_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_forest_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_forest_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_forest_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_forest_1999-12-31.nc + + +[grasslandOptions] +name = grassland +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 + +cropCoefficientNC = global_05min/landSurface/landCover/naturalShort/cropCoefficientGrassland.nc +interceptCapNC = global_05min/landSurface/landCover/naturalShort/interceptCapInputGrassland.nc +coverFractionNC = global_05min/landSurface/landCover/naturalShort/coverFractionInputGrassland.nc + +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/naturalShort/vegf_short.nc +minSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/minf_short_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/naturalShort/maxf_short.nc +rootFraction1 = global_05min/landSurface/landCover/naturalShort/rfrac1_short.nc +rootFraction2 = global_05min/landSurface/landCover/naturalShort/rfrac2_short.nc +maxRootDepth = 0.5 +# Note: The maxRootDepth is not used for non irrigated land cover type. + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_grassland_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_grassland_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_grassland_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_grassland_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_grassland_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_grassland_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_grassland_1999-12-31.nc + + +[irrPaddyOptions] +name = irrPaddy +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 + +# land cover map +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/irrPaddy/fractionPaddy.nc +minSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/minf_paddy_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/irrPaddy/maxf_paddy.nc +rootFraction1 = global_30min/landSurface/landCover/irrPaddy/rfrac1_paddy.nc +rootFraction2 = global_30min/landSurface/landCover/irrPaddy/rfrac2_paddy.nc +maxRootDepth = 0.5 + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# other paramater values +minTopWaterLayer = 0.05 +minCropKC = 0.2 +cropDeplFactor = 0.2 +minInterceptCap = 0.0002 + +cropCoefficientNC = global_30min/landSurface/landCover/irrPaddy/Global_CropCoefficientKc-IrrPaddy_30min.nc + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrPaddy_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrPaddy_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrPaddy_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrPaddy_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrPaddy_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrPaddy_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrPaddy_1999-12-31.nc + + +[irrNonPaddyOptions] +name = irrNonPaddy +debugWaterBalance = True + +# snow module properties +snowModuleType = Simple +freezingT = 0.0 +degreeDayFactor = 0.0025 +snowWaterHoldingCap = 0.1 +refreezingCoeff = 0.05 +# +landCoverMapsNC = None +# If NC file is not provided, we have to provide the following values: +fracVegCover = global_05min/landSurface/landCover/irrNonPaddy/fractionNonPaddy.nc +minSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/minf_nonpaddy_permafrost.nc +maxSoilDepthFrac = global_30min/landSurface/landCover/irrNonPaddy/maxf_nonpaddy.nc +rootFraction1 = global_30min/landSurface/landCover/irrNonPaddy/rfrac1_nonpaddy.nc +rootFraction2 = global_30min/landSurface/landCover/irrNonPaddy/rfrac2_nonpaddy.nc +maxRootDepth = 1.0 + +# Parameters for the Arno's scheme: +arnoBeta = None +# If arnoBeta is defined, the soil water capacity distribution is based on this. +# If arnoBeta is NOT defined, maxSoilDepthFrac must be defined such that arnoBeta will be calculated based on maxSoilDepthFrac and minSoilDepthFrac. + +# other paramater values +minTopWaterLayer = 0.0 +minCropKC = 0.2 +cropDeplFactor = 0.5 +minInterceptCap = 0.0002 + +cropCoefficientNC = global_30min/landSurface/landCover/irrNonPaddy/Global_CropCoefficientKc-IrrNonPaddy_30min.nc + +# initial conditions: +interceptStorIni = global_05min/initialConditions/non-natural/1999/interceptStor_irrNonPaddy_1999-12-31.nc +snowCoverSWEIni = global_05min/initialConditions/non-natural/1999/snowCoverSWE_irrNonPaddy_1999-12-31.nc +snowFreeWaterIni = global_05min/initialConditions/non-natural/1999/snowFreeWater_irrNonPaddy_1999-12-31.nc +topWaterLayerIni = global_05min/initialConditions/non-natural/1999/topWaterLayer_irrNonPaddy_1999-12-31.nc +storUppIni = global_05min/initialConditions/non-natural/1999/storUpp_irrNonPaddy_1999-12-31.nc +storLowIni = global_05min/initialConditions/non-natural/1999/storLow_irrNonPaddy_1999-12-31.nc +interflowIni = global_05min/initialConditions/non-natural/1999/interflow_irrNonPaddy_1999-12-31.nc + + + +[groundwaterOptions] +debugWaterBalance = True + +# The file will containspecificYield (m3.m-3), kSatAquifer (m.day-1), recessionCoeff (day-1) +groundwaterPropertiesNC = global_05min/groundwater/properties/groundwaterProperties5ArcMin.nc +# - minimum value for groundwater recession coefficient (day-1) +minRecessionCoeff = 1.0e-4 + +# some options for constraining groundwater abstraction +limitFossilGroundWaterAbstraction = True +estimateOfRenewableGroundwaterCapacity = 0.0 +estimateOfTotalGroundwaterThickness = global_05min/groundwater/aquifer_thickness_estimate/thickness_05min.nc + +# minimum and maximum total groundwater thickness +minimumTotalGroundwaterThickness = 100. +maximumTotalGroundwaterThickness = None + +# annual pumping capacity for each region (unit: billion cubic meter per year), should be given in a netcdf file +pumpingCapacityNC = global_30min/waterUse/groundwater_pumping_capacity/regional_abstraction_limit.nc + +# initial conditions: +storGroundwaterIni = global_05min/initialConditions/non-natural/1999/storGroundwater_1999-12-31.nc +storGroundwaterFossilIni = global_05min/initialConditions/non-natural/1999/storGroundwaterFossil_1999-12-31.nc + +# additional initial conditions for pumping behaviors +avgNonFossilGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationLong_1999-12-31.nc +avgNonFossilGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgNonFossilGroundwaterAllocationShort_1999-12-31.nc +avgTotalGroundwaterAbstractionIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAbstraction_1999-12-31.nc +avgTotalGroundwaterAllocationLongIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationLong_1999-12-31.nc +avgTotalGroundwaterAllocationShortIni = global_05min/initialConditions/non-natural/1999/avgTotalGroundwaterAllocationShort_1999-12-31.nc + +# additional initial conditions (needed only for MODFLOW run) +relativeGroundwaterHeadIni = global_05min/initialConditions/non-natural/1999/relativeGroundwaterHead_1999-12-31.nc +baseflowIni = global_05min/initialConditions/non-natural/1999/baseflow_1999-12-31.nc + +# zonal IDs (scale) at which zonal allocation of groundwater is performed +allocationSegmentsForGroundwater = global_05min/waterUse/abstraction_zones/abstraction_zones_30min_05min.nc + + + +[routingOptions] +debugWaterBalance = True + +# drainage direction map +lddMap = global_05min/routing/ldd_and_cell_area/lddsound_05min.nc + +# cell area (unit: m2) +cellAreaMap = global_05min/routing/ldd_and_cell_area/cellsize05min.correct.nc + +# routing method: +routingMethod = accuTravelTime +#~ routingMethod = kinematicWave + +# manning coefficient +manningsN = 0.04 + +# Option for flood plain simulation +dynamicFloodPlain = True + +# manning coefficient for floodplain +floodplainManningsN = 0.07 + +# channel gradient +gradient = global_05min/routing/channel_properties/channel_gradient.nc + +# constant channel depth +constantChannelDepth = global_05min/routing/channel_properties/bankfull_depth.nc + +# constant channel width (optional) +constantChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc + +# minimum channel width (optional) +minimumChannelWidth = global_05min/routing/channel_properties/bankfull_width.nc + +# channel properties for flooding +# - if None, it will be estimated from (bankfull) channel depth (m) and width (m) +bankfullCapacity = None + +# files for relative elevation (above minimum dem) +relativeElevationFiles = global_05min/routing/channel_properties/dzRel%04d.nc +relativeElevationLevels = 0.0, 0.01, 0.05, 0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90, 1.00 + +# composite crop factors for WaterBodies: +cropCoefficientWaterNC = global_30min/routing/kc_surface_water/cropCoefficientForOpenWater.nc +minCropWaterKC = 1.00 + +# lake and reservoir parameters +waterBodyInputNC = global_05min/routing/surface_water_bodies/waterBodies5ArcMin.nc +onlyNaturalWaterBodies = False + +# initial conditions: +waterBodyStorageIni = global_05min/initialConditions/non-natural/1999/waterBodyStorage_1999-12-31.nc +channelStorageIni = global_05min/initialConditions/non-natural/1999/channelStorage_1999-12-31.nc +readAvlChannelStorageIni = global_05min/initialConditions/non-natural/1999/readAvlChannelStorage_1999-12-31.nc +avgDischargeLongIni = global_05min/initialConditions/non-natural/1999/avgDischargeLong_1999-12-31.nc +avgDischargeShortIni = global_05min/initialConditions/non-natural/1999/avgDischargeShort_1999-12-31.nc +m2tDischargeLongIni = global_05min/initialConditions/non-natural/1999/m2tDischargeLong_1999-12-31.nc +avgBaseflowLongIni = global_05min/initialConditions/non-natural/1999/avgBaseflowLong_1999-12-31.nc +riverbedExchangeIni = global_05min/initialConditions/non-natural/1999/riverbedExchange_1999-12-31.nc + +# initial condition of sub-time step discharge (needed for estimating number of time steps in kinematic wave methods) +subDischargeIni = global_05min/initialConditions/non-natural/1999/subDischarge_1999-12-31.nc + +avgLakeReservoirInflowShortIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirInflowShort_1999-12-31.nc +avgLakeReservoirOutflowLongIni = global_05min/initialConditions/non-natural/1999/avgLakeReservoirOutflowLong_1999-12-31.nc + +# number of days (timesteps) that have been performed for spinning up initial conditions in the routing module (i.e. channelStorageIni, avgDischargeLongIni, avgDischargeShortIni, etc.) +timestepsToAvgDischargeIni = global_05min/initialConditions/non-natural/1999/timestepsToAvgDischarge_1999-12-31.nc +# Note that: +# - maximum number of days (timesteps) to calculate long term average flow values (default: 5 years = 5 * 365 days = 1825) +# - maximum number of days (timesteps) to calculate short term average values (default: 1 month = 1 * 30 days = 30) + + + +[reportingOptions] +# output files that will be written in the disk in netcdf files: +# - daily resolution (discharge,totalRunoff,gwRecharge,totalGroundwaterAbstraction,surfaceWaterStorage) +outDailyTotNC = None + +# - monthly resolution +# (outMonthTotNC: actualET,runoff,totalRunoff,baseflow,directRunoff,interflowTotal,,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,precipitation,gwRecharge,surfaceWaterInf,referencePotET,totalEvaporation,totalPotentialEvaporation,totLandSurfaceActuaET,totalLandSurfacePotET,waterBodyActEvaporation,waterBodyPotEvaporation) +# (outMonthAvgNC: temperature,dynamicFracWat,surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalActiveStorageThickness,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage) +# (outMonthEndNC: storGroundwater,storGroundwaterFossil,waterBodyStorage,channelStorage,totalWaterStorageThickness,totalActiveStorageThickness) +outMonthTotNC = irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,totalGroundwaterAbstraction,desalinationAbstraction,surfaceWaterAbstraction +outMonthAvgNC = discharge +outMonthEndNC = None + +# - annual resolution +# (outAnnuaTotNC: totalEvaporation,precipitation,gwRecharge,totalRunoff,baseflow,desalinationAbstraction,surfaceWaterAbstraction,nonFossilGroundwaterAbstraction,fossilGroundwaterAbstraction,totalGroundwaterAbstraction,totalAbstraction,irrGrossDemand,nonIrrGrossDemand,totalGrossDemand,nonIrrWaterConsumption,nonIrrReturnFlow,runoff,actualET,irrPaddyWaterWithdrawal,irrNonPaddyWaterWithdrawal,irrigationWaterWithdrawal,domesticWaterWithdrawal,industryWaterWithdrawal,livestockWaterWithdrawal,precipitation_at_irrigation,netLqWaterToSoil_at_irrigation,evaporation_from_irrigation,transpiration_from_irrigation,referencePotET) +# (outAnnuaAvgNC: temperature,discharge,surfaceWaterStorage,waterBodyStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness,satDegUpp,satDegLow,channelStorage,waterBodyStorage,fractionWaterBodyEvaporation,fractionTotalEvaporation,fracSurfaceWaterAllocation,fracDesalinatedWaterAllocation,gwRecharge) +# (outAnnuaEndNC: surfaceWaterStorage,interceptStor,snowFreeWater,snowCoverSWE,topWaterLayer,storUppTotal,storLowTotal,storGroundwater,storGroundwaterFossil,totalWaterStorageThickness) +outAnnuaTotNC = None +outAnnuaAvgNC = None +outAnnuaEndNC = None + +# - monthly and annual maxima (channelStorage,dynamicFracWat,floodVolume,floodDepth,surfaceWaterLevel,discharge,totalRunoff) +outMonthMaxNC = None +outAnnuaMaxNC = None + +# netcdf format and zlib setup +formatNetCDF = NETCDF4 +zlib = True diff --git a/model/landCoverGC.py b/model/landCoverGC.py new file mode 100644 index 000000000..35538bd5f --- /dev/null +++ b/model/landCoverGC.py @@ -0,0 +1,2375 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model +# +# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, +# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, +# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, +# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens +# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import re +import types + +import netCDF4 as nc +import pcraster as pcr + +import logging +logger = logging.getLogger(__name__) + +import virtualOS as vos +from ncConverter import * +import waterUse as wu + +class LandCover(object): + + def __init__(self,iniItems,nameOfSectionInIniFile,soil_and_topo_parameters,landmask,irrigationEfficiency,usingAllocSegments = False): + object.__init__(self) + + self.cloneMap = iniItems.cloneMap + self.tmpDir = iniItems.tmpDir + self.inputDir = iniItems.globalOptions['inputDir'] + self.landmask = landmask + + # number of soil layers: + self.numberOfSoilLayers = int(iniItems.landSurfaceOptions['numberOfUpperSoilLayers']) + + # soil and topo parameters + self.parameters = soil_and_topo_parameters + + # configuration for a certain land cover type + self.iniItemsLC = iniItems.__getattribute__(nameOfSectionInIniFile) + self.name = self.iniItemsLC['name'] + + # limitAbstraction + self.limitAbstraction = False + if iniItems.landSurfaceOptions['limitAbstraction'] == "True": self.limitAbstraction = True + + # if using MODFLOW, limitAbstraction must be True (the abstraction cannot exceed storGroundwater) + if "useMODFLOW" in list(iniItems.groundwaterOptions.keys()): + if iniItems.groundwaterOptions["useMODFLOW"] == "True": self.limitAbstraction = True + + # calculate naturalised conditions + self.naturalisedConditions = False + if iniItems.landSurfaceOptions['naturalisedConditions'] == "True": self.naturalisedConditions = True + + # includeIrrigation + self.includeIrrigation = False + if iniItems.landSurfaceOptions['includeIrrigation'] == "True": self.includeIrrigation = True + + # irrigation efficiency map (dimensionless) + self.irrigationEfficiency = irrigationEfficiency + + # interception module type + # - "Original" is principally the same as defined in van Beek et al., 2014 (default) + # - "Modified" is with a modification by Edwin Sutanudjaja: extending interception definition, using totalPotET for the available energy + self.interceptionModuleType = "Original" + if "interceptionModuleType" in list(self.iniItemsLC.keys()): + if self.iniItemsLC['interceptionModuleType'] == "Modified": + msg = 'Using the "Modified" version of the interception module (i.e. extending interception definition, using totalPotET for the available energy for the interception process).' + logger.info(msg) + self.interceptionModuleType = "Modified" + else: + if self.iniItemsLC['interceptionModuleType'] != "Original": + msg = 'The interceptionModuleType '+self.iniItemsLC['interceptionModuleType']+' is NOT known.' + logger.info(msg) + msg = 'The "Original" interceptionModuleType is used.' + logger.info(msg) + + # minimum interception capacity (only used if interceptionModuleType == "Modified", extended interception definition) + self.minInterceptCap = 0.0 + if self.interceptionModuleType == "Original" and "minInterceptCap" in list(self.iniItemsLC.keys()): + msg = 'As the "Original" interceptionModuleType is used, the "minInterceptCap" value is ignored. The interception scope is only "canopy".' + logger.warning(msg) + if self.interceptionModuleType == "Modified": + self.minInterceptCap = vos.readPCRmapClone(self.iniItemsLC['minInterceptCap'], self.cloneMap, + self.tmpDir, self.inputDir) + + # option to assume surface water as the first priority/alternative for water source (not used) + self.surfaceWaterPiority = False + + # option to activate water balance check + self.debugWaterBalance = True + if self.iniItemsLC['debugWaterBalance'] == "False": self.debugWaterBalance = False + + # Improved Arno Scheme's method: + # - In the "Original" work of van Beek et al., 2011 there is no "directRunoff reduction" + # - However, later (20 April 2011), Rens van Beek introduce this reduction, particularly to maintain soil saturation. This is currently the "Default" method. + self.improvedArnoSchemeMethod = "Default" + if "improvedArnoSchemeMethod" in list(iniItems.landSurfaceOptions.keys()): + self.improvedArnoSchemeMethod = iniItems.landSurfaceOptions['improvedArnoSchemeMethod'] + if self.improvedArnoSchemeMethod == "Original": logger.warning("Using the old/original approach of Improved Arno Scheme. No reduction for directRunoff.") + + # In the original oldcalc script of Rens (2 layer model), the percolation percUpp (P1) can be negative + # - To avoid this, Edwin changed few lines (see the method updateSoilStates) + self.allowNegativePercolation = False + if 'allowNegativePercolation' in list(self.iniItemsLC.keys()) and self.iniItemsLC['allowNegativePercolation'] == "True": + msg = 'Allowing negative values of percolation percUpp (P1), as done in the oldcalc script of PCR-GLOBWB 1.0. \n' + msg += 'Note that this option is only relevant for the two layer soil model.' + logger.warning(msg) + self.allowNegativePercolation = True + + # In the original oldcalc script of Rens, there is a possibility that rootFraction/transpiration is only defined in the bottom layer, while no root in upper layer(s) + # - To avoid this, Edwin changed few lines (see the methods 'scaleRootFractionsFromTwoLayerSoilParameters' and 'estimateTranspirationAndBareSoilEvap') + self.usingOriginalOldCalcRootTranspirationPartitioningMethod = False + if 'usingOriginalOldCalcRootTranspirationPartitioningMethod' in list(self.iniItemsLC.keys()) and self.iniItemsLC['usingOriginalOldCalcRootTranspirationPartitioningMethod'] == "True": + msg = 'Using the original rootFraction/transpiration as defined in the oldcalc script of PCR-GLOBWB 1.0. \n' + msg += 'There is a possibility that rootFraction/transpiration is only defined in the bottom layer, while no root in upper layer(s).' + logger.warning(msg) + self.usingOriginalOldCalcRootTranspirationPartitioningMethod = True + + # get snow module type and its parameters: + self.snowModuleType = self.iniItemsLC['snowModuleType'] + snowParams = ['freezingT', + 'degreeDayFactor', + 'snowWaterHoldingCap', + 'refreezingCoeff'] + for var in snowParams: + input = self.iniItemsLC[str(var)] + vars(self)[var] = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + vars(self)[var] = pcr.spatial(pcr.scalar(vars(self)[var])) + + + # initialization some variables + self.fractionArea = None # area (m2) of a certain land cover type ; will be assigned by the landSurface module + self.naturalFracVegCover = None # fraction (-) of natural area over (entire) cell ; will be assigned by the landSurface module + self.irrTypeFracOverIrr = None # fraction (m2) of a certain irrigation type over (only) total irrigation area ; will be assigned by the landSurface module + + # previous fractions of land cover (needed for transfering states when land cover fraction (annualy) changes + self.previousFracVegCover = None + + # number of soil layers (two or three) + self.numberOfLayers = self.parameters.numberOfLayers + + # an option to introduce changes of land cover parameters (not only fracVegCover) + self.noAnnualChangesInLandCoverParameter = True + if 'annualChangesInLandCoverParameters' in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions['annualChangesInLandCoverParameters'] == "True": self.noAnnualChangesInLandCoverParameter = False + + # get land cover parameters that are fixed for the entire simulation + if self.noAnnualChangesInLandCoverParameter: + if self.numberOfLayers == 2: + self.fracVegCover, self.arnoBeta, self.rootZoneWaterStorageMin, self.rootZoneWaterStorageRange, \ + self.maxRootDepth, self.adjRootFrUpp, self.adjRootFrLow = \ + self.get_land_cover_parameters() + if self.numberOfLayers == 3: + self.fracVegCover, self.arnoBeta, self.rootZoneWaterStorageMin, self.rootZoneWaterStorageRange, \ + self.maxRootDepth, self.adjRootFrUpp000005, self.adjRootFrUpp005030, self.adjRootFrLow030150 = \ + self.get_land_cover_parameters() + # estimate parameters while transpiration is being halved + self.calculateParametersAtHalfTranspiration() + # calculate TAW for estimating irrigation gross demand + if self.includeIrrigation: self.calculateTotAvlWaterCapacityInRootZone() + + # get additional land cover parameters (ALWAYS fixed for the entire simulation) + landCovParamsAdd = ['minTopWaterLayer', + 'minCropKC'] + for var in landCovParamsAdd: + input = self.iniItemsLC[str(var)] + vars(self)[var] = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + if input != "None":\ + vars(self)[var] = pcr.cover(vars(self)[var],0.0) + + # get additional parameter(s) for irrigation areas (ALWAYS fixed for the entire simulation) + if self.includeIrrigation: + # - cropDeplFactor (dimesionless, crop depletion factor while irrigation is being applied), needed for NON paddy irrigation areas + if self.iniItemsLC['name'].startswith('irr') and self.name != "irrPaddy": + self.cropDeplFactor = vos.readPCRmapClone(self.iniItemsLC['cropDeplFactor'], self.cloneMap, \ + self.tmpDir, self.inputDir) + # - infiltration/percolation losses for paddy fields + if self.name == 'irrPaddy' or self.name == 'irr_paddy':\ + self.design_percolation_loss = self.estimate_paddy_infiltration_loss(self.iniItemsLC) + + # water allocation zones: + self.usingAllocSegments = usingAllocSegments # water allocation option: + if self.usingAllocSegments: + + # cellArea (unit: m2) # TODO: If possible, integrate this one with the one coming from the routing module + cellArea = vos.readPCRmapClone(\ + iniItems.routingOptions['cellAreaMap'], + self.cloneMap, self.tmpDir, self.inputDir) + cellArea = pcr.ifthen(self.landmask, cellArea) + + # reading the allocation zone file + self.allocSegments = vos.readPCRmapClone(\ + iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'], + self.cloneMap,self.tmpDir,self.inputDir,isLddMap=False,cover=None,isNomMap=True) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + self.allocSegments = pcr.clump(self.allocSegments) + + # extrapolate it + self.allocSegments = pcr.cover(self.allocSegments, \ + pcr.windowmajority(self.allocSegments, 0.5)) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # clump it and cover the rests with cell ids + self.allocSegments = pcr.clump(self.allocSegments) + cell_ids = pcr.mapmaximum(pcr.scalar(self.allocSegments)) + pcr.scalar(100.0) + pcr.uniqueid(pcr.boolean(1.0)) + self.allocSegments = pcr.cover(self.allocSegments, pcr.nominal(cell_ids)) + self.allocSegments = pcr.clump(self.allocSegments) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # zonal/segment areas (unit: m2) + self.segmentArea = pcr.areatotal(pcr.cover(cellArea, 0.0), self.allocSegments) + self.segmentArea = pcr.ifthen(self.landmask, self.segmentArea) + + # option to prioritize local sources before abstracting water from neighboring cells + self.prioritizeLocalSourceToMeetWaterDemand = iniItems.landSurfaceOptions['prioritizeLocalSourceToMeetWaterDemand'] == "True" + if self.prioritizeLocalSourceToMeetWaterDemand: + msg = "Local water sources are first used before abstracting water from neighboring cells" + logger.info(msg) + + + # get the names of cropCoefficient files: + self.cropCoefficientNC = vos.getFullPath(self.iniItemsLC['cropCoefficientNC'], self.inputDir) + + # get the file names of interceptCap and coverFraction files: + if 'interceptCapNC' in list(self.iniItemsLC.keys()) and 'coverFractionNC' in list(self.iniItemsLC.keys()): + self.interceptCapNC = vos.getFullPath(\ + self.iniItemsLC['interceptCapNC'], self.inputDir) + self.coverFractionNC = vos.getFullPath(\ + self.iniItemsLC['coverFractionNC'], self.inputDir) + else: + msg = 'The netcdf files for interceptCapNC (interception capacity) and/or coverFraction (canopy cover fraction) are NOT defined for the landCover type: ' + self.name + '\n' + msg = 'This run assumes zero canopy interception capacity for this run, UNLESS minInterceptCap (minimum interception capacity) is bigger than zero.' + '\n' + logger.warning(msg) + self.coverFractionNC = None + self.interceptCapNC = None + + if 'coverFractionNC' in list(self.iniItemsLC.keys()) and self.iniItemsLC['coverFractionNC'] == "None": self.coverFractionNC = None + if 'interceptCapNC' in list(self.iniItemsLC.keys()) and self.iniItemsLC['interceptCapNC' ] == "None": self.interceptCapNC = None + + # for reporting: output in netCDF files: + self.report = True + try: + self.outDailyTotNC = self.iniItemsLC['outDailyTotNC'].split(",") + self.outMonthTotNC = self.iniItemsLC['outMonthTotNC'].split(",") + self.outMonthAvgNC = self.iniItemsLC['outMonthAvgNC'].split(",") + self.outMonthEndNC = self.iniItemsLC['outMonthEndNC'].split(",") + except: + self.report = False + if self.report: + self.outNCDir = iniItems.outNCDir + self.netcdfObj = PCR2netCDF(iniItems) + # prepare the netCDF objects and files: + if self.outDailyTotNC[0] != "None": + for var in self.outDailyTotNC: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/" + \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "dailyTot.nc",\ + var,"undefined") + + # monthly output in netCDF files: + # - cummulative + if self.outMonthTotNC[0] != "None": + for var in self.outMonthTotNC: + # initiating monthlyVarTot (accumulator variable): + vars(self)[var+'Tot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthTot.nc",\ + var,"undefined") + # - average + if self.outMonthAvgNC[0] != "None": + for var in self.outMonthAvgNC: + # initiating monthlyVarAvg: + vars(self)[var+'Avg'] = None + # initiating monthlyTotAvg (accumulator variable) + vars(self)[var+'Tot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthAvg.nc",\ + var,"undefined") + # - last day of the month + if self.outMonthEndNC[0] != "None": + for var in self.outMonthEndNC: + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthEnd.nc",\ + var,"undefined") + + + def updateIrrigationWaterEfficiency(self,currTimeStep): + #-RvB: irrigation water efficiency + # this reads in the irrigation water efficiency from the configuration file + # at the start of each calendar year - it can optionally handle netCDF files, + # PCRaster maps or values + + var = 'irrigationWaterEfficiency' + + if var in list(self.iniItemsLC.keys()) or 'irrigationEfficiency' in list(self.iniItemsLC.keys()) and (self.iniItemsLC['name'].startswith('irr')): + msg = "Irrigation efficiency is set based on the file defined in the landCoverOptions." + + if 'irrigationWaterEfficiency' in list(self.iniItemsLC.keys()): + self.iniItemsLC[var] = self.iniItemsLC['irrigationWaterEfficiency'] + + input = self.iniItemsLC[var] + try: + # static input + self.irrigationEfficiency = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + except: + # dynamic input + if 'nc' in os.path.splitext(input)[1]: + #-netCDF file + ncFileIn = vos.getFullPath(input,self.inputDir) + self.irrigationEfficiency = vos.netcdf2PCRobjClone(ncFileIn,var, \ + currTimeStep, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap) + else: + #-assumed PCRaster file, add year and '.map' extension + input= input + '%04d.map' % currTimeStep.year + self.irrigationEfficiency = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + + extrapolate = True + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False + + if extrapolate: + # extrapolate efficiency map: # TODO: Make a better extrapolation algorithm (considering cell size, etc.). + window_size = 1.25 * pcr.clone().cellSize() + window_size = min(window_size, min(pcr.clone().nrRows(), pcr.clone().nrCols())*pcr.clone().cellSize()) + try: + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 0.75)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.00)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.50)) + except: + pass + + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, 1.0) + self.irrigationEfficiency = pcr.max(0.1, self.irrigationEfficiency) + self.irrigationEfficiency = pcr.ifthen(self.landmask, self.irrigationEfficiency) + + else: + msg = "Irrigation efficiency is set based on the file defined in the landSurfaceOptions (for irrigated land cover types only)." + + logger.info(msg) + + + def get_land_cover_parameters(self, date_in_string = None, get_only_fracVegCover = False): + + # obtain the land cover parameters + + # list of model parameters that will be read + landCovParams = ['minSoilDepthFrac', 'maxSoilDepthFrac', + 'rootFraction1', 'rootFraction2', + 'maxRootDepth', + 'fracVegCover'] + # - and 'arnoBeta' + + # an option to return only fracVegCover + if get_only_fracVegCover: landCovParams = ['fracVegCover'] + + # set initial values to None + lc_parameters = {} + if get_only_fracVegCover == False: + for var in landCovParams+['arnoBeta']: lc_parameters[var] = None + + # get parameters that are fixed for the entire simulation: + if date_in_string == None: + + msg = 'Obtaining the land cover parameters that are fixed for the entire simulation.' + logger.debug(msg) + + if self.iniItemsLC['landCoverMapsNC'] == str(None): + # using pcraster maps + landCoverPropertiesNC = None + for var in landCovParams: + input = self.iniItemsLC[str(var)] + lc_parameters[var] = vos.readPCRmapClone(input, self.cloneMap, + self.tmpDir, self.inputDir) + if input != "None": + lc_parameters[var] = pcr.cover(lc_parameters[var], 0.0) + else: + # using netcdf file + landCoverPropertiesNC = vos.getFullPath(\ + self.iniItemsLC['landCoverMapsNC'], self.inputDir) + for var in landCovParams: + lc_parameters[var] = pcr.cover(vos.netcdf2PCRobjCloneWithoutTime(\ + landCoverPropertiesNC, var, \ + cloneMapFileName = self.cloneMap), 0.0) + + # The parameter arnoBeta for the Improved Arno's scheme: + # - There are three ways in defining arnoBeta. The ranks below indicate their priority: + # 1. defined as a pcraster map file or a uniform scalar value (i.e. self.iniItemsLC['arnoBeta']) + # 2. included in the netcdf file (i.e. self.iniItemsLC['landCoverMapsNC']) + # 3. approximated from the minSoilDepthFrac and maxSoilDepthFrac + + lc_parameters['arnoBeta'] = None + if 'arnoBeta' not in list(self.iniItemsLC.keys()) and get_only_fracVegCover == False: self.iniItemsLC['arnoBeta'] = "None" + + # - option one (top priority): using a pcraster file + if self.iniItemsLC['arnoBeta'] != "None" and get_only_fracVegCover == False: + + logger.debug("The parameter arnoBeta: "+str(self.iniItemsLC['arnoBeta'])) + lc_parameters['arnoBeta'] = vos.readPCRmapClone(self.iniItemsLC['arnoBeta'], self.cloneMap,\ + self.tmpDir, self.inputDir) + + # - option two: included in the netcdf file + if (lc_parameters['arnoBeta'] is None + and landCoverPropertiesNC is not None + and not get_only_fracVegCover): + + if vos.checkVariableInNC(landCoverPropertiesNC, "arnoBeta"): + + logger.debug("The parameter arnoBeta is defined in the netcdf file "+str(self.iniItemsLC['arnoBeta'])) + lc_parameters['arnoBeta'] = vos.netcdf2PCRobjCloneWithoutTime(landCoverPropertiesNC, 'arnoBeta', self.cloneMap) + + # - option three: approximated from the minSoilDepthFrac and maxSoilDepthFrac + if lc_parameters['arnoBeta'] is None and not get_only_fracVegCover: + + logger.debug("The parameter arnoBeta is approximated from the minSoilDepthFrac and maxSoilDepthFrac values.") + + # make sure that maxSoilDepthFrac >= minSoilDepthFrac: + # - Note that maxSoilDepthFrac is needed only for calculating arnoBeta, + # while minSoilDepthFrac is needed not only for arnoBeta, but also for rootZoneWaterStorageMin + lc_parameters['maxSoilDepthFrac'] = pcr.max(lc_parameters['maxSoilDepthFrac'], lc_parameters['minSoilDepthFrac']) + + # estimating arnoBeta from the values of maxSoilDepthFrac and minSoilDepthFrac. + lc_parameters['arnoBeta'] = pcr.max(0.001,\ + (lc_parameters['maxSoilDepthFrac']-1.)/(1.-lc_parameters['minSoilDepthFrac'])+\ + self.parameters.orographyBeta-0.01) # Rens's line: BCF[TYPE]= max(0.001,(MAXFRAC[TYPE]-1)/(1-MINFRAC[TYPE])+B_ORO-0.01) + + # get landCovParams that (annualy) changes + # - files provided in netcdf files + if date_in_string != None: + + msg = 'Obtaining the land cover parameters (from netcdf files) for the year/date: '+str(date_in_string) + logger.debug(msg) + + if get_only_fracVegCover: + landCovParams = ['fracVegCover'] + else: + landCovParams += ['arnoBeta'] + + for var in landCovParams: + + # read parameter values from the ncFile mentioned in the ini/configuration file + ini_option = self.iniItemsLC[var+'NC'] + + if ini_option.endswith(vos.netcdf_suffixes): + netcdf_file = vos.getFullPath(ini_option, self.inputDir) + lc_parameters[var] = pcr.cover( + vos.netcdf2PCRobjClone(netcdf_file,var, \ + date_in_string, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap), 0.0) + else: + # reading parameters from pcraster maps or scalar values + try: + lc_parameters[var] = pcr.cover( + pcr.spatial( + vos.readPCRmapClone(ini_option, self.cloneMap,\ + self.tmpDir, self.inputDir)), 0.0) + except: + lc_parameters[var] = vos.readPCRmapClone(ini_option, self.cloneMap,\ + self.tmpDir, self.inputDir) + + # if not defined, arnoBeta would be approximated from the minSoilDepthFrac and maxSoilDepthFrac + if not get_only_fracVegCover and lc_parameters['arnoBeta'] is None: + + logger.debug("The parameter arnoBeta is approximated from the minSoilDepthFrac and maxSoilDepthFrac values.") + + # make sure that maxSoilDepthFrac >= minSoilDepthFrac: + # - Note that maxSoilDepthFrac is needed only for calculating arnoBeta, + # while minSoilDepthFrac is needed not only for arnoBeta, but also for rootZoneWaterStorageMin + lc_parameters['maxSoilDepthFrac'] = pcr.max(lc_parameters['maxSoilDepthFrac'], lc_parameters['minSoilDepthFrac']) + + # estimating arnoBeta from the values of maxSoilDepthFrac and minSoilDepthFrac + lc_parameters['arnoBeta'] = pcr.max(0.001,\ + (lc_parameters['maxSoilDepthFrac']-1.)/(1.-lc_parameters['minSoilDepthFrac'])+\ + self.parameters.orographyBeta-0.01) # Rens's line: BCF[TYPE]= max(0.001,(MAXFRAC[TYPE]-1)/(1-MINFRAC[TYPE])+B_ORO-0.01) + + # limit 0.0 <= fracVegCover <= 1.0 + fracVegCover = pcr.cover(lc_parameters['fracVegCover'], 0.0) + fracVegCover = pcr.max(0.0, fracVegCover) + fracVegCover = pcr.min(1.0, fracVegCover) + + if get_only_fracVegCover: + return pcr.ifthen(self.landmask, fracVegCover) + + # WMIN (unit: m): minimum local soil water capacity within the grid-cell + rootZoneWaterStorageMin = lc_parameters['minSoilDepthFrac'] * \ + self.parameters.rootZoneWaterStorageCap # This is WMIN in the oldcalc script. + + # WMAX - WMIN (unit: m) + rootZoneWaterStorageRange = \ + self.parameters.rootZoneWaterStorageCap -\ + rootZoneWaterStorageMin + + # the parameter arnoBeta (dimensionless) + arnoBeta = pcr.max(0.001, lc_parameters['arnoBeta']) + arnoBeta = pcr.cover(arnoBeta, 0.001) + + # maxium root depth + maxRootDepth = lc_parameters['maxRootDepth'] + + # saving also minSoilDepthFrac and maxSoilDepthFrac (only for debugging purpose) + self.minSoilDepthFrac = lc_parameters['minSoilDepthFrac'] + self.maxSoilDepthFrac = lc_parameters['maxSoilDepthFrac'] + + # saving also rootFraction1 and rootFraction2 (only for debugging purpose) + self.rootFraction1 = lc_parameters['rootFraction1'] + self.rootFraction2 = lc_parameters['rootFraction2'] + + if self.numberOfLayers == 2 and get_only_fracVegCover == False: + + # scaling root fractions + adjRootFrUpp, adjRootFrLow = \ + self.scaleRootFractionsFromTwoLayerSoilParameters(lc_parameters['rootFraction1'], lc_parameters['rootFraction2']) + + # provide all land cover parameters + return pcr.ifthen(self.landmask, fracVegCover), \ + pcr.ifthen(self.landmask, arnoBeta), \ + pcr.ifthen(self.landmask, rootZoneWaterStorageMin), \ + pcr.ifthen(self.landmask, rootZoneWaterStorageRange), \ + pcr.ifthen(self.landmask, maxRootDepth), \ + pcr.ifthen(self.landmask, adjRootFrUpp), \ + pcr.ifthen(self.landmask, adjRootFrLow) \ + + if self.numberOfLayers == 3 and get_only_fracVegCover == False: + + # scaling root fractions + adjRootFrUpp000005, adjRootFrUpp005030, adjRootFrLow030150 = \ + self.scaleRootFractionsFromTwoLayerSoilParameters(lc_parameters['rootFraction1'], lc_parameters['rootFraction2']) + + # provide all land cover parameters + return pcr.ifthen(self.landmask, fracVegCover), \ + pcr.ifthen(self.landmask, arnoBeta), \ + pcr.ifthen(self.landmask, rootZoneWaterStorageMin), \ + pcr.ifthen(self.landmask, rootZoneWaterStorageRange), \ + pcr.ifthen(self.landmask, maxRootDepth), \ + pcr.ifthen(self.landmask, adjRootFrUpp000005), \ + pcr.ifthen(self.landmask, adjRootFrUpp005030), \ + pcr.ifthen(self.landmask, adjRootFrLow030150) \ + + + def estimate_paddy_infiltration_loss(self, iniPaddyOptions): + + # Due to compaction infiltration/percolation loss rate can be much smaller than original soil saturated conductivity + # - Wada et al. (2014) assume it will be 10 times smaller + if self.numberOfLayers == 2:\ + design_percolation_loss = self.parameters.kSatUpp/10. # unit: m/day + if self.numberOfLayers == 3:\ + design_percolation_loss = self.parameters.kSatUpp000005/10. # unit: m/day + + # However, it can also be much smaller especially in well-puddled paddy fields and should avoid salinization problems. + # - Default minimum and maximum percolation loss values based on FAO values Reference: http://www.fao.org/docrep/s2022e/s2022e08.htm + min_percolation_loss = 0.006 + max_percolation_loss = 0.008 + # - Minimum and maximum percolation loss values given in the ini or configuration file: + if 'minPercolationLoss' in list(iniPaddyOptions.keys()) and iniPaddyOptions['minPercolationLoss'] != "None": + min_percolation_loss = vos.readPCRmapClone(iniPaddyOptions['minPercolationLoss'], self.cloneMap, + self.tmpDir, self.inputDir) + if 'maxPercolationLoss' in list(iniPaddyOptions.keys()) and iniPaddyOptions['maxPercolationLoss'] != "None": + min_percolation_loss = vos.readPCRmapClone(iniPaddyOptions['maxPercolationLoss'], self.cloneMap, + self.tmpDir, self.inputDir) + # - percolation loss at paddy fields (m/day) + design_percolation_loss = pcr.max(min_percolation_loss, \ + pcr.min(max_percolation_loss, design_percolation_loss)) + # - if soil condition is already 'good', we will use its original infiltration/percolation rate + if self.numberOfLayers == 2:\ + design_percolation_loss = pcr.min(self.parameters.kSatUpp , design_percolation_loss) + if self.numberOfLayers == 3:\ + design_percolation_loss = pcr.min(self.parameters.kSatUpp000005, design_percolation_loss) + + # PS: The 'design_percolation_loss' is the maximum loss occuring in paddy fields. + return design_percolation_loss + + + def scaleRootFractionsFromTwoLayerSoilParameters(self, rootFraction1, rootFraction2): + + # covering rootFraction1 and rootFraction2 + rootFraction1 = pcr.cover(rootFraction1, 0.0) + rootFraction2 = pcr.cover(rootFraction2, 0.0) + + if self.numberOfLayers == 2: + # root fractions + rootFracUpp = (0.30/0.30) * rootFraction1 + rootFracLow = (1.20/1.20) * rootFraction2 + adjRootFrUpp = vos.getValDivZero(rootFracUpp, (rootFracUpp + rootFracLow)) + adjRootFrLow = vos.getValDivZero(rootFracLow, (rootFracUpp + rootFracLow)) + # RFW1[TYPE]= RFRAC1[TYPE]/(RFRAC1[TYPE]+RFRAC2[TYPE]); + # RFW2[TYPE]= RFRAC2[TYPE]/(RFRAC1[TYPE]+RFRAC2[TYPE]); + # if not defined, put everything in the first layer: + if self.usingOriginalOldCalcRootTranspirationPartitioningMethod == False: + adjRootFrUpp = pcr.max(0.0, pcr.min(1.0, pcr.cover(adjRootFrUpp,1.0))) + adjRootFrLow = pcr.max(0.0, pcr.scalar(1.0) - adjRootFrUpp) + + return adjRootFrUpp, adjRootFrLow + + if self.numberOfLayers == 3: + # root fractions + rootFracUpp000005 = 0.05/0.30 * rootFraction1 + rootFracUpp005030 = 0.25/0.30 * rootFraction1 + rootFracLow030150 = 1.20/1.20 * rootFraction2 + adjRootFrUpp000005 = vos.getValDivZero(rootFracUpp000005, (rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150)) + adjRootFrUpp005030 = vos.getValDivZero(rootFracUpp005030, (rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150)) + adjRootFrLow030150 = vos.getValDivZero(rootFracLow030150, (rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150)) + # + # if not defined, put everything in the first layer: + if self.usingOriginalOldCalcRootTranspirationPartitioningMethod == False: + adjRootFrUpp000005 = pcr.max(0.0, pcr.min(1.0, pcr.cover(adjRootFrUpp000005, 1.0))) + adjRootFrUpp005030 = pcr.max(0.0, pcr.ifthenelse(adjRootFrUpp000005 < 1.0, pcr.min(adjRootFrUpp005030, pcr.scalar(1.0) - adjRootFrUpp000005), 0.0)) + adjRootFrLow030150 = pcr.max(0.0, pcr.scalar(1.0) - (adjRootFrUpp000005 + adjRootFrUpp005030)) + + return adjRootFrUpp000005, adjRootFrUpp005030, adjRootFrLow030150 + + + def calculateParametersAtHalfTranspiration(self): + # average soil parameters at which actual transpiration is halved + if self.numberOfLayers == 2: + denominator = (self.parameters.storCapUpp*self.adjRootFrUpp + + self.parameters.storCapLow*self.adjRootFrLow ) + + self.effSatAt50 = pcr.ifthenelse(denominator > 0.0,\ + (self.parameters.storCapUpp * \ + self.adjRootFrUpp * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueUpp)**\ + (-1./self.parameters.poreSizeBetaUpp) +\ + self.parameters.storCapLow * \ + self.adjRootFrLow * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueLow)**\ + (-1./self.parameters.poreSizeBetaLow)) /\ + (self.parameters.storCapUpp*self.adjRootFrUpp +\ + self.parameters.storCapLow*self.adjRootFrLow ), 0.5) + + self.effPoreSizeBetaAt50 = pcr.ifthenelse(denominator > 0.0,\ + (self.parameters.storCapUpp*self.adjRootFrUpp*\ + self.parameters.poreSizeBetaUpp +\ + self.parameters.storCapLow*self.adjRootFrLow*\ + self.parameters.poreSizeBetaLow) / (\ + (self.parameters.storCapUpp*self.adjRootFrUpp +\ + self.parameters.storCapLow*self.adjRootFrLow )), 0.5*(self.parameters.poreSizeBetaUpp + self.parameters.poreSizeBetaLow)) + + if self.numberOfLayers == 3: + denominator = (self.parameters.storCapUpp000005*self.adjRootFrUpp000005 + + self.parameters.storCapUpp005030*self.adjRootFrUpp005030 + + self.parameters.storCapLow030150*self.adjRootFrLow030150 ) + + self.effSatAt50 = pcr.ifthenelse(denominator > 0.0,\ + (self.parameters.storCapUpp000005 * \ + self.adjRootFrUpp000005 * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueUpp000005)**\ + (-1./self.parameters.poreSizeBetaUpp000005) +\ + self.parameters.storCapUpp005030 * \ + self.adjRootFrUpp005030 * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueUpp000005)**\ + (-1./self.parameters.poreSizeBetaUpp000005) +\ + self.parameters.storCapLow030150 * \ + self.adjRootFrLow030150 * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueLow030150)**\ + (-1./self.parameters.poreSizeBetaLow030150) /\ + (self.parameters.storCapUpp000005*self.adjRootFrUpp000005 +\ + self.parameters.storCapUpp005030*self.adjRootFrUpp005030 +\ + self.parameters.storCapLow030150*self.adjRootFrLow030150 )), 0.5) + + self.effPoreSizeBetaAt50 = pcr.ifthenelse(denominator > 0.0,\ + (self.parameters.storCapUpp000005*self.adjRootFrUpp000005*\ + self.parameters.poreSizeBetaUpp000005 +\ + self.parameters.storCapUpp005030*self.adjRootFrUpp005030*\ + self.parameters.poreSizeBetaUpp005030 +\ + self.parameters.storCapLow030150*self.adjRootFrLow030150*\ + self.parameters.poreSizeBetaLow030150) / \ + (self.parameters.storCapUpp000005*self.adjRootFrUpp000005 +\ + self.parameters.storCapUpp005030*self.adjRootFrUpp005030 +\ + self.parameters.storCapLow030150*self.adjRootFrLow030150 ), 0.5 * (0.5*(self.parameters.poreSizeBetaUpp000005 + \ + self.parameters.poreSizeBetaUpp005030) + self.parameters.poreSizeBetaLow030150)) + + # I don't think that we need the following items. + self.effSatAt50 = pcr.cover(self.effSatAt50, 0.5) + if self.numberOfLayers == 2: self.effPoreSizeBetaAt50 = pcr.cover(self.effPoreSizeBetaAt50, 0.5*(self.parameters.poreSizeBetaUpp + self.parameters.poreSizeBetaLow)) + if self.numberOfLayers == 3: self.effPoreSizeBetaAt50 = pcr.cover(self.effPoreSizeBetaAt50, 0.5 * (0.5*(self.parameters.poreSizeBetaUpp000005 + \ + self.parameters.poreSizeBetaUpp005030) + self.parameters.poreSizeBetaLow030150)) + + # crop only to the landmask region + self.effSatAt50 = pcr.ifthen(self.landmask, self.effSatAt50) + self.effPoreSizeBetaAt50 = pcr.ifthen(self.landmask, self.effPoreSizeBetaAt50) + + + def calculateTotAvlWaterCapacityInRootZone(self): + # total water capacity in the root zone (upper soil layers) + # Note: This is dependent on the land cover type. + + if self.numberOfLayers == 2: + + self.totAvlWater = \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp - self.parameters.effSatAtWiltPointUpp))*\ + (self.parameters.satVolMoistContUpp - self.parameters.resVolMoistContUpp )*\ + pcr.min(self.parameters.thickUpp,self.maxRootDepth) + \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapLow - self.parameters.effSatAtWiltPointLow))*\ + (self.parameters.satVolMoistContLow - self.parameters.resVolMoistContLow )*\ + pcr.min(self.parameters.thickLow,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp,0.)) # Edwin modified this line. Edwin uses soil thickness thickUpp and thickLow (instead of storCapUpp and storCapLow). + # And Rens support this. + self.totAvlWater = pcr.min(self.totAvlWater, \ + self.parameters.storCapUpp + self.parameters.storCapLow) + + if self.numberOfLayers == 3: + + self.totAvlWater = \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp000005 - self.parameters.effSatAtWiltPointUpp000005))*\ + (self.parameters.satVolMoistContUpp000005 - self.parameters.resVolMoistContUpp000005 )*\ + pcr.min(self.parameters.thickUpp000005,self.maxRootDepth) + \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp005030 - self.parameters.effSatAtWiltPointUpp005030))*\ + (self.parameters.satVolMoistContUpp005030 - self.parameters.resVolMoistContUpp005030 )*\ + pcr.min(self.parameters.thickUpp005030,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp000005)) + \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapLow030150 - self.parameters.effSatAtWiltPointLow030150))*\ + (self.parameters.satVolMoistContLow030150 - self.parameters.resVolMoistContLow030150 )*\ + pcr.min(self.parameters.thickLow030150,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp005030,0.)) + # + self.totAvlWater = pcr.min(self.totAvlWater, \ + self.parameters.storCapUpp000005 + \ + self.parameters.storCapUpp005030 + \ + self.parameters.storCapLow030150) + + + def getICsLC(self,iniItems,iniConditions = None): + if self.numberOfLayers == 2: + # List of state and flux variables: + initialVars = ['interceptStor', + 'snowCoverSWE','snowFreeWater', + 'topWaterLayer', + 'storUpp', + 'storLow', + 'interflow'] + for var in initialVars: + if iniConditions == None: + input = self.iniItemsLC[str(var)+'Ini'] + vars(self)[var] = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + vars(self)[var] = pcr.cover(vars(self)[var], 0.0) + else: + vars(self)[var] = iniConditions[str(var)] + vars(self)[var] = pcr.ifthen(self.landmask,vars(self)[var]) + + if self.numberOfLayers == 3: + # List of state and flux variables: + initialVars = ['interceptStor', + 'snowCoverSWE','snowFreeWater', + 'topWaterLayer', + 'storUpp000005','storUpp005030', + 'storLow030150', + 'interflow'] + for var in initialVars: + if iniConditions == None: + input = self.iniItemsLC[str(var)+'Ini'] + vars(self)[var] = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir, + cover = 0.0) + vars(self)[var] = pcr.cover(vars(self)[var], 0.0) + else: + vars(self)[var] = iniConditions[str(var)] + vars(self)[var] = pcr.ifthen(self.landmask,vars(self)[var]) + +#..................................................................................................................................................... + + def updateLC(self,meteo,groundwater,routing, + capRiseFrac, + nonIrrGrossDemandDict, + swAbstractionFractionDict, + currTimeStep, + allocSegments, + desalinationWaterUse, + groundwater_pumping_region_ids, + regionalAnnualGroundwaterAbstractionLimit): + + # get land cover parameters at the first day of the year or the first day of the simulation + if self.noAnnualChangesInLandCoverParameter == False and\ + (currTimeStep.timeStepPCR == 1 or currTimeStep.doy == 1): + if self.numberOfLayers == 2: + self.fracVegCover, self.arnoBeta, self.rootZoneWaterStorageMin, self.rootZoneWaterStorageRange, \ + self.maxRootDepth, self.adjRootFrUpp, self.adjRootFrLow = \ + self.get_land_cover_parameters(currTimeStep.fulldate) + if self.numberOfLayers == 3: + self.fracVegCover, self.arnoBeta, self.rootZoneWaterStorageMin, self.rootZoneWaterStorageRange, \ + self.maxRootDepth, self.adjRootFrUpp000005, self.adjRootFrUpp005030, self.adjRootFrLow030150 = \ + self.get_land_cover_parameters(currTimeStep.fulldate) + # estimate parameters while transpiration is being halved + self.calculateParametersAtHalfTranspiration() + # calculate TAW for estimating irrigation gross demand + if self.includeIrrigation: self.calculateTotAvlWaterCapacityInRootZone() + + # calculate total PotET (based on meteo and cropKC) + self.getPotET(meteo,currTimeStep) + + # calculate interception evaporation flux (m/day) and update interception storage (m) + self.interceptionUpdate(meteo, currTimeStep) + + # calculate snow melt (or refreezing) + if self.snowModuleType == "Simple": self.snowMeltHBVSimple(meteo,currTimeStep) + # TODO: Define other snow modules + + # calculate qDR & qSF & q23 (and update storages) + self.upperSoilUpdate(meteo, \ + groundwater, \ + routing, \ + capRiseFrac, \ + nonIrrGrossDemandDict, + swAbstractionFractionDict,\ + currTimeStep, \ + allocSegments, \ + desalinationWaterUse, \ + groundwater_pumping_region_ids,regionalAnnualGroundwaterAbstractionLimit) + + # saturation degrees (needed only for reporting): + if self.numberOfSoilLayers == 2: + self.satDegUpp = vos.getValDivZero(\ + self.storUpp, self.parameters.storCapUpp,\ + vos.smallNumber,0.) + self.satDegUpp = pcr.ifthen(self.landmask, self.satDegUpp) + self.satDegLow = vos.getValDivZero(\ + self.storLow, self.parameters.storCapLow,\ + vos.smallNumber,0.) + self.satDegLow = pcr.ifthen(self.landmask, self.satDegLow) + + self.satDegUppTotal = self.satDegUpp + self.satDegLowTotal = self.satDegLow + + self.satDegTotal = pcr.ifthen(self.landmask, \ + vos.getValDivZero(\ + self.storUpp + self.storLow, self.parameters.storCapUpp + self.parameters.storCapLow,\ + vos.smallNumber, 0.0)) + + if self.numberOfSoilLayers == 3: + self.satDegUpp000005 = vos.getValDivZero(\ + self.storUpp000005, self.parameters.storCapUpp000005,\ + vos.smallNumber,0.) + self.satDegUpp000005 = pcr.ifthen(self.landmask, self.satDegUpp000005) + self.satDegUpp005030 = vos.getValDivZero(\ + self.storUpp005030, self.parameters.storCapUpp005030,\ + vos.smallNumber,0.) + self.satDegUpp005030 = pcr.ifthen(self.landmask, self.satDegUpp005030) + self.satDegLow030150 = vos.getValDivZero(\ + self.storLow030150, self.parameters.storCapLow030150,\ + vos.smallNumber,0.) + self.satDegLow030150 = pcr.ifthen(self.landmask, self.satDegLow030150) + + self.satDegUppTotal = vos.getValDivZero(\ + self.storUpp000005 + self.storUpp005030,\ + self.parameters.storCapUpp000005 + \ + self.parameters.storCapUpp005030,\ + vos.smallNumber,0.) + self.satDegUppTotal = pcr.ifthen(self.landmask, self.satDegUppTotal) + self.satDegLowTotal = self.satDegLow030150 + + self.satDegTotal = pcr.ifthen(self.landmask, \ + vos.getValDivZero(\ + self.storUpp000005 + self.storUpp005030 + self.satDegLow030150, self.parameters.storCapUpp000005 + self.parameters.storCapUpp005030 + self.parameters.storCapLow030150,\ + vos.smallNumber, 0.0)) + + if self.report == True: + # writing Output to netcdf files + # - daily output: + timeStamp = datetime.datetime(currTimeStep.year,\ + currTimeStep.month,\ + currTimeStep.day,\ + 0) + timestepPCR = currTimeStep.timeStepPCR + if self.outDailyTotNC[0] != "None": + for var in self.outDailyTotNC: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "dailyTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,timestepPCR-1) + + # writing monthly output to netcdf files + # -cummulative + if self.outMonthTotNC[0] != "None": + for var in self.outMonthTotNC: + # introduce variables at the beginning of simulation: + if currTimeStep.timeStepPCR == 1: vars(self)[var+'Tot'] = \ + pcr.scalar(0.0) + # reset variables at the beginning of the month + if currTimeStep.day == 1: vars(self)[var+'Tot'] = \ + pcr.scalar(0.0) + # accumulating + vars(self)[var+'Tot'] += vars(self)[var] + # reporting at the end of the month: + if currTimeStep.endMonth == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'Tot'),vos.MV),\ + timeStamp,currTimeStep.monthIdx-1) + # -average + if self.outMonthAvgNC[0] != "None": + for var in self.outMonthAvgNC: + # only if a accumulator variable has not been defined: + if var not in self.outMonthTotNC: + # introduce accumulator variables at the beginning of simulation: + if currTimeStep.timeStepPCR == 1: vars(self)[var+'Tot'] = \ + pcr.scalar(0.0) + # reset variables at the beginning of the month + if currTimeStep.day == 1: vars(self)[var+'Tot'] = \ + pcr.scalar(0.0) + # accumulating + vars(self)[var+'Tot'] += vars(self)[var] + # calculating average and reporting at the end of the month: + if currTimeStep.endMonth == True: + vars(self)[var+'Avg'] = vars(self)[var+'Tot'] /\ + currTimeStep.day + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthAvg.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'Avg'),vos.MV),\ + timeStamp,currTimeStep.monthIdx-1) + # -last day of the month + if self.outMonthEndNC[0] != "None": + for var in self.outMonthEndNC: + # reporting at the end of the month: + if currTimeStep.endMonth == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthEnd.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,currTimeStep.monthIdx-1) + + + def getPotET(self, meteo, currTimeStep): + # get crop coefficient + if self.iniItemsLC['cropCoefficientNC'] == "None": + cropKC = pcr.ifthen(self.landmask, pcr.spatial(pcr.scalar(0.0))) + else: + cropKC = pcr.cover( + vos.netcdf2PCRobjClone(self.cropCoefficientNC,'kc', \ + currTimeStep.fulldate, useDoy = 'daily_seasonal',\ + cloneMapFileName = self.cloneMap), 0.0) + self.inputCropKC = cropKC # This line is needed for debugging. (Can we remove this?) + self.cropKC = pcr.max(cropKC, self.minCropKC) + + # calculate potential ET (unit: m/day) + self.totalPotET = pcr.ifthen(self.landmask,\ + self.cropKC * meteo.referencePotET) + + # calculate potential bare soil evaporation and transpiration (unit: m/day) + self.potBareSoilEvap = pcr.ifthen(self.landmask,\ + self.minCropKC * meteo.referencePotET) + self.potTranspiration = pcr.max(0.0, \ + pcr.ifthen(self.landmask,\ + self.totalPotET - self.potBareSoilEvap)) + + if self.debugWaterBalance: + vos.waterBalanceCheck([self.totalPotET],\ + [self.potBareSoilEvap, self.potTranspiration],\ + [],\ + [],\ + 'partitioning potential evaporation',\ + True,\ + currTimeStep.fulldate,threshold=5e-4) + + + def interceptionUpdate(self, meteo, currTimeStep): + if self.debugWaterBalance: + prevStates = [self.interceptStor] + + # get interceptCap: + interceptCap = pcr.scalar(self.minInterceptCap) + coverFraction = pcr.scalar(1.0) + if self.interceptCapNC != None and self.coverFractionNC != None: + interceptCap = \ + pcr.cover( + vos.netcdf2PCRobjClone(self.interceptCapNC,\ + 'interceptCapInput',\ + currTimeStep.fulldate, useDoy = 'daily_seasonal',\ + cloneMapFileName = self.cloneMap), 0.0) + self.interceptCapInput = interceptCap # This line is needed for debugging. + coverFraction = \ + pcr.cover( + vos.netcdf2PCRobjClone(self.coverFractionNC,\ + 'coverFractionInput',\ + currTimeStep.fulldate, useDoy = 'daily_seasonal',\ + cloneMapFileName = self.cloneMap), 0.0) + coverFraction = pcr.cover(coverFraction, 0.0) + interceptCap = coverFraction * interceptCap + + # canopy/cover fraction over the entire cell area (unit: m2) + self.coverFraction = coverFraction + + # Edwin added the following line to extend the interception definition. + self.interceptCap = pcr.max(interceptCap, self.minInterceptCap) + + # throughfall = surplus above the interception storage threshold + if self.interceptionModuleType == "Modified": + # extended interception definition/scope (not only canopy) + self.throughfall = pcr.max(0.0, self.interceptStor + \ + meteo.precipitation - \ + self.interceptCap) # original Rens line: PRP = (1-CFRAC[TYPE])*PRPTOT+max(CFRAC[TYPE]*PRPTOT+INTS_L[TYPE]-ICC[TYPE],0) + # Edwin modified this line to extend the interception scope (not only canopy interception). + if self.interceptionModuleType == "Original": + # only canopy interception (not only canopy) + self.throughfall = (1.0 - coverFraction) * meteo.precipitation +\ + pcr.max(0.0, coverFraction * meteo.precipitation + self.interceptStor - self.interceptCap) + + # update interception storage after throughfall + self.interceptStor = pcr.max(0.0, self.interceptStor + \ + meteo.precipitation - \ + self.throughfall) # original Rens line: INTS_L[TYPE] = max(0,INTS_L[TYPE]+PRPTOT-PRP) + + # partitioning throughfall into snowfall and liquid Precipitation: + estimSnowfall = pcr.ifthenelse(meteo.temperature < self.freezingT, \ + meteo.precipitation, 0.0) + # original Rens line: SNOW = if(TA0,PRP/PRPTOT,0) + # - liquid precipitation (m/day) + self.liquidPrecip = pcr.max(0.0,\ + self.throughfall - self.snowfall) # original Rens line: PRP = PRP-SNOW + + # potential interception flux (m/day) + # - this is depending on 'interceptionModuleType' + if self.interceptionModuleType == 'Original': + # only canopy interception + self.potInterceptionFlux = self.potTranspiration + if self.interceptionModuleType == 'Modified': + # extended interception definition/scope (not only canopy) + self.potInterceptionFlux = self.totalPotET # added by Edwin to extend the interception scope/definition + + + # evaporation from intercepted water (based on potInterceptionFlux) + # - based on Van Beek et al. (2011) + self.interceptEvap = pcr.min(self.interceptStor, \ + self.potInterceptionFlux * \ + (vos.getValDivZero(self.interceptStor, self.interceptCap, \ + vos.smallNumber, 0.) ** (2.00/3.00))) + # EACT_L[TYPE]= min(INTS_L[TYPE],(T_p[TYPE]*if(ICC[TYPE]>0,INTS_L[TYPE]/ICC[TYPE],0)**(2/3))) + # update interception storage + self.interceptStor = pcr.max(0.0, \ + self.interceptStor - self.interceptEvap) # INTS_L[TYPE]= INTS_L[TYPE]-EACT_L[TYPE] + + # update potBareSoilEvap and potTranspiration after interceptEvap + if self.interceptionModuleType == 'Modified': + # fraction of potential bare soil evaporation and transpiration + fracPotBareSoilEvap = pcr.max(0.0, pcr.min(1.0, \ + vos.getValDivZero(self.potBareSoilEvap, \ + self.potBareSoilEvap + self.potTranspiration, vos.smallNumber))) + fracPotTranspiration = pcr.scalar(1.0 - self.fracPotBareSoilEvap) + # substract interceptEvap from potBareSoilEvap and potTranspiration + self.potBareSoilEvap = pcr.max(0.0, self.potBareSoilEvap -\ + fracPotBareSoilEvap * self.interceptEvap) + self.potTranspiration = pcr.max(0.0, self.potTranspiration -\ + fracPotTranspiration * self.interceptEvap) + # original Rens line: T_p[TYPE] = max(0,T_p[TYPE]-EACT_L[TYPE]) + # Edwin modified this line to extend the interception scope/definition (not only canopy interception). + if self.interceptionModuleType == 'Original': + self.potTranspiration = pcr.max(0.0, self.potTranspiration - self.interceptEvap) + + # update actual evaporation (after interceptEvap) + self.actualET = 0. # interceptEvap is the first flux in ET + self.actualET += self.interceptEvap + + if self.debugWaterBalance: + vos.waterBalanceCheck([self.throughfall],\ + [self.snowfall, self.liquidPrecip],\ + [],\ + [],\ + 'rain-snow-partitioning',\ + True,\ + currTimeStep.fulldate, threshold=1e-5) + vos.waterBalanceCheck([meteo.precipitation], + [self.throughfall, self.interceptEvap], + prevStates,\ + [self.interceptStor],\ + 'interceptStor',\ + True,\ + currTimeStep.fulldate,threshold=1e-4) + + def snowMeltHBVSimple(self,meteo,currTimeStep): + + if self.debugWaterBalance: + prevStates = [self.snowCoverSWE,self.snowFreeWater] + prevSnowCoverSWE = self.snowCoverSWE + prevSnowFreeWater = self.snowFreeWater + + # changes in snow cover: - melt ; + gain in snow or refreezing + deltaSnowCover = \ + pcr.ifthenelse(meteo.temperature <= self.freezingT, \ + self.refreezingCoeff*self.snowFreeWater, \ + -pcr.min(self.snowCoverSWE, \ + pcr.max(meteo.temperature - self.freezingT, 0.0) * \ + self.degreeDayFactor)*1.0*1.0) # DSC[TYPE] = if(TA<=TT,CFR*SCF_L[TYPE], + # -min(SC_L[TYPE],max(TA-TT,0)*CFMAX*Duration*timeslice())) + + # update snowCoverSWE + self.snowCoverSWE = pcr.max(0.0, self.snowfall + deltaSnowCover + self.snowCoverSWE) + # SC_L[TYPE] = max(0.0, SC_L[TYPE]+DSC[TYPE]+SNOW) + + # for reporting snow melt in m/day + self.snowMelt = pcr.ifthenelse(deltaSnowCover < 0.0, deltaSnowCover * pcr.scalar(-1.0), pcr.scalar(0.0)) + + # update snowFreeWater = liquid water stored above snowCoverSWE + self.snowFreeWater = self.snowFreeWater - deltaSnowCover + \ + self.liquidPrecip # SCF_L[TYPE] = SCF_L[TYPE]-DSC[TYPE]+PRP; + + # netLqWaterToSoil = net liquid transferred to soil + self.netLqWaterToSoil = pcr.max(0., self.snowFreeWater - \ + self.snowWaterHoldingCap * self.snowCoverSWE) # Pn = max(0,SCF_L[TYPE]-CWH*SC_L[TYPE]) + + # update snowFreeWater (after netLqWaterToSoil) + self.snowFreeWater = pcr.max(0., self.snowFreeWater - \ + self.netLqWaterToSoil) # SCF_L[TYPE] = max(0,SCF_L[TYPE]-Pn) + + # evaporation from snowFreeWater (based on potBareSoilEvap) + self.actSnowFreeWaterEvap = pcr.min(self.snowFreeWater, \ + self.potBareSoilEvap) # ES_a[TYPE] = min(SCF_L[TYPE],ES_p[TYPE]) + + # update snowFreeWater and potBareSoilEvap + self.snowFreeWater = pcr.max(0.0, \ + self.snowFreeWater - self.actSnowFreeWaterEvap) + # SCF_L[TYPE]= SCF_L[TYPE]-ES_a[TYPE] + self.potBareSoilEvap = pcr.max(0, \ + self.potBareSoilEvap - self.actSnowFreeWaterEvap) + # ES_p[TYPE]= max(0,ES_p[TYPE]-ES_a[TYPE]) + + # update actual evaporation (after evaporation from snowFreeWater) + self.actualET += self.actSnowFreeWaterEvap # EACT_L[TYPE]= EACT_L[TYPE]+ES_a[TYPE]; + + if self.debugWaterBalance: + vos.waterBalanceCheck([self.snowfall, self.liquidPrecip], + [self.netLqWaterToSoil,\ + self.actSnowFreeWaterEvap], + prevStates,\ + [self.snowCoverSWE, self.snowFreeWater],\ + 'snow module',\ + True,\ + currTimeStep.fulldate,threshold=1e-4) + vos.waterBalanceCheck([self.snowfall, deltaSnowCover],\ + [pcr.scalar(0.0)],\ + [prevSnowCoverSWE],\ + [self.snowCoverSWE],\ + 'snowCoverSWE',\ + True,\ + currTimeStep.fulldate,threshold=5e-4) + vos.waterBalanceCheck([self.liquidPrecip], + [deltaSnowCover, self.actSnowFreeWaterEvap, self.netLqWaterToSoil], + [prevSnowFreeWater],\ + [self.snowFreeWater],\ + 'snowFreeWater',\ + True,\ + currTimeStep.fulldate,threshold=5e-4) + +#..................................................................................................................................................... + + def getSoilStates(self): + + if self.numberOfLayers == 2: + + # initial total soilWaterStorage + self.soilWaterStorage = pcr.max(0.,\ + self.storUpp + \ + self.storLow ) + + # effective degree of saturation (-) + self.effSatUpp = pcr.max(0., self.storUpp/ self.parameters.storCapUpp) # THEFF1= max(0,S1_L[TYPE]/SC1[TYPE]); + self.effSatLow = pcr.max(0., self.storLow/ self.parameters.storCapLow) # THEFF2= max(0,S2_L[TYPE]/SC2[TYPE]); + self.effSatUpp = pcr.min(1., self.effSatUpp) + self.effSatLow = pcr.min(1., self.effSatLow) + self.effSatUpp = pcr.cover(self.effSatUpp, 1.0) + self.effSatLow = pcr.cover(self.effSatLow, 1.0) + + # matricSuction (m) + self.matricSuctionUpp = self.parameters.airEntryValueUpp*\ + (pcr.max(0.01,self.effSatUpp)**-self.parameters.poreSizeBetaUpp) + self.matricSuctionLow = self.parameters.airEntryValueLow*\ + (pcr.max(0.01,self.effSatLow)**-self.parameters.poreSizeBetaLow) # PSI1= PSI_A1[TYPE]*max(0.01,THEFF1)**-BCH1[TYPE]; + # PSI2= PSI_A2[TYPE]*max(0.01,THEFF2)**-BCH2[TYPE]; + + self.kUnsatUpp = pcr.max(0.,(self.effSatUpp**\ + self.parameters.campbellBetaUpp)*self.parameters.kSatUpp) # original Rens's code: KTHEFF1= max(0,THEFF1**BCB1[TYPE]*KS1[TYPE]) + self.kUnsatLow = pcr.max(0.,(self.effSatLow**\ + self.parameters.campbellBetaLow)*self.parameters.kSatLow) # original Rens's code: KTHEFF2= max(0,THEFF2**BCB2[TYPE]*KS2[TYPE]) + self.kUnsatUpp = pcr.min(self.kUnsatUpp,self.parameters.kSatUpp) + self.kUnsatLow = pcr.min(self.kUnsatLow,self.parameters.kSatLow) + + # kThVert (m.day-1) = unsaturated conductivity capped at field capacity + # - exchange between layers capped at field capacity + self.kThVertUppLow = pcr.min(\ + pcr.sqrt(self.kUnsatUpp*self.kUnsatLow),\ + (self.kUnsatUpp*self.kUnsatLow* \ + self.parameters.kUnsatAtFieldCapUpp*\ + self.parameters.kUnsatAtFieldCapLow)**0.25) + # KTHVERT = min(sqrt(KTHEFF1*KTHEFF2),(KTHEFF1*KTHEFF2*KTHEFF1_FC*KTHEFF2_FC)**0.25) + + # gradient for capillary rise (index indicating target store to its underlying store) + self.gradientUppLow = pcr.max(0.0,\ + (self.matricSuctionUpp-self.matricSuctionLow)*2./\ + (self.parameters.thickUpp+self.parameters.thickLow)-pcr.scalar(1.0)) + self.gradientUppLow = pcr.cover(self.gradientUppLow, 0.0) + # GRAD = max(0,2*(PSI1-PSI2)/(Z1[TYPE]+Z2[TYPE])-1); + + self.readAvlWater = \ + (pcr.max(0.,\ + self.effSatUpp - self.parameters.effSatAtWiltPointUpp))*\ + (self.parameters.satVolMoistContUpp - self.parameters.resVolMoistContUpp )*\ + pcr.min(self.parameters.thickUpp,self.maxRootDepth) + \ + (pcr.max(0.,\ + self.effSatLow - self.parameters.effSatAtWiltPointLow))*\ + (self.parameters.satVolMoistContLow - self.parameters.resVolMoistContLow )*\ + pcr.min(self.parameters.thickLow,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp,0.)) # Edwin modified this line. Edwin uses soil thickness thickUpp & thickLow (instead of storCapUpp & storCapLow). + # And Rens support this. + + if self.numberOfLayers == 3: + # initial total soilWaterStorage + self.soilWaterStorage = pcr.max(0.,\ + self.storUpp000005 + \ + self.storUpp005030 + \ + self.storLow030150 ) + + # effective degree of saturation (-) + self.effSatUpp000005 = pcr.max(0., self.storUpp000005/ self.parameters.storCapUpp000005) + self.effSatUpp005030 = pcr.max(0., self.storUpp005030/ self.parameters.storCapUpp005030) + self.effSatLow030150 = pcr.max(0., self.storLow030150/ self.parameters.storCapLow030150) + self.effSatUpp000005 = pcr.min(1., self.effSatUpp000005) + self.effSatUpp005030 = pcr.min(1., self.effSatUpp005030) + self.effSatLow030150 = pcr.min(1., self.effSatLow030150) + + # matricSuction (m) + self.matricSuctionUpp000005 = self.parameters.airEntryValueUpp000005*(pcr.max(0.01,self.effSatUpp000005)**-self.parameters.poreSizeBetaUpp000005) + self.matricSuctionUpp005030 = self.parameters.airEntryValueUpp005030*(pcr.max(0.01,self.effSatUpp005030)**-self.parameters.poreSizeBetaUpp005030) + self.matricSuctionLow030150 = self.parameters.airEntryValueLow030150*(pcr.max(0.01,self.effSatLow030150)**-self.parameters.poreSizeBetaLow030150) + + # kUnsat (m.day-1): unsaturated hydraulic conductivity + self.kUnsatUpp000005 = pcr.max(0.,(self.effSatUpp000005**self.parameters.campbellBetaUpp000005)*self.parameters.kSatUpp000005) + self.kUnsatUpp005030 = pcr.max(0.,(self.effSatUpp005030**self.parameters.campbellBetaUpp005030)*self.parameters.kSatUpp005030) + self.kUnsatLow030150 = pcr.max(0.,(self.effSatLow030150**self.parameters.campbellBetaLow030150)*self.parameters.kSatLow030150) + + self.kUnsatUpp000005 = pcr.min(self.kUnsatUpp000005,self.parameters.kSatUpp000005) + self.kUnsatUpp005030 = pcr.min(self.kUnsatUpp005030,self.parameters.kSatUpp005030) + self.kUnsatLow030150 = pcr.min(self.kUnsatLow030150,self.parameters.kSatLow030150) + + # kThVert (m.day-1) = unsaturated conductivity capped at field capacity + # - exchange between layers capped at field capacity + # between Upp000005Upp005030 + self.kThVertUpp000005Upp005030 = pcr.min(\ + pcr.sqrt(self.kUnsatUpp000005*self.kUnsatUpp005030),\ + (self.kUnsatUpp000005*self.kUnsatUpp005030* \ + self.parameters.kUnsatAtFieldCapUpp000005*\ + self.parameters.kUnsatAtFieldCapUpp005030)**0.25) + # between Upp005030Low030150 + self.kThVertUpp005030Low030150 = pcr.min(\ + pcr.sqrt(self.kUnsatUpp005030*self.kUnsatLow030150),\ + (self.kUnsatUpp005030*self.kUnsatLow030150* \ + self.parameters.kUnsatAtFieldCapUpp005030*\ + self.parameters.kUnsatAtFieldCapLow030150)**0.25) + + # gradient for capillary rise (index indicating target store to its underlying store) + # between Upp000005Upp005030 + self.gradientUpp000005Upp005030 = pcr.max(0.,2.*\ + (self.matricSuctionUpp000005-self.matricSuctionUpp005030)/\ + (self.parameters.thickUpp000005+ self.parameters.thickUpp005030)-1.) + # between Upp005030Low030150 + self.gradientUpp005030Low030150 = pcr.max(0.,2.*\ + (self.matricSuctionUpp005030-self.matricSuctionLow030150)/\ + (self.parameters.thickUpp005030+ self.parameters.thickLow030150)-1.) + + # readily available water in the root zone (upper soil layers) + self.readAvlWater = \ + (pcr.max(0.,\ + self.effSatUpp000005 - self.parameters.effSatAtWiltPointUpp000005))*\ + (self.parameters.satVolMoistContUpp000005 - self.parameters.resVolMoistContUpp000005 )*\ + pcr.min(self.parameters.thickUpp000005,self.maxRootDepth) + \ + (pcr.max(0.,\ + self.effSatUpp005030 - self.parameters.effSatAtWiltPointUpp005030))*\ + (self.parameters.satVolMoistContUpp005030 - self.parameters.resVolMoistContUpp005030 )*\ + pcr.min(self.parameters.thickUpp005030,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp000005)) + \ + (pcr.max(0.,\ + self.effSatLow030150 - self.parameters.effSatAtWiltPointLow030150))*\ + (self.parameters.satVolMoistContLow030150 - self.parameters.resVolMoistContLow030150 )*\ + pcr.min(self.parameters.thickLow030150,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp005030,0.)) + + # RvB: initialize satAreaFrac + self.satAreaFrac= None + + + def calculateDirectRunoff(self): + # topWaterLater is partitioned into directRunoff (and infiltration) + self.directRunoff = self.improvedArnoScheme(\ + iniWaterStorage = self.soilWaterStorage, \ + inputNetLqWaterToSoil = self.topWaterLayer, \ + directRunoffReductionMethod = self.improvedArnoSchemeMethod) + self.directRunoff = pcr.min(self.topWaterLayer, self.directRunoff) + + # Yet, we minimize directRunoff in the irrigation areas: + if self.name.startswith('irr') and self.includeIrrigation: self.directRunoff = pcr.scalar(0.0) + + # update topWaterLayer (above soil) after directRunoff + self.topWaterLayer = pcr.max(0.0, self.topWaterLayer - self.directRunoff) + + + def improvedArnoScheme(self, iniWaterStorage, inputNetLqWaterToSoil, directRunoffReductionMethod = "Default"): + + # arnoBeta = BCF = b coefficient of soil water storage capacity distribution + # + # WMIN = root zone water storage capacity, minimum values + # WMAX = root zone water storage capacity, area-averaged values + # W = actual water storage in root zone + # WRANGE = WMAX - WMIN + # DW = WMAX-W + # WFRAC = DW/WRANGE ; WFRAC capped at 1 + # WFRACB = DW/WRANGE raised to the power (1/(b+1)) + # SATFRAC = fractional saturated area + # WACT = actual water storage within rootzone + + self.satAreaFracOld = self.satAreaFrac + + Pn = iniWaterStorage + \ + inputNetLqWaterToSoil # Pn = W[TYPE]+Pn; + Pn = Pn - pcr.max(self.rootZoneWaterStorageMin, \ + iniWaterStorage) # Pn = Pn-max(WMIN[TYPE],W[TYPE]); + soilWaterStorage = pcr.ifthenelse(Pn < 0.,\ + self.rootZoneWaterStorageMin+Pn, \ + pcr.max(iniWaterStorage,self.rootZoneWaterStorageMin)) # W[TYPE]= if(Pn<0,WMIN[TYPE]+Pn,max(W[TYPE],WMIN[TYPE])); + Pn = pcr.max(0.,Pn) # Pn = max(0,Pn); + # + DW = pcr.max(0.0,self.parameters.rootZoneWaterStorageCap - \ + soilWaterStorage) # DW = max(0,WMAX[TYPE]-W[TYPE]); + + #~ WFRAC = pcr.min(1.0,DW/self.rootZoneWaterStorageRange) # WFRAC = min(1,DW/WRANGE[TYPE]); + # modified by Edwin ; to solve problems with rootZoneWaterStorageRange = 0.0 + WFRAC = pcr.ifthenelse(self.rootZoneWaterStorageRange > 0.0, pcr.min(1.0,DW/self.rootZoneWaterStorageRange), 1.0) + + self.WFRACB = WFRAC**(1./(1.+self.arnoBeta)) # WFRACB = WFRAC**(1/(1+BCF[TYPE])); + self.satAreaFrac = pcr.ifthenelse(self.WFRACB > 0.,\ + 1.-self.WFRACB**self.arnoBeta,\ + 1.) # SATFRAC_L = if(WFRACB>0,1-WFRACB**BCF[TYPE],1); + # make sure that 0.0 <= satAreaFrac <= 1.0 + self.satAreaFrac = pcr.min(self.satAreaFrac, 1.0) + self.satAreaFrac = pcr.max(self.satAreaFrac, 0.0) + + actualW = (self.arnoBeta+1.0)*self.parameters.rootZoneWaterStorageCap - \ + self.arnoBeta*self.rootZoneWaterStorageMin - \ + (self.arnoBeta+1.0)*self.rootZoneWaterStorageRange*self.WFRACB + # WACT_L = (BCF[TYPE]+1)*WMAX[TYPE]- BCF[TYPE]*WMIN[TYPE]- (BCF[TYPE]+1)*WRANGE[TYPE]*WFRACB; + + directRunoffReduction = pcr.scalar(0.0) # as in the "Original" work of van Beek et al. (2011) + if directRunoffReductionMethod == "Default": + if self.numberOfLayers == 2: directRunoffReduction = pcr.min(self.kUnsatLow,\ + pcr.sqrt(self.kUnsatLow*self.parameters.kUnsatAtFieldCapLow)) + if self.numberOfLayers == 3: directRunoffReduction = pcr.min(self.kUnsatLow030150,\ + pcr.sqrt(self.kUnsatLow030150*self.parameters.kUnsatAtFieldCapLow030150)) + # Rens: # In order to maintain full saturation and + # continuous groundwater recharge/percolation, + # the amount of directRunoff may be reduced. + # In this case, this reduction is estimated + # based on (for two layer case) percLow = pcr.min(KUnSatLow,\ + # pcr.sqrt(self.parameters.KUnSatFC2*KUnSatLow)) + + if directRunoffReductionMethod == "Modified": + if self.numberOfLayers == 2: directRunoffReduction = pcr.min(self.kUnsatLow,\ + pcr.sqrt(self.kUnsatLow*self.parameters.kUnsatAtFieldCapLow)) + if self.numberOfLayers == 3: directRunoffReduction = pcr.min(self.kUnsatLow030150,\ + pcr.sqrt(self.kUnsatLow030150*self.parameters.kUnsatAtFieldCapLow030150)) + # the reduction of directRunoff (preferential flow groundwater) + # is only introduced if the soilWaterStorage near its saturation + # - this is in order to maintain the saturation + saturation_treshold = 0.999 + directRunoffReduction = pcr.ifthenelse(vos.getValDivZero(soilWaterStorage,self.parameters.rootZoneWaterStorageCap) > saturation_treshold, directRunoffReduction, 0.0) + + # directRunoff + condition = (self.arnoBeta+pcr.scalar(1.))*self.rootZoneWaterStorageRange* self.WFRACB + directRunoff = pcr.max(0.0, \ + Pn -\ + (self.parameters.rootZoneWaterStorageCap+directRunoffReduction-soilWaterStorage) + \ + pcr.ifthenelse(Pn >= condition, + pcr.scalar(0.0), \ + self.rootZoneWaterStorageRange*(self.WFRACB-\ + Pn / ((self.arnoBeta+1.)*\ + self.rootZoneWaterStorageRange))**(self.arnoBeta+1.))) + # Q1_L[TYPE]= max(0,Pn-(WMAX[TYPE]+P2_L[TYPE]-W[TYPE])+ + # if(Pn>=(BCF[TYPE]+1)*WRANGE[TYPE]*WFRACB, 0, + # WRANGE[TYPE]*(WFRACB-Pn/((BCF[TYPE]+1)*WRANGE[TYPE]))**(BCF[TYPE]+1))); #* + # make sure that there is always value + directRunoff = pcr.cover(directRunoff, 0.0) + + return directRunoff + + + def calculateOpenWaterEvap(self): + # update topWaterLayer (above soil) + # - with netLqWaterToSoil and irrGrossDemand + self.topWaterLayer += pcr.max(0.,self.netLqWaterToSoil + self.irrGrossDemand) + + # potential evaporation for openWaterEvap + remainingPotETP = self.potBareSoilEvap + self.potTranspiration # Edwin's principle: LIMIT = self.potBareSoilEvap +self.potTranspiration + # remainingPotETP = self.totalPotET # DW, RvB, and YW use self.totalPotETP + + # openWaterEvap is ONLY for evaporation from paddy field areas + self.openWaterEvap = pcr.spatial(pcr.scalar(0.)) + + if self.name == 'irrPaddy' or self.name == "irr_paddy": # only open water evaporation from the paddy field + self.openWaterEvap = \ + pcr.min(\ + pcr.max(0.,self.topWaterLayer), remainingPotETP) + + # update potBareSoilEvap & potTranspiration (after openWaterEvap) + # - CHECK; WHY DO WE USE COVER ABOVE? Edwin replaced them using the following lines: + self.potBareSoilEvap = pcr.cover(\ + pcr.max(0.0, self.potBareSoilEvap -\ + vos.getValDivZero(self.potBareSoilEvap, remainingPotETP)*self.openWaterEvap ), 0.0) + self.potTranspiration = pcr.cover(\ + pcr.max(0.0, self.potTranspiration -\ + vos.getValDivZero(self.potTranspiration, remainingPotETP)*self.openWaterEvap), 0.0) + + # update top water layer after openWaterEvap + self.topWaterLayer = pcr.max(0.,self.topWaterLayer - self.openWaterEvap) + + + def calculateInfiltration(self): + # infiltration, limited with KSat1 and available water in topWaterLayer + if self.numberOfLayers == 2: + self.infiltration = pcr.min(self.topWaterLayer,self.parameters.kSatUpp) # P0_L = min(P0_L,KS1*Duration*timeslice()); + + if self.numberOfLayers == 3: + self.infiltration = pcr.min(self.topWaterLayer,self.parameters.kSatUpp000005) # P0_L = min(P0_L,KS1*Duration*timeslice()); + + # for paddy, infiltration should consider percolation losses + if (self.name == 'irrPaddy' or self.name == "irr_paddy") and self.includeIrrigation: + infiltration_loss = pcr.max(self.design_percolation_loss, + ((1./self.irrigationEfficiencyUsed) - 1.) * self.topWaterLayer) + self.infiltration = pcr.min(infiltration_loss, self.infiltration) + + # update top water layer after infiltration + self.topWaterLayer = pcr.max(0.0,\ + self.topWaterLayer - self.infiltration) + + # release excess topWaterLayer above minTopWaterLayer as additional direct runoff + self.directRunoff += pcr.max(0.0,\ + self.topWaterLayer - self.minTopWaterLayer) + + # update topWaterLayer after additional direct runoff + self.topWaterLayer = pcr.min( self.topWaterLayer , \ + self.minTopWaterLayer) + + + def estimateTranspirationAndBareSoilEvap(self, returnTotalEstimation = False, returnTotalTranspirationOnly = False): + # TRANSPIRATION + # - fractions for distributing transpiration (based on rott fraction and actual layer storages) + # + if self.numberOfLayers == 2: + dividerTranspFracs = pcr.max( 1e-9, self.adjRootFrUpp*self.storUpp +\ + self.adjRootFrLow*self.storLow ) + transpFracUpp = \ + pcr.ifthenelse((self.storUpp + self.storLow) > 0.,\ + self.adjRootFrUpp*self.storUpp/ dividerTranspFracs, \ + self.adjRootFrUpp) + transpFracLow = \ + pcr.ifthenelse((self.storUpp + self.storLow) > 0.,\ + self.adjRootFrLow*self.storLow/ dividerTranspFracs, \ + self.adjRootFrLow) # WF1= if((S1_L[TYPE]+S2_L[TYPE])>0,RFW1[TYPE]*S1_L[TYPE]/ + # max(1e-9,RFW1[TYPE]*S1_L[TYPE]+RFW2[TYPE]*S2_L[TYPE]),RFW1[TYPE]); + # WF2= if((S1_L[TYPE]+S2_L[TYPE])>0,RFW2[TYPE]*S2_L[TYPE]/ + # max(1e-9,RFW1[TYPE]*S1_L[TYPE]+RFW2[TYPE]*S2_L[TYPE]),RFW2[TYPE]); + if self.numberOfLayers == 3: + dividerTranspFracs = pcr.max( 1e-9, self.adjRootFrUpp000005*self.storUpp000005 +\ + self.adjRootFrUpp005030*self.storUpp005030 +\ + self.adjRootFrLow030150*self.storLow030150) + transpFracUpp000005 = \ + pcr.ifthenelse((self.storUpp000005 + \ + self.storUpp005030 + \ + self.storLow030150) > 0.,\ + self.adjRootFrUpp000005*self.storUpp000005/ dividerTranspFracs, \ + self.adjRootFrUpp000005) + transpFracUpp005030 = \ + pcr.ifthenelse((self.storUpp000005 + \ + self.storUpp005030 + \ + self.storLow030150) > 0.,\ + self.adjRootFrUpp005030*self.storUpp005030/ dividerTranspFracs, \ + self.adjRootFrUpp005030) + transpFracLow030150 = \ + pcr.ifthenelse((self.storUpp000005 + \ + self.storUpp005030 + \ + self.storLow030150) > 0.,\ + self.adjRootFrLow030150*self.storLow030150/ dividerTranspFracs, \ + self.adjRootFrLow030150) + + relActTranspiration = pcr.scalar(1.0) # no reduction in case of returnTotalEstimation + if returnTotalEstimation == False: + # reduction factor for transpiration + # + # - relActTranspiration = fraction actual transpiration over potential transpiration + relActTranspiration = (self.parameters.rootZoneWaterStorageCap + \ + self.arnoBeta*self.rootZoneWaterStorageRange*(1.- \ + (1.+self.arnoBeta)/self.arnoBeta*self.WFRACB)) / \ + (self.parameters.rootZoneWaterStorageCap + \ + self.arnoBeta*self.rootZoneWaterStorageRange*(1.- self.WFRACB)) # original Rens's line: + # FRACTA[TYPE] = (WMAX[TYPE]+BCF[TYPE]*WRANGE[TYPE]*(1-(1+BCF[TYPE])/BCF[TYPE]*WFRACB))/ + # (WMAX[TYPE]+BCF[TYPE]*WRANGE[TYPE]*(1-WFRACB)); + relActTranspiration = (1.-self.satAreaFrac) / \ + (1.+(pcr.max(0.01,relActTranspiration)/self.effSatAt50)**\ + (self.effPoreSizeBetaAt50*pcr.scalar(-3.0))) # original Rens's line: + # FRACTA[TYPE] = (1-SATFRAC_L)/(1+(max(0.01,FRACTA[TYPE])/THEFF_50[TYPE])**(-3*BCH_50)); + relActTranspiration = pcr.max(0.0, relActTranspiration) + relActTranspiration = pcr.min(1.0, relActTranspiration) + + # an idea by Edwin - 23 March 2015: no transpiration reduction in irrigated areas + if self.name.startswith('irr') and self.includeIrrigation: relActTranspiration = pcr.scalar(1.0) + + # partitioning potential tranpiration (based on Rens's oldcalc script provided 30 July 2015) + if self.numberOfLayers == 2: + potTranspirationUpp = pcr.min(transpFracUpp*self.potTranspiration, self.potTranspiration) + potTranspirationLow = pcr.max(0.0, self.potTranspiration - potTranspirationUpp) + if self.numberOfLayers == 3: + potTranspirationUpp000005 = pcr.min(transpFracUpp000005*self.potTranspiration, self.potTranspiration) + potTranspirationUpp005030 = pcr.min(transpFracUpp005030*self.potTranspiration, pcr.max(0.0, self.potTranspiration - potTranspirationUpp000005)) + potTranspirationLow030150 = pcr.max(0.0, self.potTranspiration - potTranspirationUpp000005 - potTranspirationUpp005030) + + # estimate actual transpiration fluxes + if self.numberOfLayers == 2: + actTranspiUpp = pcr.cover(relActTranspiration*potTranspirationUpp, 0.0) + actTranspiLow = pcr.cover(relActTranspiration*potTranspirationLow, 0.0) + if self.numberOfLayers == 3: + actTranspiUpp000005 = pcr.cover(relActTranspiration*potTranspirationUpp000005, 0.0) + actTranspiUpp005030 = pcr.cover(relActTranspiration*potTranspirationUpp005030, 0.0) + actTranspiLow030150 = pcr.cover(relActTranspiration*potTranspirationLow030150, 0.0) + + # BARE SOIL EVAPORATION + # actual bare soil evaporation (potential) # no reduction in case of returnTotalEstimation + actBareSoilEvap = self.potBareSoilEvap + if self.numberOfLayers == 2 and returnTotalEstimation == False: + actBareSoilEvap = self.satAreaFrac * pcr.min(\ + self.potBareSoilEvap,self.parameters.kSatUpp) + \ + (1.-self.satAreaFrac)* pcr.min(\ + self.potBareSoilEvap,self.kUnsatUpp) # ES_a[TYPE] = SATFRAC_L *min(ES_p[TYPE],KS1[TYPE]*Duration*timeslice())+ + # (1-SATFRAC_L)*min(ES_p[TYPE],KTHEFF1*Duration*timeslice()); + if self.numberOfLayers == 3 and returnTotalEstimation == False: + actBareSoilEvap = self.satAreaFrac * pcr.min(\ + self.potBareSoilEvap,self.parameters.kSatUpp000005) + \ + (1.-self.satAreaFrac)* pcr.min(\ + self.potBareSoilEvap,self.kUnsatUpp000005) + actBareSoilEvap = pcr.max(0.0, actBareSoilEvap) + actBareSoilEvap = pcr.min(actBareSoilEvap,self.potBareSoilEvap) + actBareSoilEvap = pcr.cover(actBareSoilEvap, 0.0) + + # no bare soil evaporation in the inundated paddy field + if self.name == 'irrPaddy' or self.name == "irr_paddy": + # no bare soil evaporation if topWaterLayer is above treshold + #~ treshold = 0.0005 # unit: m ; + treshold = self.potBareSoilEvap + self.potTranspiration # an idea by Edwin on 23 march 2015 + actBareSoilEvap = pcr.ifthenelse(self.topWaterLayer > treshold, 0.0, actBareSoilEvap) + + # return the calculated variables: + if self.numberOfLayers == 2: + if returnTotalEstimation: + if returnTotalTranspirationOnly: + return actTranspiUpp+ actTranspiLow + else: + return actBareSoilEvap+ actTranspiUpp+ actTranspiLow + else: + return actBareSoilEvap, actTranspiUpp, actTranspiLow + if self.numberOfLayers == 3: + if returnTotalEstimation: + if returnTotalTranspirationOnly: + return actTranspiUpp000005+ actTranspiUpp005030+ actTranspiLow030150 + else: + return actBareSoilEvap+ actTranspiUpp000005+ actTranspiUpp005030+ actTranspiLow030150 + else: + return actBareSoilEvap, actTranspiUpp000005, actTranspiUpp005030, actTranspiLow030150 + + + def estimateSoilFluxes(self,capRiseFrac,groundwater): + # Given states, we estimate all fluxes. + ################################################################ + + if self.numberOfLayers == 2: + # - percolation from storUpp to storLow + self.percUpp = self.kThVertUppLow * 1. + self.percUpp = \ + pcr.ifthenelse( self.effSatUpp > self.parameters.effSatAtFieldCapUpp, \ + pcr.min(pcr.max(0., self.effSatUpp - self.parameters.effSatAtFieldCapUpp)*self.parameters.storCapUpp, self.percUpp), self.percUpp) + \ + pcr.max(0.,self.infiltration - \ + (self.parameters.storCapUpp-self.storUpp)) # original Rens's line: + # P1_L[TYPE] = KTHVERT*Duration*timeslice(); + # P1_L[TYPE] = if(THEFF1 > THEFF1_FC[TYPE],min(max(0,THEFF1-THEFF1_FC[TYPE])*SC1[TYPE], + # P1_L[TYPE]),P1_L[TYPE])+max(0,P0_L[TYPE]-(SC1[TYPE]-S1_L[TYPE])); + # - percolation from storLow to storGroundwater + self.percLow = pcr.min(self.kUnsatLow, pcr.sqrt(\ + self.kUnsatLow*self.parameters.kUnsatAtFieldCapLow)) + # original Rens's line: + # P2_L[TYPE] = min(KTHEFF2,sqrt(KTHEFF2*KTHEFF2_FC[TYPE]))*Duration*timeslice() + + # - capillary rise to storUpp from storLow + self.capRiseUpp = \ + pcr.min(pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp - \ + self.effSatUpp)*self.parameters.storCapUpp,\ + self.kThVertUppLow * self.gradientUppLow) # original Rens's line: + # CR1_L[TYPE] = min(max(0,THEFF1_FC[TYPE]-THEFF1)*SC1[TYPE],KTHVERT*GRAD*Duration*timeslice()); + + # - capillary rise to storLow from storGroundwater (m) + self.capRiseLow = 0.5*(self.satAreaFrac + capRiseFrac)*\ + pcr.min((1.-self.effSatLow)*\ + pcr.sqrt(self.parameters.kSatLow* \ + self.kUnsatLow),\ + pcr.max(0.0,self.parameters.effSatAtFieldCapLow- \ + self.effSatLow)*\ + self.parameters.storCapLow) # original Rens's line: + # CR2_L[TYPE] = 0.5*(SATFRAC_L+CRFRAC)*min((1-THEFF2)*sqrt(KS2[TYPE]*KTHEFF2)*Duration*timeslice(), + # max(0,THEFF2_FC[TYPE]-THEFF2)*SC2[TYPE]); + + # - no capillary rise from non productive aquifer + self.capRiseLow = pcr.ifthenelse(groundwater.productive_aquifer,\ + self.capRiseLow, 0.0) + + # - interflow (m) + percToInterflow = self.parameters.percolationImp*(\ + self.percUpp+self.capRiseLow-\ + (self.percLow+self.capRiseUpp)) + self.interflow = pcr.max(\ + self.parameters.interflowConcTime*percToInterflow +\ + (pcr.scalar(1.)-self.parameters.interflowConcTime)*self.interflow, 0.0) + + if self.numberOfLayers == 3: + # - percolation from storUpp000005 to storUpp005030 (m) + self.percUpp000005 = self.kThVertUpp000005Upp005030 * 1. + self.percUpp000005 = \ + pcr.ifthenelse( self.effSatUpp000005 > self.parameters.effSatAtFieldCapUpp000005, \ + pcr.min(pcr.max(0., self.effSatUpp000005 - self.parameters.effSatAtFieldCapUpp000005)*self.parameters.storCapUpp000005, self.percUpp000005), self.percUpp000005) + \ + pcr.max(0.,self.infiltration - \ + (self.parameters.storCapUpp000005-self.storUpp000005)) + + # - percolation from storUpp005030 to storLow030150 (m) + self.percUpp005030 = self.kThVertUpp005030Low030150 * 1. + self.percUpp005030 = \ + pcr.ifthenelse( self.effSatUpp005030 > self.parameters.effSatAtFieldCapUpp005030, \ + pcr.min(pcr.max(0., self.effSatUpp005030 - self.parameters.effSatAtFieldCapUpp005030)*self.parameters.storCapUpp005030, self.percUpp005030), self.percUpp005030) + \ + pcr.max(0.,self.percUpp000005 - \ + (self.parameters.storCapUpp005030-self.storUpp005030)) + + # - percolation from storLow030150 to storGroundwater (m) + self.percLow030150 = pcr.min(self.kUnsatLow030150,pcr.sqrt(\ + self.parameters.kUnsatAtFieldCapLow030150*\ + self.kUnsatLow030150)) + + # - capillary rise to storUpp000005 from storUpp005030 (m) + self.capRiseUpp000005 = pcr.min(pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp000005 - \ + self.effSatUpp000005)* \ + self.parameters.storCapUpp000005, \ + self.kThVertUpp000005Upp005030* \ + self.gradientUpp000005Upp005030) + + # - capillary rise to storUpp005030 from storLow030150 (m) + self.capRiseUpp005030 = pcr.min(pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp005030 - \ + self.effSatUpp005030)* \ + self.parameters.storCapUpp005030, \ + self.kThVertUpp005030Low030150* \ + self.gradientUpp005030Low030150) + + # - capillary rise to storLow030150 from storGroundwater (m) + self.capRiseLow030150 = 0.5*(self.satAreaFrac + capRiseFrac)*\ + pcr.min((1.-self.effSatLow030150)*\ + pcr.sqrt(self.parameters.kSatLow030150* \ + self.kUnsatLow030150),\ + pcr.max(0.0,self.parameters.effSatAtFieldCapLow030150- \ + self.effSatLow030150)*\ + self.parameters.storCapLow030150) + + # - no capillary rise from non productive aquifer + self.capRiseLow030150 = pcr.ifthenelse(groundwater.productive_aquifer,\ + self.capRiseLow030150, 0.0) + + # - interflow (m) + percToInterflow = self.parameters.percolationImp*(\ + self.percUpp005030+self.capRiseLow030150-\ + (self.percLow030150+self.capRiseUpp005030)) + self.interflow = pcr.max(\ + self.parameters.interflowConcTime*percToInterflow +\ + (pcr.scalar(1.)-self.parameters.interflowConcTime)*self.interflow, 0.0) + + + def scaleAllFluxes(self, groundwater): + # We re-scale all fluxes (based on available water). + ######################################################################################################################################## + + if self.numberOfLayers == 2: + # scale fluxes (for Upp) + ADJUST = self.actBareSoilEvap + self.actTranspiUpp + self.percUpp + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp + \ + self.infiltration) / ADJUST),0.) + ADJUST = pcr.cover(ADJUST, 0.0) + self.actBareSoilEvap = ADJUST*self.actBareSoilEvap + self.percUpp = ADJUST*self.percUpp + self.actTranspiUpp = ADJUST*self.actTranspiUpp + # original Rens's line: + # ADJUST = ES_a[TYPE]+T_a1[TYPE]+P1_L[TYPE]; + # ADJUST = if(ADJUST>0,min(1,(max(0,S1_L[TYPE]+P0_L[TYPE]))/ADJUST),0); + # ES_a[TYPE] = ADJUST*ES_a[TYPE]; + # T_a1[TYPE] = ADJUST*T_a1[TYPE]; + # P1_L[TYPE] = ADJUST*P1_L[TYPE]; + + # scale fluxes (for Low) + ADJUST = self.actTranspiLow + self.percLow + self.interflow + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow + \ + self.percUpp)/ADJUST),0.) + ADJUST = pcr.cover(ADJUST, 0.0) + self.percLow = ADJUST*self.percLow + self.actTranspiLow = ADJUST*self.actTranspiLow + self.interflow = ADJUST*self.interflow + # original Rens's line: + # ADJUST = T_a2[TYPE]+P2_L[TYPE]+Q2_L[TYPE]; + # ADJUST = if(ADJUST>0,min(1,max(S2_L[TYPE]+P1_L[TYPE],0)/ADJUST),0); + # T_a2[TYPE] = ADJUST*T_a2[TYPE]; + # P2_L[TYPE] = ADJUST*P2_L[TYPE]; + # Q2_L[TYPE] = ADJUST*Q2_L[TYPE]; + + # capillary rise to storLow is limited to available storGroundwater + # and also limited with reducedCapRise + self.capRiseLow = pcr.max(0.,\ + pcr.min(\ + pcr.max(0.,\ + groundwater.storGroundwater-self.reducedCapRise),self.capRiseLow)) + + # capillary rise to storUpp is limited to available storLow + estimateStorLowBeforeCapRise = pcr.max(0,self.storLow + self.percUpp - \ + (self.actTranspiLow + self.percLow + self.interflow )) + self.capRiseUpp = pcr.min(\ + estimateStorLowBeforeCapRise,self.capRiseUpp) # original Rens's line: + # CR1_L[TYPE] = min(max(0,S2_L[TYPE]+P1_L[TYPE]-(T_a2[TYPE]+P2_L[TYPE]+Q2_L[TYPE])),CR1_L[TYPE]) + + if self.numberOfLayers == 3: + # scale fluxes (for Upp000005) + ADJUST = self.actBareSoilEvap + self.actTranspiUpp000005 + self.percUpp000005 + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp000005 + \ + self.infiltration) / ADJUST),0.) + self.actBareSoilEvap = ADJUST*self.actBareSoilEvap + self.percUpp000005 = ADJUST*self.percUpp000005 + self.actTranspiUpp000005 = ADJUST*self.actTranspiUpp000005 + + # scale fluxes (for Upp005030) + ADJUST = self.actTranspiUpp005030 + self.percUpp005030 + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp005030 + \ + self.percUpp000005)/ ADJUST),0.) + self.percUpp005030 = ADJUST*self.percUpp005030 + self.actTranspiUpp005030 = ADJUST*self.actTranspiUpp005030 + + # scale fluxes (for Low030150) + ADJUST = self.actTranspiLow030150 + self.percLow030150 + self.interflow + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow030150 + \ + self.percUpp005030)/ADJUST),0.) + self.percLow030150 = ADJUST*self.percLow030150 + self.actTranspiLow030150 = ADJUST*self.actTranspiLow030150 + self.interflow = ADJUST*self.interflow + + # capillary rise to storLow is limited to available storGroundwater + # and also limited with reducedCapRise + # + self.capRiseLow030150 = pcr.max(0.,\ + pcr.min(\ + pcr.max(0.,\ + groundwater.storGroundwater-\ + self.reducedCapRise),\ + self.capRiseLow030150)) + + # capillary rise to storUpp005030 is limited to available storLow030150 + # + estimateStorLow030150BeforeCapRise = pcr.max(0,self.storLow030150 + self.percUpp005030 - \ + (self.actTranspiLow030150 + self.percLow030150 + self.interflow )) + self.capRiseUpp005030 = pcr.min(\ + estimateStorLow030150BeforeCapRise,self.capRiseUpp005030) + + # capillary rise to storUpp000005 is limited to available storUpp005030 + # + estimateStorUpp005030BeforeCapRise = pcr.max(0,self.storUpp005030 + self.percUpp000005 - \ + (self.actTranspiUpp005030 + self.percUpp005030)) + self.capRiseUpp000005 = pcr.min(\ + estimateStorUpp005030BeforeCapRise,self.capRiseUpp000005) + + + def scaleAllFluxesForIrrigatedAreas(self, groundwater): + # for irrigation areas: interflow will be minimized + if self.name.startswith('irr'): self.interflow = 0. + + # deep percolation should consider irrigation application losses (idea on 16 June 2015) + if self.name.startswith('irr'): + + startingKC = 0.20 # starting crop coefficient indicate the growing season + + if self.numberOfLayers == 2: + deep_percolation_loss = self.percLow + deep_percolation_loss = pcr.max(deep_percolation_loss, \ + pcr.max(0.0, self.storLow) * ((1./self.irrigationEfficiencyUsed) - 1.)) + self.percLow = pcr.ifthenelse(self.cropKC > startingKC, deep_percolation_loss, self.percLow) + + if self.numberOfLayers == 3: + deep_percolation_loss = self.percLow030150 + deep_percolation_loss = pcr.max(deep_percolation_loss, \ + pcr.max(0.0, self.storLow030150) * ((1./self.irrigationEfficiencyUsed) - 1.)) + self.percLow030150 = pcr.ifthenelse(self.cropKC > startingKC, deep_percolation_loss, self.percLow030150) + + # scale all fluxes based on available water (alternative 1) + self.scaleAllFluxes(groundwater) + + def updateSoilStates(self): + # We give new states and make sure that no storage capacities will be exceeded. + ################################################################################# + + if self.numberOfLayers == 2: + + # update storLow after the following fluxes: + # + percUpp + # + capRiseLow + # - percLow + # - interflow + # - actTranspiLow + # - capRiseUpp + # + self.storLow = pcr.max(0., self.storLow + \ + self.percUpp + \ + self.capRiseLow - \ + (self.percLow + self.interflow + \ + self.actTranspiLow +\ + self.capRiseUpp)) # S2_L[TYPE]= max(0,S2_L[TYPE]+P1_L[TYPE]+CR2_L[TYPE]- + # (P2_L[TYPE]+Q2_L[TYPE]+CR1_L[TYPE]+T_a2[TYPE])); + + # If necessary, reduce percolation input: + percUpp = self.percUpp + + if self.allowNegativePercolation: + # this is as defined in the original oldcalc script of Rens + self.percUpp = percUpp - \ + pcr.max(0.,self.storLow - \ + self.parameters.storCapLow) + # Rens's line: P1_L[TYPE] = P1_L[TYPE]-max(0,S2_L[TYPE]-SC2[TYPE]); + # PS: In the original Rens's code, P1 can be negative. + else: + # alternative, proposed by Edwin: avoid negative percolation + self.percUpp = pcr.max(0., percUpp - \ + pcr.max(0.,self.storLow - \ + self.parameters.storCapLow)) + self.storLow = self.storLow - percUpp + \ + self.percUpp + # If necessary, reduce capRise input: + capRiseLow = self.capRiseLow + self.capRiseLow = pcr.max(0.,capRiseLow - \ + pcr.max(0.,self.storLow - \ + self.parameters.storCapLow)) + self.storLow = self.storLow - capRiseLow + \ + self.capRiseLow + # If necessary, increase interflow outflow: + addInterflow = pcr.max(0.,\ + self.storLow - self.parameters.storCapLow) + self.interflow += addInterflow + self.storLow -= addInterflow + # + self.storLow = pcr.min(self.storLow, self.parameters.storCapLow) + + # + # update storUpp after the following fluxes: + # + infiltration + # + capRiseUpp + # - percUpp + # - actTranspiUpp + # - actBareSoilEvap + # + self.storUpp = pcr.max(0.,self.storUpp + \ + self.infiltration + \ + self.capRiseUpp - \ + (self.percUpp + \ + self.actTranspiUpp + self.actBareSoilEvap)) # Rens's line: S1_L[TYPE]= max(0,S1_L[TYPE]+P0_L[TYPE]+CR1_L[TYPE]- + # (P1_L[TYPE]+T_a1[TYPE]+ES_a[TYPE])); #* + # + # any excess above storCapUpp is handed to topWaterLayer + self.satExcess = pcr.max(0.,self.storUpp - \ + self.parameters.storCapUpp) + self.topWaterLayer = self.topWaterLayer + self.satExcess + + # any excess above minTopWaterLayer is released as directRunoff + self.directRunoff = self.directRunoff + \ + pcr.max(0.,self.topWaterLayer - self.minTopWaterLayer) + + # make sure that storage capacities are not exceeded + self.topWaterLayer = pcr.min( self.topWaterLayer , \ + self.minTopWaterLayer) + self.storUpp = pcr.min(self.storUpp,\ + self.parameters.storCapUpp) + self.storLow = pcr.min(self.storLow,\ + self.parameters.storCapLow) + + # total actual evaporation + transpiration + self.actualET += self.actBareSoilEvap + \ + self.openWaterEvap + \ + self.actTranspiUpp + \ + self.actTranspiLow + + # total actual transpiration + self.actTranspiTotal = self.actTranspiUpp + \ + self.actTranspiLow + + # net percolation between upperSoilStores (positive indicating downward direction) + self.netPercUpp = self.percUpp - self.capRiseUpp + + # groundwater recharge (positive indicating downward direction) + self.gwRecharge = self.percLow - self.capRiseLow + + # the following variables introduced for the comparison with threeLayer model output + self.storUppTotal = self.storUpp + self.storLowTotal = self.storLow + self.actTranspiUppTotal = self.actTranspiUpp + self.actTranspiLowTotal = self.actTranspiLow + self.interflowTotal = self.interflow + + if self.numberOfLayers == 3: + + # update storLow030150 after the following fluxes: + # + percUpp005030 + # + capRiseLow030150 + # - percLow030150 + # - interflow + # - actTranspiLow030150 + # - capRiseUpp005030 + # + self.storLow030150 = pcr.max(0., self.storLow030150 + \ + self.percUpp005030 + \ + self.capRiseLow030150 - \ + (self.percLow030150 + self.interflow + \ + self.actTranspiLow030150 +\ + self.capRiseUpp005030)) + # + # If necessary, reduce percolation input: + percUpp005030 = self.percUpp005030 + self.percUpp005030 = pcr.max(0., percUpp005030 - \ + pcr.max(0.,self.storLow030150 - \ + self.parameters.storCapLow030150)) + self.storLow030150 = self.storLow030150 - \ + percUpp005030 + \ + self.percUpp005030 + # + # If necessary, reduce capRise input: + capRiseLow030150 = self.capRiseLow030150 + self.capRiseLow030150 = pcr.max(0.,capRiseLow030150 - \ + pcr.max(0.,self.storLow030150 - \ + self.parameters.storCapLow030150)) + self.storLow030150 = self.storLow030150 - \ + capRiseLow030150 + \ + self.capRiseLow030150 + # + # If necessary, increase interflow outflow: + addInterflow = pcr.max(0.,\ + self.storLow030150 - self.parameters.storCapLow030150) + self.interflow += addInterflow + self.storLow030150 -= addInterflow + + self.storLow030150 = pcr.min(self.storLow030150,\ + self.parameters.storCapLow030150) + + # update storUpp005030 after the following fluxes: + # + percUpp000005 + # + capRiseUpp005030 + # - percUpp005030 + # - actTranspiUpp005030 + # - capRiseUpp000005 + # + self.storUpp005030 = pcr.max(0., self.storUpp005030 + \ + self.percUpp000005 + \ + self.capRiseUpp005030 - \ + (self.percUpp005030 + \ + self.actTranspiUpp005030 + \ + self.capRiseUpp000005)) + # + # If necessary, reduce percolation input: + percUpp000005 = self.percUpp000005 + self.percUpp000005 = pcr.max(0., percUpp000005 - \ + pcr.max(0.,self.storUpp005030 - \ + self.parameters.storCapUpp005030)) + self.storUpp005030 = self.storUpp005030 - \ + percUpp000005 + \ + self.percUpp000005 + # + # If necessary, reduce capRise input: + capRiseUpp005030 = self.capRiseUpp005030 + self.capRiseUpp005030 = pcr.max(0.,capRiseUpp005030 - \ + pcr.max(0.,self.storUpp005030 - \ + self.parameters.storCapUpp005030)) + self.storUpp005030 = self.storUpp005030 - \ + capRiseUpp005030 + \ + self.capRiseUpp005030 + # + # If necessary, introduce interflow outflow: + self.interflowUpp005030 = pcr.max(0.,\ + self.storUpp005030 - self.parameters.storCapUpp005030) + self.storUpp005030 = self.storUpp005030 - \ + self.interflowUpp005030 + + # update storUpp000005 after the following fluxes: + # + infiltration + # + capRiseUpp000005 + # - percUpp000005 + # - actTranspiUpp000005 + # - actBareSoilEvap + # + self.storUpp000005 = pcr.max(0.,self.storUpp000005 + \ + self.infiltration + \ + self.capRiseUpp000005 - \ + (self.percUpp000005 + \ + self.actTranspiUpp000005 + \ + self.actBareSoilEvap)) + # + # any excess above storCapUpp is handed to topWaterLayer + self.satExcess = pcr.max(0.,self.storUpp000005 - \ + self.parameters.storCapUpp000005) + self.topWaterLayer = self.topWaterLayer + self.satExcess + + # any excess above minTopWaterLayer is released as directRunoff + self.directRunoff = self.directRunoff + \ + pcr.max(0.,self.topWaterLayer - \ + self.minTopWaterLayer) + + # make sure that storage capacities are not exceeded + self.topWaterLayer = pcr.min( self.topWaterLayer , \ + self.minTopWaterLayer) + self.storUpp000005 = pcr.min(self.storUpp000005,\ + self.parameters.storCapUpp000005) + self.storUpp005030 = pcr.min(self.storUpp005030,\ + self.parameters.storCapUpp005030) + self.storLow030150 = pcr.min(self.storLow030150,\ + self.parameters.storCapLow030150) + + # total actual evaporation + transpiration + self.actualET += self.actBareSoilEvap + \ + self.openWaterEvap + \ + self.actTranspiUpp000005 + \ + self.actTranspiUpp005030 + \ + self.actTranspiLow030150 + + # total actual transpiration + self.actTranspiUppTotal = self.actTranspiUpp000005 + \ + self.actTranspiUpp005030 + + # total actual transpiration + self.actTranspiTotal = self.actTranspiUppTotal + \ + self.actTranspiLow030150 + + # net percolation between upperSoilStores (positive indicating downward direction) + self.netPercUpp000005 = self.percUpp000005 - self.capRiseUpp000005 + self.netPercUpp005030 = self.percUpp005030 - self.capRiseUpp005030 + + # groundwater recharge + self.gwRecharge = self.percLow030150 - self.capRiseLow030150 + + # the following variables introduced for the comparison with twoLayer model output + self.storUppTotal = self.storUpp000005 + self.storUpp005030 + self.storLowTotal = self.storLow030150 + self.actTranspiUppTotal = self.actTranspiUpp000005 + self.actTranspiUpp005030 + self.actTranspiLowTotal = self.actTranspiLow030150 + self.interflowTotal = self.interflow + self.interflowUpp005030 + + # variables / states that are defined the twoLayer and threeLayer model: + ######################################################################## + + # landSurfaceRunoff (needed for routing) + self.landSurfaceRunoff = self.directRunoff + self.interflowTotal + + def upperSoilUpdate(self,meteo,groundwater,routing, + capRiseFrac, + nonIrrGrossDemandDict, + swAbstractionFractionDict, + currTimeStep, + allocSegments, + desalinationWaterUse, + groundwater_pumping_region_ids, + regionalAnnualGroundwaterAbstractionLimit): + + if self.debugWaterBalance: + netLqWaterToSoil = self.netLqWaterToSoil # input + preTopWaterLayer = self.topWaterLayer + if self.numberOfLayers == 2: + preStorUpp = self.storUpp + preStorLow = self.storLow + if self.numberOfLayers == 3: + preStorUpp000005 = self.storUpp000005 + preStorUpp005030 = self.storUpp005030 + preStorLow030150 = self.storLow030150 + + # given soil storages, we can calculate several derived states, such as + # effective degree of saturation, unsaturated hydraulic conductivity, and + # readily available water within the root zone. + self.getSoilStates() + + # calculate water demand (including partitioning to different source) + # self.calculateWaterDemand(nonIrrGrossDemandDict, + # swAbstractionFractionDict, + # groundwater, routing, + # allocSegments, currTimeStep, + # desalinationWaterUse, + # groundwater_pumping_region_ids, + # regionalAnnualGroundwaterAbstractionLimit) + + water_use = wu.WaterUse(self, + nonIrrGrossDemandDict, + swAbstractionFractionDict, + groundwater, + routing, + allocSegments, + currTimeStep, + desalinationWaterUse, + groundwater_pumping_region_ids, + regionalAnnualGroundwaterAbstractionLimit) + + self.totalPotentialGrossDemand = water_use.totalPotentialGrossDemand + self.nonIrrGrossDemand = water_use.nonIrrGrossDemand + self.irrGrossDemand = water_use.irrGrossDemand + self.irrGrossDemandPaddy = water_use.irrGrossDemandPaddy + self.irrGrossDemandNonPaddy = water_use.irrGrossDemandNonPaddy + self.desalinationAbstraction = water_use.desalinationAbstraction + self.desalinationAllocation = water_use.desalinationAllocation + self.actSurfaceWaterAbstract = water_use.actSurfaceWaterAbstract + self.allocSurfaceWaterAbstract = water_use.allocSurfaceWaterAbstract + self.nonFossilGroundwaterAbs = water_use.nonFossilGroundwaterAbs + self.allocNonFossilGroundwater = water_use.allocNonFossilGroundwater + self.fossilGroundwaterAbstr = water_use.fossilGroundwaterAbstr + self.fossilGroundwaterAlloc = water_use.fossilGroundwaterAlloc + self.totalGroundwaterAbstraction = water_use.totalGroundwaterAbstraction + self.totalGroundwaterAllocation = water_use.totalGroundwaterAllocation + + self.totalPotentialMaximumGrossDemand = water_use.totalPotentialMaximumGrossDemand + self.totalPotentialMaximumNonIrrGrossDemand = water_use.totalPotentialMaximumNonIrrGrossDemand + self.totalPotentialMaximumIrrGrossDemand = water_use.totalPotentialMaximumIrrGrossDemand + self.totalPotentialMaximumIrrGrossDemandPaddy = water_use.totalPotentialMaximumIrrGrossDemandPaddy + self.totalPotentialMaximumIrrGrossDemandNonPaddy = water_use.totalPotentialMaximumIrrGrossDemandNonPaddy + + self.domesticWaterWithdrawal = water_use.domesticWaterWithdrawal + self.industryWaterWithdrawal = water_use.industryWaterWithdrawal + self.livestockWaterWithdrawal = water_use.livestockWaterWithdrawal + + self.nonIrrReturnFlow = water_use.nonIrrReturnFlow + self.reducedCapRise = water_use.reducedCapRise + self.irrigationEfficiencyUsed = water_use.irrigationEfficiencyUsed + + # calculate openWaterEvap: open water evaporation from the paddy field, + # and update topWaterLayer after openWaterEvap. + self.calculateOpenWaterEvap() + + # calculate directRunoff and infiltration, based on the improved Arno scheme (Hageman and Gates, 2003): + # and update topWaterLayer (after directRunoff and infiltration). + self.calculateDirectRunoff() + self.calculateInfiltration() + + # estimate bare soil evaporation and transpiration: + if self.numberOfLayers == 2: + self.actBareSoilEvap, self.actTranspiUpp, self.actTranspiLow = \ + self.estimateTranspirationAndBareSoilEvap() + if self.numberOfLayers == 3: + self.actBareSoilEvap, self.actTranspiUpp000005, self.actTranspiUpp005030, self.actTranspiLow030150 = \ + self.estimateTranspirationAndBareSoilEvap() + + # estimate percolation and capillary rise, as well as interflow + self.estimateSoilFluxes(capRiseFrac,groundwater) + + # all fluxes are limited to available (source) storage + if self.name.startswith('irr') and self.includeIrrigation: + self.scaleAllFluxesForIrrigatedAreas(groundwater) + #~ self.scaleAllFluxes(groundwater) + else: + self.scaleAllFluxes(groundwater) + + # update all soil states (including get final/corrected fluxes) + self.updateSoilStates() + + # reporting irrigation transpiration deficit + self.irrigationTranspirationDeficit = 0.0 + if self.name.startswith('irr'): self.irrigationTranspirationDeficit = pcr.max(0.0, self.potTranspiration - self.actTranspiTotal) + + if self.debugWaterBalance: + # + vos.waterBalanceCheck([netLqWaterToSoil ,\ + self.irrGrossDemand ,\ + self.satExcess ],\ + [self.directRunoff ,\ + self.openWaterEvap ,\ + self.infiltration] ,\ + [ preTopWaterLayer ],\ + [self.topWaterLayer ] ,\ + 'topWaterLayer',True,\ + currTimeStep.fulldate,threshold=1e-4) + + if self.numberOfLayers == 2: + # + vos.waterBalanceCheck([self.infiltration, + self.capRiseUpp],\ + [self.actTranspiUpp, + self.percUpp, + self.actBareSoilEvap, + self.satExcess],\ + [ preStorUpp],\ + [self.storUpp],\ + 'storUpp',\ + True,\ + currTimeStep.fulldate,threshold=1e-5) + # + vos.waterBalanceCheck([self.percUpp],\ + [self.actTranspiLow, + self.gwRecharge, + self.interflow, + self.capRiseUpp],\ + [ preStorLow],\ + [self.storLow],\ + 'storLow',\ + True,\ + currTimeStep.fulldate,threshold=1e-5) + # + vos.waterBalanceCheck([self.infiltration,\ + self.capRiseLow],\ + [self.satExcess, + self.interflow, + self.percLow, + self.actTranspiUpp, + self.actTranspiLow, + self.actBareSoilEvap],\ + [ preStorUpp, + preStorLow],\ + [self.storUpp, + self.storLow],\ + 'entireSoilLayers',\ + True,\ + currTimeStep.fulldate,threshold=1e-4) + # + vos.waterBalanceCheck([netLqWaterToSoil, + self.capRiseLow, + self.irrGrossDemand],\ + [self.directRunoff, + self.interflow, + self.percLow, + self.actTranspiUpp, + self.actTranspiLow, + self.actBareSoilEvap, + self.openWaterEvap],\ + [ preTopWaterLayer, + preStorUpp, + preStorLow],\ + [self.topWaterLayer, + self.storUpp, + self.storLow],\ + 'allLayers',\ + True,\ + currTimeStep.fulldate,threshold=5e-4) + + if self.numberOfLayers == 3: + vos.waterBalanceCheck([self.infiltration, + self.capRiseUpp000005],\ + [self.actTranspiUpp000005, + self.percUpp000005, + self.actBareSoilEvap, + self.satExcess],\ + [ preStorUpp000005],\ + [self.storUpp000005],\ + 'storUpp000005',True,\ + currTimeStep.fulldate,threshold=1e-5) + + # + vos.waterBalanceCheck([self.percUpp000005, + self.capRiseUpp005030],\ + [self.actTranspiUpp005030, + self.percUpp005030, + self.interflowUpp005030, + self.capRiseUpp000005],\ + [ preStorUpp005030],\ + [self.storUpp005030],\ + 'storUpp005030',True,\ + currTimeStep.fulldate,threshold=1e-5) + # + vos.waterBalanceCheck([self.percUpp005030],\ + [self.actTranspiLow030150, + self.gwRecharge, + self.interflow, + self.capRiseUpp005030],\ + [ preStorLow030150],\ + [self.storLow030150],\ + 'storLow030150',True,\ + currTimeStep.fulldate,threshold=1e-5) + # + vos.waterBalanceCheck([self.infiltration,\ + self.capRiseLow030150],\ + [self.satExcess, + self.interflow, + self.interflowUpp005030, + self.percLow030150, + self.actTranspiUpp000005, + self.actTranspiUpp005030, + self.actTranspiLow030150, + self.actBareSoilEvap],\ + [ preStorUpp000005, + preStorUpp005030, + preStorLow030150],\ + [self.storUpp000005, + self.storUpp005030, + self.storLow030150],\ + 'entireSoilLayers',True,\ + currTimeStep.fulldate,threshold=1e-4) + # + vos.waterBalanceCheck([netLqWaterToSoil, + self.capRiseLow030150, + self.irrGrossDemand],\ + [self.directRunoff, + self.interflow, + self.interflowUpp005030, + self.percLow030150, + self.actTranspiUpp000005, + self.actTranspiUpp005030, + self.actTranspiLow030150, + self.actBareSoilEvap, + self.openWaterEvap],\ + [ preTopWaterLayer, + preStorUpp000005, + preStorUpp005030, + preStorLow030150],\ + [self.topWaterLayer, + self.storUpp000005, + self.storUpp005030, + self.storLow030150],\ + 'allLayers',True,\ + currTimeStep.fulldate,threshold=1e-4) diff --git a/model/landCoverOrig.py b/model/landCoverOrig.py new file mode 100644 index 000000000..fa9491647 --- /dev/null +++ b/model/landCoverOrig.py @@ -0,0 +1,3875 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model +# +# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, +# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, +# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, +# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens +# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import re +import types + +import netCDF4 as nc +import pcraster as pcr + +import logging +logger = logging.getLogger(__name__) + +import virtualOS as vos +from ncConverter import * + +class LandCover(object): + + def __init__(self,iniItems,nameOfSectionInIniFile,soil_and_topo_parameters,landmask,irrigationEfficiency,usingAllocSegments = False): + object.__init__(self) + + self.cloneMap = iniItems.cloneMap + self.tmpDir = iniItems.tmpDir + self.inputDir = iniItems.globalOptions['inputDir'] + self.landmask = landmask + + # number of soil layers: + self.numberOfSoilLayers = int(iniItems.landSurfaceOptions['numberOfUpperSoilLayers']) + + # soil and topo parameters + self.parameters = soil_and_topo_parameters + + # configuration for a certain land cover type + self.iniItemsLC = iniItems.__getattribute__(nameOfSectionInIniFile) + self.name = self.iniItemsLC['name'] + + # limitAbstraction + self.limitAbstraction = False + if iniItems.landSurfaceOptions['limitAbstraction'] == "True": self.limitAbstraction = True + + # if using MODFLOW, limitAbstraction must be True (the abstraction cannot exceed storGroundwater) + if "useMODFLOW" in list(iniItems.groundwaterOptions.keys()): + if iniItems.groundwaterOptions["useMODFLOW"] == "True": self.limitAbstraction = True + + # includeIrrigation + self.includeIrrigation = False + if iniItems.landSurfaceOptions['includeIrrigation'] == "True": self.includeIrrigation = True + + # irrigation efficiency map (dimensionless) + self.irrigationEfficiency = irrigationEfficiency + + # interception module type + # - "Original" is principally the same as defined in van Beek et al., 2014 (default) + # - "Modified" is with a modification by Edwin Sutanudjaja: extending interception definition, using totalPotET for the available energy + self.interceptionModuleType = "Original" + if "interceptionModuleType" in list(self.iniItemsLC.keys()): + if self.iniItemsLC['interceptionModuleType'] == "Modified": + msg = 'Using the "Modified" version of the interception module (i.e. extending interception definition, using totalPotET for the available energy for the interception process).' + logger.info(msg) + self.interceptionModuleType = "Modified" + else: + if self.iniItemsLC['interceptionModuleType'] != "Original": + msg = 'The interceptionModuleType '+self.iniItemsLC['interceptionModuleType']+' is NOT known.' + logger.info(msg) + msg = 'The "Original" interceptionModuleType is used.' + logger.info(msg) + + # minimum interception capacity (only used if interceptionModuleType == "Modified", extended interception definition) + self.minInterceptCap = 0.0 + if self.interceptionModuleType == "Original" and "minInterceptCap" in list(self.iniItemsLC.keys()): + msg = 'As the "Original" interceptionModuleType is used, the "minInterceptCap" value is ignored. The interception scope is only "canopy".' + logger.warning(msg) + if self.interceptionModuleType == "Modified": + self.minInterceptCap = vos.readPCRmapClone(self.iniItemsLC['minInterceptCap'], self.cloneMap, + self.tmpDir, self.inputDir) + + # option to assume surface water as the first priority/alternative for water source (not used) + self.surfaceWaterPiority = False + + # option to activate water balance check + self.debugWaterBalance = True + if self.iniItemsLC['debugWaterBalance'] == "False": self.debugWaterBalance = False + + # Improved Arno Scheme's method: + # - In the "Original" work of van Beek et al., 2011 there is no "directRunoff reduction" + # - However, later (20 April 2011), Rens van Beek introduce this reduction, particularly to maintain soil saturation. This is currently the "Default" method. + self.improvedArnoSchemeMethod = "Default" + if "improvedArnoSchemeMethod" in list(iniItems.landSurfaceOptions.keys()): + self.improvedArnoSchemeMethod = iniItems.landSurfaceOptions['improvedArnoSchemeMethod'] + if self.improvedArnoSchemeMethod == "Original": logger.warning("Using the old/original approach of Improved Arno Scheme. No reduction for directRunoff.") + + # In the original oldcalc script of Rens (2 layer model), the percolation percUpp (P1) can be negative + # - To avoid this, Edwin changed few lines (see the method updateSoilStates) + self.allowNegativePercolation = False + if 'allowNegativePercolation' in list(self.iniItemsLC.keys()) and self.iniItemsLC['allowNegativePercolation'] == "True": + msg = 'Allowing negative values of percolation percUpp (P1), as done in the oldcalc script of PCR-GLOBWB 1.0. \n' + msg += 'Note that this option is only relevant for the two layer soil model.' + logger.warning(msg) + self.allowNegativePercolation = True + + # In the original oldcalc script of Rens, there is a possibility that rootFraction/transpiration is only defined in the bottom layer, while no root in upper layer(s) + # - To avoid this, Edwin changed few lines (see the methods 'scaleRootFractionsFromTwoLayerSoilParameters' and 'estimateTranspirationAndBareSoilEvap') + self.usingOriginalOldCalcRootTranspirationPartitioningMethod = False + if 'usingOriginalOldCalcRootTranspirationPartitioningMethod' in list(self.iniItemsLC.keys()) and self.iniItemsLC['usingOriginalOldCalcRootTranspirationPartitioningMethod'] == "True": + msg = 'Using the original rootFraction/transpiration as defined in the oldcalc script of PCR-GLOBWB 1.0. \n' + msg += 'There is a possibility that rootFraction/transpiration is only defined in the bottom layer, while no root in upper layer(s).' + logger.warning(msg) + self.usingOriginalOldCalcRootTranspirationPartitioningMethod = True + + # get snow module type and its parameters: + self.snowModuleType = self.iniItemsLC['snowModuleType'] + snowParams = ['freezingT', + 'degreeDayFactor', + 'snowWaterHoldingCap', + 'refreezingCoeff'] + for var in snowParams: + input = self.iniItemsLC[str(var)] + vars(self)[var] = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + vars(self)[var] = pcr.spatial(pcr.scalar(vars(self)[var])) + + + # initialization some variables + self.fractionArea = None # area (m2) of a certain land cover type ; will be assigned by the landSurface module + self.naturalFracVegCover = None # fraction (-) of natural area over (entire) cell ; will be assigned by the landSurface module + self.irrTypeFracOverIrr = None # fraction (m2) of a certain irrigation type over (only) total irrigation area ; will be assigned by the landSurface module + + # previous fractions of land cover (needed for transfering states when land cover fraction (annualy) changes + self.previousFracVegCover = None + + # number of soil layers (two or three) + self.numberOfLayers = self.parameters.numberOfLayers + + # an option to introduce changes of land cover parameters (not only fracVegCover) + self.noAnnualChangesInLandCoverParameter = True + if 'annualChangesInLandCoverParameters' in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions['annualChangesInLandCoverParameters'] == "True": self.noAnnualChangesInLandCoverParameter = False + + # get land cover parameters that are fixed for the entire simulation + if self.noAnnualChangesInLandCoverParameter: + if self.numberOfLayers == 2: + self.fracVegCover, self.arnoBeta, self.rootZoneWaterStorageMin, self.rootZoneWaterStorageRange, \ + self.maxRootDepth, self.adjRootFrUpp, self.adjRootFrLow = \ + self.get_land_cover_parameters() + if self.numberOfLayers == 3: + self.fracVegCover, self.arnoBeta, self.rootZoneWaterStorageMin, self.rootZoneWaterStorageRange, \ + self.maxRootDepth, self.adjRootFrUpp000005, self.adjRootFrUpp005030, self.adjRootFrLow030150 = \ + self.get_land_cover_parameters() + # estimate parameters while transpiration is being halved + self.calculateParametersAtHalfTranspiration() + # calculate TAW for estimating irrigation gross demand + if self.includeIrrigation: self.calculateTotAvlWaterCapacityInRootZone() + + # get additional land cover parameters (ALWAYS fixed for the entire simulation) + landCovParamsAdd = ['minTopWaterLayer', + 'minCropKC'] + for var in landCovParamsAdd: + input = self.iniItemsLC[str(var)] + vars(self)[var] = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + if input != "None":\ + vars(self)[var] = pcr.cover(vars(self)[var],0.0) + + # get additional parameter(s) for irrigation areas (ALWAYS fixed for the entire simulation) + if self.includeIrrigation: + # - cropDeplFactor (dimesionless, crop depletion factor while irrigation is being applied), needed for NON paddy irrigation areas + if self.iniItemsLC['name'].startswith('irr') and self.name != "irrPaddy": + self.cropDeplFactor = vos.readPCRmapClone(self.iniItemsLC['cropDeplFactor'], self.cloneMap, \ + self.tmpDir, self.inputDir) + # - infiltration/percolation losses for paddy fields + if self.name == 'irrPaddy' or self.name == 'irr_paddy':\ + self.design_percolation_loss = self.estimate_paddy_infiltration_loss(self.iniItemsLC) + + # water allocation zones: + self.usingAllocSegments = usingAllocSegments # water allocation option: + if self.usingAllocSegments: + + # cellArea (unit: m2) # TODO: If possible, integrate this one with the one coming from the routing module + cellArea = vos.readPCRmapClone(\ + iniItems.routingOptions['cellAreaMap'], + self.cloneMap, self.tmpDir, self.inputDir) + cellArea = pcr.ifthen(self.landmask, cellArea) + + # reading the allocation zone file + self.allocSegments = vos.readPCRmapClone(\ + iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'], + self.cloneMap,self.tmpDir,self.inputDir,isLddMap=False,cover=None,isNomMap=True) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + self.allocSegments = pcr.clump(self.allocSegments) + + # extrapolate it + self.allocSegments = pcr.cover(self.allocSegments, \ + pcr.windowmajority(self.allocSegments, 0.5)) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # clump it and cover the rests with cell ids + self.allocSegments = pcr.clump(self.allocSegments) + cell_ids = pcr.mapmaximum(pcr.scalar(self.allocSegments)) + pcr.scalar(100.0) + pcr.uniqueid(pcr.boolean(1.0)) + self.allocSegments = pcr.cover(self.allocSegments, pcr.nominal(cell_ids)) + self.allocSegments = pcr.clump(self.allocSegments) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # zonal/segment areas (unit: m2) + self.segmentArea = pcr.areatotal(pcr.cover(cellArea, 0.0), self.allocSegments) + self.segmentArea = pcr.ifthen(self.landmask, self.segmentArea) + + # option to prioritize local sources before abstracting water from neighboring cells + self.prioritizeLocalSourceToMeetWaterDemand = iniItems.landSurfaceOptions['prioritizeLocalSourceToMeetWaterDemand'] == "True" + if self.prioritizeLocalSourceToMeetWaterDemand: + msg = "Local water sources are first used before abstracting water from neighboring cells" + logger.info(msg) + + + # get the names of cropCoefficient files: + self.cropCoefficientNC = vos.getFullPath(self.iniItemsLC['cropCoefficientNC'], self.inputDir) + + #~ # get the names of interceptCap and coverFraction files: + #~ if not self.iniItemsLC['name'].startswith("irr"): + #~ self.interceptCapNC = vos.getFullPath(\ + #~ self.iniItemsLC['interceptCapNC'], self.inputDir) + #~ self.coverFractionNC = vos.getFullPath(\ + #~ self.iniItemsLC['coverFractionNC'], self.inputDir) + + # get the file names of interceptCap and coverFraction files: + if 'interceptCapNC' in list(self.iniItemsLC.keys()) and 'coverFractionNC' in list(self.iniItemsLC.keys()): + self.interceptCapNC = vos.getFullPath(\ + self.iniItemsLC['interceptCapNC'], self.inputDir) + self.coverFractionNC = vos.getFullPath(\ + self.iniItemsLC['coverFractionNC'], self.inputDir) + else: + msg = 'The netcdf files for interceptCapNC (interception capacity) and/or coverFraction (canopy cover fraction) are NOT defined for the landCover type: ' + self.name + '\n' + msg = 'This run assumes zero canopy interception capacity for this run, UNLESS minInterceptCap (minimum interception capacity) is bigger than zero.' + '\n' + logger.warning(msg) + self.coverFractionNC = None + self.interceptCapNC = None + + if 'coverFractionNC' in list(self.iniItemsLC.keys()) and self.iniItemsLC['coverFractionNC'] == "None": self.coverFractionNC = None + if 'interceptCapNC' in list(self.iniItemsLC.keys()) and self.iniItemsLC['interceptCapNC' ] == "None": self.interceptCapNC = None + + # for reporting: output in netCDF files: + self.report = True + try: + self.outDailyTotNC = self.iniItemsLC['outDailyTotNC'].split(",") + self.outMonthTotNC = self.iniItemsLC['outMonthTotNC'].split(",") + self.outMonthAvgNC = self.iniItemsLC['outMonthAvgNC'].split(",") + self.outMonthEndNC = self.iniItemsLC['outMonthEndNC'].split(",") + except: + self.report = False + if self.report: + self.outNCDir = iniItems.outNCDir + self.netcdfObj = PCR2netCDF(iniItems) + # prepare the netCDF objects and files: + if self.outDailyTotNC[0] != "None": + for var in self.outDailyTotNC: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/" + \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "dailyTot.nc",\ + var,"undefined") + + # monthly output in netCDF files: + # - cummulative + if self.outMonthTotNC[0] != "None": + for var in self.outMonthTotNC: + # initiating monthlyVarTot (accumulator variable): + vars(self)[var+'Tot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthTot.nc",\ + var,"undefined") + # - average + if self.outMonthAvgNC[0] != "None": + for var in self.outMonthAvgNC: + # initiating monthlyVarAvg: + vars(self)[var+'Avg'] = None + # initiating monthlyTotAvg (accumulator variable) + vars(self)[var+'Tot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthAvg.nc",\ + var,"undefined") + # - last day of the month + if self.outMonthEndNC[0] != "None": + for var in self.outMonthEndNC: + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthEnd.nc",\ + var,"undefined") + + + def updateIrrigationWaterEfficiency(self,currTimeStep): + #-RvB: irrigation water efficiency + # this reads in the irrigation water efficiency from the configuration file + # at the start of each calendar year - it can optionally handle netCDF files, + # PCRaster maps or values + + var = 'irrigationWaterEfficiency' + + if var in list(self.iniItemsLC.keys()) or 'irrigationEfficiency' in list(self.iniItemsLC.keys()) and (self.iniItemsLC['name'].startswith('irr')): + + msg = "Irrigation efficiency is set based on the file defined in the landCoverOptions." + + if 'irrigationWaterEfficiency' in list(self.iniItemsLC.keys()): + self.iniItemsLC[var] = self.iniItemsLC['irrigationWaterEfficiency'] + + input = self.iniItemsLC[var] + + try: + # static input + self.irrigationEfficiency = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + except: + # dynamic input + if 'nc' in os.path.splitext(input)[1]: + #-netCDF file + ncFileIn = vos.getFullPath(input,self.inputDir) + self.irrigationEfficiency = vos.netcdf2PCRobjClone(ncFileIn,var, \ + currTimeStep, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap) + else: + #-assumed PCRaster file, add year and '.map' extension + input= input + '%04d.map' % currTimeStep.year + self.irrigationEfficiency = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + + extrapolate = True + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False + + if extrapolate: + + # extrapolate efficiency map: # TODO: Make a better extrapolation algorithm (considering cell size, etc.). + window_size = 1.25 * pcr.clone().cellSize() + window_size = min(window_size, min(pcr.clone().nrRows(), pcr.clone().nrCols())*pcr.clone().cellSize()) + + try: + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 0.75)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.00)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.50)) + except: + pass + + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, 1.0) + self.irrigationEfficiency = pcr.max(0.1, self.irrigationEfficiency) + self.irrigationEfficiency = pcr.ifthen(self.landmask, self.irrigationEfficiency) + + else: + + msg = "Irrigation efficiency is set based on the file defined in the landSurfaceOptions (for irrigated land cover types only)." + + logger.info(msg) + + + def get_land_cover_parameters(self, date_in_string = None, get_only_fracVegCover = False): + + # obtain the land cover parameters + + # list of model parameters that will be read + landCovParams = ['minSoilDepthFrac', 'maxSoilDepthFrac', + 'rootFraction1', 'rootFraction2', + 'maxRootDepth', + 'fracVegCover'] + # - and 'arnoBeta' + + # an option to return only fracVegCover + if get_only_fracVegCover: landCovParams = ['fracVegCover'] + + # set initial values to None + lc_parameters = {} + if get_only_fracVegCover == False: + for var in landCovParams+['arnoBeta']: lc_parameters[var] = None + + # get parameters that are fixed for the entire simulation: + if date_in_string == None: + + msg = 'Obtaining the land cover parameters that are fixed for the entire simulation.' + logger.debug(msg) + + if self.iniItemsLC['landCoverMapsNC'] == str(None): + # using pcraster maps + landCoverPropertiesNC = None + for var in landCovParams: + input = self.iniItemsLC[str(var)] + lc_parameters[var] = vos.readPCRmapClone(input, self.cloneMap, + self.tmpDir, self.inputDir) + if input != "None": + lc_parameters[var] = pcr.cover(lc_parameters[var], 0.0) + else: + # using netcdf file + landCoverPropertiesNC = vos.getFullPath(\ + self.iniItemsLC['landCoverMapsNC'], self.inputDir) + for var in landCovParams: + lc_parameters[var] = pcr.cover(vos.netcdf2PCRobjCloneWithoutTime(\ + landCoverPropertiesNC, var, \ + cloneMapFileName = self.cloneMap), 0.0) + + # The parameter arnoBeta for the Improved Arno's scheme: + # - There are three ways in defining arnoBeta. The ranks below indicate their priority: + # 1. defined as a pcraster map file or a uniform scalar value (i.e. self.iniItemsLC['arnoBeta']) + # 2. included in the netcdf file (i.e. self.iniItemsLC['landCoverMapsNC']) + # 3. approximated from the minSoilDepthFrac and maxSoilDepthFrac + + lc_parameters['arnoBeta'] = None + if 'arnoBeta' not in list(self.iniItemsLC.keys()) and get_only_fracVegCover == False: self.iniItemsLC['arnoBeta'] = "None" + + # - option one (top priority): using a pcraster file + if self.iniItemsLC['arnoBeta'] != "None" and get_only_fracVegCover == False: + + logger.debug("The parameter arnoBeta: "+str(self.iniItemsLC['arnoBeta'])) + lc_parameters['arnoBeta'] = vos.readPCRmapClone(self.iniItemsLC['arnoBeta'], self.cloneMap,\ + self.tmpDir, self.inputDir) + + # - option two: included in the netcdf file + if ( + lc_parameters['arnoBeta'] is None + and landCoverPropertiesNC is not None + and not get_only_fracVegCover + ): + + if vos.checkVariableInNC(landCoverPropertiesNC, "arnoBeta"): + + logger.debug("The parameter arnoBeta is defined in the netcdf file "+str(self.iniItemsLC['arnoBeta'])) + lc_parameters['arnoBeta'] = vos.netcdf2PCRobjCloneWithoutTime(landCoverPropertiesNC, 'arnoBeta', self.cloneMap) + + # - option three: approximated from the minSoilDepthFrac and maxSoilDepthFrac + if lc_parameters['arnoBeta'] is None and not get_only_fracVegCover: + + logger.debug("The parameter arnoBeta is approximated from the minSoilDepthFrac and maxSoilDepthFrac values.") + + # make sure that maxSoilDepthFrac >= minSoilDepthFrac: + # - Note that maxSoilDepthFrac is needed only for calculating arnoBeta, + # while minSoilDepthFrac is needed not only for arnoBeta, but also for rootZoneWaterStorageMin + lc_parameters['maxSoilDepthFrac'] = pcr.max(lc_parameters['maxSoilDepthFrac'], lc_parameters['minSoilDepthFrac']) + + # estimating arnoBeta from the values of maxSoilDepthFrac and minSoilDepthFrac. + lc_parameters['arnoBeta'] = pcr.max(0.001,\ + (lc_parameters['maxSoilDepthFrac']-1.)/(1.-lc_parameters['minSoilDepthFrac'])+\ + self.parameters.orographyBeta-0.01) # Rens's line: BCF[TYPE]= max(0.001,(MAXFRAC[TYPE]-1)/(1-MINFRAC[TYPE])+B_ORO-0.01) + + + # get landCovParams that (annualy) changes + # - files provided in netcdf files + if date_in_string != None: + + msg = 'Obtaining the land cover parameters (from netcdf files) for the year/date: '+str(date_in_string) + logger.debug(msg) + + if get_only_fracVegCover: + landCovParams = ['fracVegCover'] + else: + landCovParams += ['arnoBeta'] + + for var in landCovParams: + + # read parameter values from the ncFile mentioned in the ini/configuration file + ini_option = self.iniItemsLC[var+'NC'] + + if ini_option.endswith(vos.netcdf_suffixes): + netcdf_file = vos.getFullPath(ini_option, self.inputDir) + lc_parameters[var] = pcr.cover( + vos.netcdf2PCRobjClone(netcdf_file,var, \ + date_in_string, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap), 0.0) + else: + # reading parameters from pcraster maps or scalar values + try: + lc_parameters[var] = pcr.cover( + pcr.spatial( + vos.readPCRmapClone(ini_option, self.cloneMap,\ + self.tmpDir, self.inputDir)), 0.0) + except: + lc_parameters[var] = vos.readPCRmapClone(ini_option, self.cloneMap,\ + self.tmpDir, self.inputDir) + + # if not defined, arnoBeta would be approximated from the minSoilDepthFrac and maxSoilDepthFrac + if not get_only_fracVegCover and lc_parameters['arnoBeta'] is None: + + logger.debug("The parameter arnoBeta is approximated from the minSoilDepthFrac and maxSoilDepthFrac values.") + + # make sure that maxSoilDepthFrac >= minSoilDepthFrac: + # - Note that maxSoilDepthFrac is needed only for calculating arnoBeta, + # while minSoilDepthFrac is needed not only for arnoBeta, but also for rootZoneWaterStorageMin + lc_parameters['maxSoilDepthFrac'] = pcr.max(lc_parameters['maxSoilDepthFrac'], lc_parameters['minSoilDepthFrac']) + + # estimating arnoBeta from the values of maxSoilDepthFrac and minSoilDepthFrac + lc_parameters['arnoBeta'] = pcr.max(0.001,\ + (lc_parameters['maxSoilDepthFrac']-1.)/(1.-lc_parameters['minSoilDepthFrac'])+\ + self.parameters.orographyBeta-0.01) # Rens's line: BCF[TYPE]= max(0.001,(MAXFRAC[TYPE]-1)/(1-MINFRAC[TYPE])+B_ORO-0.01) + + # limit 0.0 <= fracVegCover <= 1.0 + fracVegCover = pcr.cover(lc_parameters['fracVegCover'], 0.0) + fracVegCover = pcr.max(0.0, fracVegCover) + fracVegCover = pcr.min(1.0, fracVegCover) + + if get_only_fracVegCover: + return pcr.ifthen(self.landmask, fracVegCover) + + + # WMIN (unit: m): minimum local soil water capacity within the grid-cell + rootZoneWaterStorageMin = lc_parameters['minSoilDepthFrac'] * \ + self.parameters.rootZoneWaterStorageCap # This is WMIN in the oldcalc script. + + # WMAX - WMIN (unit: m) + rootZoneWaterStorageRange = \ + self.parameters.rootZoneWaterStorageCap -\ + rootZoneWaterStorageMin + + # the parameter arnoBeta (dimensionless) + arnoBeta = pcr.max(0.001, lc_parameters['arnoBeta']) + arnoBeta = pcr.cover(arnoBeta, 0.001) + + # maxium root depth + maxRootDepth = lc_parameters['maxRootDepth'] + + # saving also minSoilDepthFrac and maxSoilDepthFrac (only for debugging purpose) + self.minSoilDepthFrac = lc_parameters['minSoilDepthFrac'] + self.maxSoilDepthFrac = lc_parameters['maxSoilDepthFrac'] + + # saving also rootFraction1 and rootFraction2 (only for debugging purpose) + self.rootFraction1 = lc_parameters['rootFraction1'] + self.rootFraction2 = lc_parameters['rootFraction2'] + + if self.numberOfLayers == 2 and get_only_fracVegCover == False: + + # scaling root fractions + adjRootFrUpp, adjRootFrLow = \ + self.scaleRootFractionsFromTwoLayerSoilParameters(lc_parameters['rootFraction1'], lc_parameters['rootFraction2']) + + # provide all land cover parameters + return pcr.ifthen(self.landmask, fracVegCover), \ + pcr.ifthen(self.landmask, arnoBeta), \ + pcr.ifthen(self.landmask, rootZoneWaterStorageMin), \ + pcr.ifthen(self.landmask, rootZoneWaterStorageRange), \ + pcr.ifthen(self.landmask, maxRootDepth), \ + pcr.ifthen(self.landmask, adjRootFrUpp), \ + pcr.ifthen(self.landmask, adjRootFrLow) \ + + if self.numberOfLayers == 3 and get_only_fracVegCover == False: + + # scaling root fractions + adjRootFrUpp000005, adjRootFrUpp005030, adjRootFrLow030150 = \ + self.scaleRootFractionsFromTwoLayerSoilParameters(lc_parameters['rootFraction1'], lc_parameters['rootFraction2']) + + # provide all land cover parameters + return pcr.ifthen(self.landmask, fracVegCover), \ + pcr.ifthen(self.landmask, arnoBeta), \ + pcr.ifthen(self.landmask, rootZoneWaterStorageMin), \ + pcr.ifthen(self.landmask, rootZoneWaterStorageRange), \ + pcr.ifthen(self.landmask, maxRootDepth), \ + pcr.ifthen(self.landmask, adjRootFrUpp000005), \ + pcr.ifthen(self.landmask, adjRootFrUpp005030), \ + pcr.ifthen(self.landmask, adjRootFrLow030150) \ + + + def estimate_paddy_infiltration_loss(self, iniPaddyOptions): + + # Due to compaction infiltration/percolation loss rate can be much smaller than original soil saturated conductivity + # - Wada et al. (2014) assume it will be 10 times smaller + if self.numberOfLayers == 2:\ + design_percolation_loss = self.parameters.kSatUpp/10. # unit: m/day + if self.numberOfLayers == 3:\ + design_percolation_loss = self.parameters.kSatUpp000005/10. # unit: m/day + + # However, it can also be much smaller especially in well-puddled paddy fields and should avoid salinization problems. + # - Default minimum and maximum percolation loss values based on FAO values Reference: http://www.fao.org/docrep/s2022e/s2022e08.htm + min_percolation_loss = 0.006 + max_percolation_loss = 0.008 + # - Minimum and maximum percolation loss values given in the ini or configuration file: + if 'minPercolationLoss' in list(iniPaddyOptions.keys()) and iniPaddyOptions['minPercolationLoss'] != "None": + min_percolation_loss = vos.readPCRmapClone(iniPaddyOptions['minPercolationLoss'], self.cloneMap, + self.tmpDir, self.inputDir) + if 'maxPercolationLoss' in list(iniPaddyOptions.keys()) and iniPaddyOptions['maxPercolationLoss'] != "None": + min_percolation_loss = vos.readPCRmapClone(iniPaddyOptions['maxPercolationLoss'], self.cloneMap, + self.tmpDir, self.inputDir) + # - percolation loss at paddy fields (m/day) + design_percolation_loss = pcr.max(min_percolation_loss, \ + pcr.min(max_percolation_loss, design_percolation_loss)) + # - if soil condition is already 'good', we will use its original infiltration/percolation rate + if self.numberOfLayers == 2:\ + design_percolation_loss = pcr.min(self.parameters.kSatUpp , design_percolation_loss) + if self.numberOfLayers == 3:\ + design_percolation_loss = pcr.min(self.parameters.kSatUpp000005, design_percolation_loss) + + # PS: The 'design_percolation_loss' is the maximum loss occuring in paddy fields. + return design_percolation_loss + + def scaleRootFractionsFromTwoLayerSoilParameters(self, rootFraction1, rootFraction2): + + # covering rootFraction1 and rootFraction2 + rootFraction1 = pcr.cover(rootFraction1, 0.0) + rootFraction2 = pcr.cover(rootFraction2, 0.0) + + if self.numberOfLayers == 2: + # root fractions + rootFracUpp = (0.30/0.30) * rootFraction1 + rootFracLow = (1.20/1.20) * rootFraction2 + adjRootFrUpp = vos.getValDivZero(rootFracUpp, (rootFracUpp + rootFracLow)) + adjRootFrLow = vos.getValDivZero(rootFracLow, (rootFracUpp + rootFracLow)) + # RFW1[TYPE]= RFRAC1[TYPE]/(RFRAC1[TYPE]+RFRAC2[TYPE]); + # RFW2[TYPE]= RFRAC2[TYPE]/(RFRAC1[TYPE]+RFRAC2[TYPE]); + # if not defined, put everything in the first layer: + if self.usingOriginalOldCalcRootTranspirationPartitioningMethod == False: + adjRootFrUpp = pcr.max(0.0, pcr.min(1.0, pcr.cover(adjRootFrUpp,1.0))) + adjRootFrLow = pcr.max(0.0, pcr.scalar(1.0) - adjRootFrUpp) + + return adjRootFrUpp, adjRootFrLow + + if self.numberOfLayers == 3: + # root fractions + rootFracUpp000005 = 0.05/0.30 * rootFraction1 + rootFracUpp005030 = 0.25/0.30 * rootFraction1 + rootFracLow030150 = 1.20/1.20 * rootFraction2 + adjRootFrUpp000005 = vos.getValDivZero(rootFracUpp000005, (rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150)) + adjRootFrUpp005030 = vos.getValDivZero(rootFracUpp005030, (rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150)) + adjRootFrLow030150 = vos.getValDivZero(rootFracLow030150, (rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150)) + # + # if not defined, put everything in the first layer: + if self.usingOriginalOldCalcRootTranspirationPartitioningMethod == False: + adjRootFrUpp000005 = pcr.max(0.0, pcr.min(1.0, pcr.cover(adjRootFrUpp000005, 1.0))) + adjRootFrUpp005030 = pcr.max(0.0, pcr.ifthenelse(adjRootFrUpp000005 < 1.0, pcr.min(adjRootFrUpp005030, pcr.scalar(1.0) - adjRootFrUpp000005), 0.0)) + adjRootFrLow030150 = pcr.max(0.0, pcr.scalar(1.0) - (adjRootFrUpp000005 + adjRootFrUpp005030)) + + return adjRootFrUpp000005, adjRootFrUpp005030, adjRootFrLow030150 + + + def scaleRootFractionsOLD(self): + + if self.numberOfLayers == 2: + # root fractions + rootFracUpp = (0.30/0.30) * self.rootFraction1 + rootFracLow = (1.20/1.20) * self.rootFraction2 + self.adjRootFrUpp = rootFracUpp / (rootFracUpp + rootFracLow) + self.adjRootFrLow = rootFracLow / (rootFracUpp + rootFracLow) # RFW1[TYPE]= RFRAC1[TYPE]/(RFRAC1[TYPE]+RFRAC2[TYPE]); + # # RFW2[TYPE]= RFRAC2[TYPE]/(RFRAC1[TYPE]+RFRAC2[TYPE]); + # if not defined, put everything in the first layer: + self.adjRootFrUpp = pcr.min(1.0, pcr.cover(self.adjRootFrUpp,1.0)) + self.adjRootFrLow = pcr.scalar(1.0) - self.adjRootFrUpp + + if self.numberOfLayers == 3: + # root fractions + rootFracUpp000005 = 0.05/0.30 * self.rootFraction1 + rootFracUpp005030 = 0.25/0.30 * self.rootFraction1 + rootFracLow030150 = 1.20/1.20 * self.rootFraction2 + self.adjRootFrUpp000005 = rootFracUpp000005 / (rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150) + self.adjRootFrUpp005030 = rootFracUpp005030 / (rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150) + self.adjRootFrLow030150 = rootFracLow030150 / (rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150) + # + # if not defined, put everything in the first layer: + self.adjRootFrUpp000005 = pcr.min(1.0, pcr.cover(self.adjRootFrUpp000005, 1.0)) + self.adjRootFrUpp005030 = pcr.ifthenelse(self.adjRootFrUpp000005 < 1.0, self.adjRootFrUpp005030, 0.0) + self.adjRootFrLow030150 = pcr.scalar(1.0) - (self.adjRootFrUpp000005 + self.adjRootFrUpp005030) + + def scaleRootFractionsAlternativeOLD(self): + + if self.numberOfLayers == 2: + # root fractions + rootFracUpp = (0.30/0.30) * self.rootFraction1 + rootFracLow = (1.20/1.20) * self.rootFraction2 + self.adjRootFrUpp = pcr.ifthenelse(rootFracUpp + rootFracLow > 0.0, pcr.min(1.0, rootFracUpp/(rootFracUpp + rootFracLow)), 0.0) + self.adjRootFrLow = pcr.ifthenelse(rootFracUpp + rootFracLow > 0.0, pcr.min(1.0, rootFracLow/(rootFracUpp + rootFracLow)), 0.0) + + # original Rens's line: # weighed root fractions + # RFW1[TYPE]= if(RFRAC1[TYPE]+RFRAC2[TYPE] > 0, + # min(1.0,RFRAC1[TYPE]/(RFRAC1[TYPE]+RFRAC2[TYPE])),0.0); + # RFW2[TYPE]= if(RFRAC1[TYPE]+RFRAC2[TYPE] > 0.0, + # min(1.0,RFRAC2[TYPE]/(RFRAC1[TYPE]+RFRAC2[TYPE])),0.0); + + if self.numberOfLayers == 3: + # root fractions + rootFracUpp000005 = 0.05/0.30 * self.rootFraction1 + rootFracUpp005030 = 0.25/0.30 * self.rootFraction1 + rootFracLow030150 = 1.20/1.20 * self.rootFraction2 + self.adjRootFrUpp000005 = pcr.ifthenelse(rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150 > 0.0, pcr.min(1.0, rootFracUpp000005/(rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150)), 0.0) + self.adjRootFrUpp005030 = pcr.ifthenelse(rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150 > 0.0, pcr.min(1.0, rootFracUpp005030/(rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150)), 0.0) + self.adjRootFrLow030150 = pcr.ifthenelse(rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150 > 0.0, pcr.min(1.0, rootFracLow030150/(rootFracUpp000005 + rootFracUpp005030 + rootFracLow030150)), 0.0) + + def calculateParametersAtHalfTranspiration(self): + + # average soil parameters at which actual transpiration is halved + + if self.numberOfLayers == 2: + + #~ self.effSatAt50 = \ + #~ (self.parameters.storCapUpp * \ + #~ self.adjRootFrUpp * \ + #~ (self.parameters.matricSuction50/self.parameters.airEntryValueUpp)**\ + #~ (-1./self.parameters.poreSizeBetaUpp) +\ + #~ self.parameters.storCapLow * \ + #~ self.adjRootFrLow * \ + #~ (self.parameters.matricSuction50/self.parameters.airEntryValueLow)**\ + #~ (-1./self.parameters.poreSizeBetaLow)) /\ + #~ (self.parameters.storCapUpp*self.adjRootFrUpp +\ + #~ self.parameters.storCapLow*self.adjRootFrLow ) + + #~ self.effPoreSizeBetaAt50 = (\ + #~ self.parameters.storCapUpp*self.adjRootFrUpp*\ + #~ self.parameters.poreSizeBetaUpp +\ + #~ self.parameters.storCapLow*self.adjRootFrLow*\ + #~ self.parameters.poreSizeBetaLow) / (\ + #~ (self.parameters.storCapUpp*self.adjRootFrUpp +\ + #~ self.parameters.storCapLow*self.adjRootFrLow )) + + # Rens's original line (version 1.1): THEFF_50[TYPE]= (SC1[TYPE]*RFW1[TYPE]*(PSI_50/PSI_A1[TYPE])**(-1/BCH1[TYPE]) + + # SC2[TYPE]*RFW2[TYPE]*(PSI_50/PSI_A2[TYPE])**(-1/BCH2[TYPE])) / + # (SC1[TYPE]*RFW1[TYPE]+SC2[TYPE]*RFW2[TYPE]); + # + # Rens's modified line (version 1.2): THEFF_50[TYPE]= if(RFW1[TYPE]+RFW2[TYPE] > 0, + # (SC1[TYPE]*RFW1[TYPE]*(PSI_50/PSI_A1[TYPE])**(-1/BCH1[TYPE])+ + # SC2[TYPE]*RFW2[TYPE]*(PSI_50/PSI_A2[TYPE])**(-1/BCH2[TYPE]))/ + # (SC1[TYPE]*RFW1[TYPE]+SC2[TYPE]*RFW2[TYPE]),0.5); + + # Rens's original liner (version 1.1): BCH_50 = (SC1[TYPE]*RFW1[TYPE]*BCH1[TYPE]+SC2[TYPE]*RFW2[TYPE]*BCH2[TYPE])/ + # (SC1[TYPE]*RFW1[TYPE]+SC2[TYPE]*RFW2[TYPE]); + # + # Rens's original lines (version 1.1): BCH_50= if(RFW1[TYPE]+RFW2[TYPE] > 0,(SC1[TYPE]*RFW1[TYPE]*BCH1[TYPE]+SC2[TYPE]*RFW2[TYPE]*BCH2[TYPE])/ + # (SC1[TYPE]*RFW1[TYPE]+SC2[TYPE]*RFW2[TYPE]),0.5*(BCH1[TYPE]+BCH2[TYPE])); + + + denominator = (self.parameters.storCapUpp*self.adjRootFrUpp + + self.parameters.storCapLow*self.adjRootFrLow ) + + self.effSatAt50 = pcr.ifthenelse(denominator > 0.0,\ + (self.parameters.storCapUpp * \ + self.adjRootFrUpp * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueUpp)**\ + (-1./self.parameters.poreSizeBetaUpp) +\ + self.parameters.storCapLow * \ + self.adjRootFrLow * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueLow)**\ + (-1./self.parameters.poreSizeBetaLow)) /\ + (self.parameters.storCapUpp*self.adjRootFrUpp +\ + self.parameters.storCapLow*self.adjRootFrLow ), 0.5) + + self.effPoreSizeBetaAt50 = pcr.ifthenelse(denominator > 0.0,\ + (self.parameters.storCapUpp*self.adjRootFrUpp*\ + self.parameters.poreSizeBetaUpp +\ + self.parameters.storCapLow*self.adjRootFrLow*\ + self.parameters.poreSizeBetaLow) / (\ + (self.parameters.storCapUpp*self.adjRootFrUpp +\ + self.parameters.storCapLow*self.adjRootFrLow )), 0.5*(self.parameters.poreSizeBetaUpp + self.parameters.poreSizeBetaLow)) + + + + if self.numberOfLayers == 3: + + #~ self.effSatAt50 = (self.parameters.storCapUpp000005 * \ + #~ self.adjRootFrUpp000005 * \ + #~ (self.parameters.matricSuction50/self.parameters.airEntryValueUpp000005)**\ + #~ (-1./self.parameters.poreSizeBetaUpp000005) +\ + #~ self.parameters.storCapUpp005030 * \ + #~ self.adjRootFrUpp005030 * \ + #~ (self.parameters.matricSuction50/self.parameters.airEntryValueUpp000005)**\ + #~ (-1./self.parameters.poreSizeBetaUpp000005) +\ + #~ self.parameters.storCapLow030150 * \ + #~ self.adjRootFrLow030150 * \ + #~ (self.parameters.matricSuction50/self.parameters.airEntryValueLow030150)**\ + #~ (-1./self.parameters.poreSizeBetaLow030150) /\ + #~ (self.parameters.storCapUpp000005*self.adjRootFrUpp000005 +\ + #~ self.parameters.storCapUpp005030*self.adjRootFrUpp005030 +\ + #~ self.parameters.storCapLow030150*self.adjRootFrLow030150 )) + + #~ self.effPoreSizeBetaAt50 = (\ + #~ self.parameters.storCapUpp000005*self.adjRootFrUpp000005*\ + #~ self.parameters.poreSizeBetaUpp000005 +\ + #~ self.parameters.storCapUpp005030*self.adjRootFrUpp005030*\ + #~ self.parameters.poreSizeBetaUpp005030 +\ + #~ self.parameters.storCapLow030150*self.adjRootFrLow030150*\ + #~ self.parameters.poreSizeBetaLow030150) / \ + #~ (self.parameters.storCapUpp000005*self.adjRootFrUpp000005 +\ + #~ self.parameters.storCapUpp005030*self.adjRootFrUpp005030 +\ + #~ self.parameters.storCapLow030150*self.adjRootFrLow030150 ) + + denominator = (self.parameters.storCapUpp000005*self.adjRootFrUpp000005 + + self.parameters.storCapUpp005030*self.adjRootFrUpp005030 + + self.parameters.storCapLow030150*self.adjRootFrLow030150 ) + + self.effSatAt50 = pcr.ifthenelse(denominator > 0.0,\ + (self.parameters.storCapUpp000005 * \ + self.adjRootFrUpp000005 * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueUpp000005)**\ + (-1./self.parameters.poreSizeBetaUpp000005) +\ + self.parameters.storCapUpp005030 * \ + self.adjRootFrUpp005030 * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueUpp000005)**\ + (-1./self.parameters.poreSizeBetaUpp000005) +\ + self.parameters.storCapLow030150 * \ + self.adjRootFrLow030150 * \ + (self.parameters.matricSuction50/self.parameters.airEntryValueLow030150)**\ + (-1./self.parameters.poreSizeBetaLow030150) /\ + (self.parameters.storCapUpp000005*self.adjRootFrUpp000005 +\ + self.parameters.storCapUpp005030*self.adjRootFrUpp005030 +\ + self.parameters.storCapLow030150*self.adjRootFrLow030150 )), 0.5) + + self.effPoreSizeBetaAt50 = pcr.ifthenelse(denominator > 0.0,\ + (self.parameters.storCapUpp000005*self.adjRootFrUpp000005*\ + self.parameters.poreSizeBetaUpp000005 +\ + self.parameters.storCapUpp005030*self.adjRootFrUpp005030*\ + self.parameters.poreSizeBetaUpp005030 +\ + self.parameters.storCapLow030150*self.adjRootFrLow030150*\ + self.parameters.poreSizeBetaLow030150) / \ + (self.parameters.storCapUpp000005*self.adjRootFrUpp000005 +\ + self.parameters.storCapUpp005030*self.adjRootFrUpp005030 +\ + self.parameters.storCapLow030150*self.adjRootFrLow030150 ), 0.5 * (0.5*(self.parameters.poreSizeBetaUpp000005 + \ + self.parameters.poreSizeBetaUpp005030) + self.parameters.poreSizeBetaLow030150)) + + # I don't think that we need the following items. + self.effSatAt50 = pcr.cover(self.effSatAt50, 0.5) + if self.numberOfLayers == 2: self.effPoreSizeBetaAt50 = pcr.cover(self.effPoreSizeBetaAt50, 0.5*(self.parameters.poreSizeBetaUpp + self.parameters.poreSizeBetaLow)) + if self.numberOfLayers == 3: self.effPoreSizeBetaAt50 = pcr.cover(self.effPoreSizeBetaAt50, 0.5 * (0.5*(self.parameters.poreSizeBetaUpp000005 + \ + self.parameters.poreSizeBetaUpp005030) + self.parameters.poreSizeBetaLow030150)) + + # crop only to the landmask region + self.effSatAt50 = pcr.ifthen(self.landmask, self.effSatAt50) + self.effPoreSizeBetaAt50 = pcr.ifthen(self.landmask, self.effPoreSizeBetaAt50) + + def calculateTotAvlWaterCapacityInRootZone(self): + + # total water capacity in the root zone (upper soil layers) + # Note: This is dependent on the land cover type. + + if self.numberOfLayers == 2: + + self.totAvlWater = \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp - self.parameters.effSatAtWiltPointUpp))*\ + (self.parameters.satVolMoistContUpp - self.parameters.resVolMoistContUpp )*\ + pcr.min(self.parameters.thickUpp,self.maxRootDepth) + \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapLow - self.parameters.effSatAtWiltPointLow))*\ + (self.parameters.satVolMoistContLow - self.parameters.resVolMoistContLow )*\ + pcr.min(self.parameters.thickLow,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp,0.)) # Edwin modified this line. Edwin uses soil thickness thickUpp and thickLow (instead of storCapUpp and storCapLow). + # And Rens support this. + self.totAvlWater = pcr.min(self.totAvlWater, \ + self.parameters.storCapUpp + self.parameters.storCapLow) + + if self.numberOfLayers == 3: + + self.totAvlWater = \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp000005 - self.parameters.effSatAtWiltPointUpp000005))*\ + (self.parameters.satVolMoistContUpp000005 - self.parameters.resVolMoistContUpp000005 )*\ + pcr.min(self.parameters.thickUpp000005,self.maxRootDepth) + \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp005030 - self.parameters.effSatAtWiltPointUpp005030))*\ + (self.parameters.satVolMoistContUpp005030 - self.parameters.resVolMoistContUpp005030 )*\ + pcr.min(self.parameters.thickUpp005030,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp000005)) + \ + (pcr.max(0.,\ + self.parameters.effSatAtFieldCapLow030150 - self.parameters.effSatAtWiltPointLow030150))*\ + (self.parameters.satVolMoistContLow030150 - self.parameters.resVolMoistContLow030150 )*\ + pcr.min(self.parameters.thickLow030150,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp005030,0.)) + # + self.totAvlWater = pcr.min(self.totAvlWater, \ + self.parameters.storCapUpp000005 + \ + self.parameters.storCapUpp005030 + \ + self.parameters.storCapLow030150) + + + def getICsLC(self,iniItems,iniConditions = None): + + if self.numberOfLayers == 2: + + # List of state and flux variables: + initialVars = ['interceptStor', + 'snowCoverSWE','snowFreeWater', + 'topWaterLayer', + 'storUpp', + 'storLow', + 'interflow'] + for var in initialVars: + if iniConditions == None: + input = self.iniItemsLC[str(var)+'Ini'] + vars(self)[var] = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir) + vars(self)[var] = pcr.cover(vars(self)[var], 0.0) + else: + vars(self)[var] = iniConditions[str(var)] + vars(self)[var] = pcr.ifthen(self.landmask,vars(self)[var]) + + if self.numberOfLayers == 3: + + # List of state and flux variables: + initialVars = ['interceptStor', + 'snowCoverSWE','snowFreeWater', + 'topWaterLayer', + 'storUpp000005','storUpp005030', + 'storLow030150', + 'interflow'] + for var in initialVars: + if iniConditions == None: + input = self.iniItemsLC[str(var)+'Ini'] + vars(self)[var] = vos.readPCRmapClone(input,self.cloneMap, + self.tmpDir,self.inputDir, + cover = 0.0) + vars(self)[var] = pcr.cover(vars(self)[var], 0.0) + else: + vars(self)[var] = iniConditions[str(var)] + vars(self)[var] = pcr.ifthen(self.landmask,vars(self)[var]) + + def updateLC(self,meteo,groundwater,routing,\ + capRiseFrac,\ + nonIrrGrossDemandDict,swAbstractionFractionDict,\ + currTimeStep,\ + allocSegments,\ + desalinationWaterUse,\ + groundwater_pumping_region_ids,\ + regionalAnnualGroundwaterAbstractionLimit): + + # get land cover parameters at the first day of the year or the first day of the simulation + if self.noAnnualChangesInLandCoverParameter == False and\ + (currTimeStep.timeStepPCR == 1 or currTimeStep.doy == 1): + if self.numberOfLayers == 2: + self.fracVegCover, self.arnoBeta, self.rootZoneWaterStorageMin, self.rootZoneWaterStorageRange, \ + self.maxRootDepth, self.adjRootFrUpp, self.adjRootFrLow = \ + self.get_land_cover_parameters(currTimeStep.fulldate) + if self.numberOfLayers == 3: + self.fracVegCover, self.arnoBeta, self.rootZoneWaterStorageMin, self.rootZoneWaterStorageRange, \ + self.maxRootDepth, self.adjRootFrUpp000005, self.adjRootFrUpp005030, self.adjRootFrLow030150 = \ + self.get_land_cover_parameters(currTimeStep.fulldate) + # estimate parameters while transpiration is being halved + self.calculateParametersAtHalfTranspiration() + # calculate TAW for estimating irrigation gross demand + if self.includeIrrigation: self.calculateTotAvlWaterCapacityInRootZone() + + # calculate total PotET (based on meteo and cropKC) + self.getPotET(meteo,currTimeStep) + + # calculate interception evaporation flux (m/day) and update interception storage (m) + self.interceptionUpdate(meteo, currTimeStep) + + # calculate snow melt (or refreezing) + if self.snowModuleType == "Simple": self.snowMeltHBVSimple(meteo,currTimeStep) + # TODO: Define other snow modules + + # calculate qDR & qSF & q23 (and update storages) + self.upperSoilUpdate(meteo, \ + groundwater, \ + routing, \ + capRiseFrac, \ + nonIrrGrossDemandDict, + swAbstractionFractionDict,\ + currTimeStep, \ + allocSegments, \ + desalinationWaterUse, \ + groundwater_pumping_region_ids,regionalAnnualGroundwaterAbstractionLimit) + + # saturation degrees (needed only for reporting): + if self.numberOfSoilLayers == 2: + self.satDegUpp = vos.getValDivZero(\ + self.storUpp, self.parameters.storCapUpp,\ + vos.smallNumber,0.) + self.satDegUpp = pcr.ifthen(self.landmask, self.satDegUpp) + self.satDegLow = vos.getValDivZero(\ + self.storLow, self.parameters.storCapLow,\ + vos.smallNumber,0.) + self.satDegLow = pcr.ifthen(self.landmask, self.satDegLow) + + self.satDegUppTotal = self.satDegUpp + self.satDegLowTotal = self.satDegLow + + self.satDegTotal = pcr.ifthen(self.landmask, \ + vos.getValDivZero(\ + self.storUpp + self.storLow, self.parameters.storCapUpp + self.parameters.storCapLow,\ + vos.smallNumber, 0.0)) + + if self.numberOfSoilLayers == 3: + self.satDegUpp000005 = vos.getValDivZero(\ + self.storUpp000005, self.parameters.storCapUpp000005,\ + vos.smallNumber,0.) + self.satDegUpp000005 = pcr.ifthen(self.landmask, self.satDegUpp000005) + self.satDegUpp005030 = vos.getValDivZero(\ + self.storUpp005030, self.parameters.storCapUpp005030,\ + vos.smallNumber,0.) + self.satDegUpp005030 = pcr.ifthen(self.landmask, self.satDegUpp005030) + self.satDegLow030150 = vos.getValDivZero(\ + self.storLow030150, self.parameters.storCapLow030150,\ + vos.smallNumber,0.) + self.satDegLow030150 = pcr.ifthen(self.landmask, self.satDegLow030150) + + self.satDegUppTotal = vos.getValDivZero(\ + self.storUpp000005 + self.storUpp005030,\ + self.parameters.storCapUpp000005 + \ + self.parameters.storCapUpp005030,\ + vos.smallNumber,0.) + self.satDegUppTotal = pcr.ifthen(self.landmask, self.satDegUppTotal) + self.satDegLowTotal = self.satDegLow030150 + + self.satDegTotal = pcr.ifthen(self.landmask, \ + vos.getValDivZero(\ + self.storUpp000005 + self.storUpp005030 + self.satDegLow030150, self.parameters.storCapUpp000005 + self.parameters.storCapUpp005030 + self.parameters.storCapLow030150,\ + vos.smallNumber, 0.0)) + + + if self.report == True: + # writing Output to netcdf files + # - daily output: + timeStamp = datetime.datetime(currTimeStep.year,\ + currTimeStep.month,\ + currTimeStep.day,\ + 0) + timestepPCR = currTimeStep.timeStepPCR + if self.outDailyTotNC[0] != "None": + for var in self.outDailyTotNC: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "dailyTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,timestepPCR-1) + + # writing monthly output to netcdf files + # -cummulative + if self.outMonthTotNC[0] != "None": + for var in self.outMonthTotNC: + # introduce variables at the beginning of simulation: + if currTimeStep.timeStepPCR == 1: vars(self)[var+'Tot'] = \ + pcr.scalar(0.0) + # reset variables at the beginning of the month + if currTimeStep.day == 1: vars(self)[var+'Tot'] = \ + pcr.scalar(0.0) + # accumulating + vars(self)[var+'Tot'] += vars(self)[var] + # reporting at the end of the month: + if currTimeStep.endMonth == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'Tot'),vos.MV),\ + timeStamp,currTimeStep.monthIdx-1) + # -average + if self.outMonthAvgNC[0] != "None": + for var in self.outMonthAvgNC: + # only if a accumulator variable has not been defined: + if var not in self.outMonthTotNC: + # introduce accumulator variables at the beginning of simulation: + if currTimeStep.timeStepPCR == 1: vars(self)[var+'Tot'] = \ + pcr.scalar(0.0) + # reset variables at the beginning of the month + if currTimeStep.day == 1: vars(self)[var+'Tot'] = \ + pcr.scalar(0.0) + # accumulating + vars(self)[var+'Tot'] += vars(self)[var] + # calculating average and reporting at the end of the month: + if currTimeStep.endMonth == True: + vars(self)[var+'Avg'] = vars(self)[var+'Tot'] /\ + currTimeStep.day + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthAvg.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'Avg'),vos.MV),\ + timeStamp,currTimeStep.monthIdx-1) + # -last day of the month + if self.outMonthEndNC[0] != "None": + for var in self.outMonthEndNC: + # reporting at the end of the month: + if currTimeStep.endMonth == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var) + "_" + \ + str(self.iniItemsLC['name']) + "_" + \ + "monthEnd.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,currTimeStep.monthIdx-1) + + + def getPotET(self, meteo, currTimeStep): + + # get crop coefficient: + if self.iniItemsLC['cropCoefficientNC'] == "None": + cropKC = pcr.ifthen(self.landmask, pcr.spatial(pcr.scalar(0.0))) + else: + cropKC = pcr.cover( + vos.netcdf2PCRobjClone(self.cropCoefficientNC,'kc', \ + currTimeStep.fulldate, useDoy = 'daily_seasonal',\ + cloneMapFileName = self.cloneMap), 0.0) + self.inputCropKC = cropKC # This line is needed for debugging. (Can we remove this?) + self.cropKC = pcr.max(cropKC, self.minCropKC) + + # calculate potential ET (unit: m/day) + self.totalPotET = pcr.ifthen(self.landmask,\ + self.cropKC * meteo.referencePotET) + + # calculate potential bare soil evaporation and transpiration (unit: m/day) + self.potBareSoilEvap = pcr.ifthen(self.landmask,\ + self.minCropKC * meteo.referencePotET) + self.potTranspiration = pcr.max(0.0, \ + pcr.ifthen(self.landmask,\ + self.totalPotET - self.potBareSoilEvap)) + + if self.debugWaterBalance: + vos.waterBalanceCheck([self.totalPotET],\ + [self.potBareSoilEvap, self.potTranspiration],\ + [],\ + [],\ + 'partitioning potential evaporation',\ + True,\ + currTimeStep.fulldate,threshold=5e-4) + + def interceptionUpdate(self, meteo, currTimeStep): + + if self.debugWaterBalance: + prevStates = [self.interceptStor] + + # get interceptCap: + interceptCap = pcr.scalar(self.minInterceptCap) + coverFraction = pcr.scalar(1.0) + if self.interceptCapNC != None and self.coverFractionNC != None: + interceptCap = \ + pcr.cover( + vos.netcdf2PCRobjClone(self.interceptCapNC,\ + 'interceptCapInput',\ + currTimeStep.fulldate, useDoy = 'daily_seasonal',\ + cloneMapFileName = self.cloneMap), 0.0) + self.interceptCapInput = interceptCap # This line is needed for debugging. + coverFraction = \ + pcr.cover( + vos.netcdf2PCRobjClone(self.coverFractionNC,\ + 'coverFractionInput',\ + currTimeStep.fulldate, useDoy = 'daily_seasonal',\ + cloneMapFileName = self.cloneMap), 0.0) + coverFraction = pcr.cover(coverFraction, 0.0) + interceptCap = coverFraction * interceptCap # original Rens line: ICC[TYPE] = CFRAC[TYPE]*INTCMAX[TYPE]; + + # canopy/cover fraction over the entire cell area (unit: m2) + self.coverFraction = coverFraction + + # Edwin added the following line to extend the interception definition. + self.interceptCap = pcr.max(interceptCap, self.minInterceptCap) + + # throughfall = surplus above the interception storage threshold + if self.interceptionModuleType == "Modified": + # extended interception definition/scope (not only canopy) + self.throughfall = pcr.max(0.0, self.interceptStor + \ + meteo.precipitation - \ + self.interceptCap) # original Rens line: PRP = (1-CFRAC[TYPE])*PRPTOT+max(CFRAC[TYPE]*PRPTOT+INTS_L[TYPE]-ICC[TYPE],0) + # Edwin modified this line to extend the interception scope (not only canopy interception). + if self.interceptionModuleType == "Original": + # only canopy interception (not only canopy) + self.throughfall = (1.0 - coverFraction) * meteo.precipitation +\ + pcr.max(0.0, coverFraction * meteo.precipitation + self.interceptStor - self.interceptCap) + + # update interception storage after throughfall + self.interceptStor = pcr.max(0.0, self.interceptStor + \ + meteo.precipitation - \ + self.throughfall) # original Rens line: INTS_L[TYPE] = max(0,INTS_L[TYPE]+PRPTOT-PRP) + + # partitioning throughfall into snowfall and liquid Precipitation: + estimSnowfall = pcr.ifthenelse(meteo.temperature < self.freezingT, \ + meteo.precipitation, 0.0) + # original Rens line: SNOW = if(TA0,PRP/PRPTOT,0) + # - liquid precipitation (m/day) + self.liquidPrecip = pcr.max(0.0,\ + self.throughfall - self.snowfall) # original Rens line: PRP = PRP-SNOW + + # potential interception flux (m/day) + # - this is depending on 'interceptionModuleType' + if self.interceptionModuleType == 'Original': + # only canopy interception + self.potInterceptionFlux = self.potTranspiration + if self.interceptionModuleType == 'Modified': + # extended interception definition/scope (not only canopy) + self.potInterceptionFlux = self.totalPotET # added by Edwin to extend the interception scope/definition + + + # evaporation from intercepted water (based on potInterceptionFlux) + # - based on Van Beek et al. (2011) + self.interceptEvap = pcr.min(self.interceptStor, \ + self.potInterceptionFlux * \ + (vos.getValDivZero(self.interceptStor, self.interceptCap, \ + vos.smallNumber, 0.) ** (2.00/3.00))) + # EACT_L[TYPE]= min(INTS_L[TYPE],(T_p[TYPE]*if(ICC[TYPE]>0,INTS_L[TYPE]/ICC[TYPE],0)**(2/3))) + #~ # - Edwin simplify it + #~ self.interceptEvap = pcr.min(self.interceptStor, self.potInterceptionFlux) + + # update interception storage + self.interceptStor = pcr.max(0.0, \ + self.interceptStor - self.interceptEvap) # INTS_L[TYPE]= INTS_L[TYPE]-EACT_L[TYPE] + + # update potBareSoilEvap and potTranspiration after interceptEvap + if self.interceptionModuleType == 'Modified': + # fraction of potential bare soil evaporation and transpiration + fracPotBareSoilEvap = pcr.max(0.0, pcr.min(1.0, \ + vos.getValDivZero(self.potBareSoilEvap, \ + self.potBareSoilEvap + self.potTranspiration, vos.smallNumber))) + fracPotTranspiration = pcr.scalar(1.0 - self.fracPotBareSoilEvap) + # substract interceptEvap from potBareSoilEvap and potTranspiration + self.potBareSoilEvap = pcr.max(0.0, self.potBareSoilEvap -\ + fracPotBareSoilEvap * self.interceptEvap) + self.potTranspiration = pcr.max(0.0, self.potTranspiration -\ + fracPotTranspiration * self.interceptEvap) + # original Rens line: T_p[TYPE] = max(0,T_p[TYPE]-EACT_L[TYPE]) + # Edwin modified this line to extend the interception scope/definition (not only canopy interception). + if self.interceptionModuleType == 'Original': + self.potTranspiration = pcr.max(0.0, self.potTranspiration - self.interceptEvap) + + # update actual evaporation (after interceptEvap) + self.actualET = 0. # interceptEvap is the first flux in ET + self.actualET += self.interceptEvap + + if self.debugWaterBalance: + vos.waterBalanceCheck([self.throughfall],\ + [self.snowfall, self.liquidPrecip],\ + [],\ + [],\ + 'rain-snow-partitioning',\ + True,\ + currTimeStep.fulldate, threshold=1e-5) + vos.waterBalanceCheck([meteo.precipitation], + [self.throughfall, self.interceptEvap], + prevStates,\ + [self.interceptStor],\ + 'interceptStor',\ + True,\ + currTimeStep.fulldate,threshold=1e-4) + + def interceptionUpdateOriginalVersion(self,meteo,currTimeStep): + + # TODO: Rewrite this method as defined by Rens. + + #~ if self.debugWaterBalance: + #~ prevStates = [self.interceptStor] + #~ + #~ # get interceptCap: + #~ interceptCap = pcr.scalar(self.minInterceptCap) + #~ coverFraction = pcr.scalar(1.0) + #~ if self.coverFractionNC != None or + #~ + #~ + #~ not self.iniItemsLC['name'].startswith("irr"): # This line assumes that no interception capacity for paddy and non paddy types + #~ interceptCap = \ + #~ pcr.cover( + #~ vos.netcdf2PCRobjClone(self.interceptCapNC,\ + #~ 'interceptCapInput',\ + #~ currTimeStep.fulldate, useDoy = 'daily_seasonal',\ + #~ cloneMapFileName = self.cloneMap), 0.0) + #~ self.interceptCapInput = interceptCap # This line is needed for debugging. + #~ coverFraction = \ + #~ pcr.cover( + #~ vos.netcdf2PCRobjClone(self.coverFractionNC,\ + #~ 'coverFractionInput',\ + #~ currTimeStep.fulldate, useDoy = 'daily_seasonal',\ + #~ cloneMapFileName = self.cloneMap), 0.0) + #~ coverFraction = pcr.cover(coverFraction, 0.0) + #~ interceptCap = coverFraction * interceptCap # original Rens line: ICC[TYPE] = CFRAC[TYPE]*INTCMAX[TYPE]; + #~ self.interceptCap = interceptCap + #~ + #~ # Edwin added this line to extend the interception definition (not only canopy interception) + #~ self.interceptCap = pcr.max(self.interceptCap, self.minInterceptCap) + #~ + #~ # canopy/cover fraction over the entire cell area (unit: m2) + #~ self.coverFraction = coverFraction + #~ + #~ # throughfall (m/day) + #~ self.throughfall = (1.0 - coverFraction) * meteo.precipitation +\ + #~ pcr.max(0.0, coverFraction * meteo.precipitation + self.interceptStor - self.interceptCap) + #~ # original Rens line: PRP = (1-CFRAC[TYPE])*PRPTOT+max(CFRAC[TYPE]*PRPTOT+INTS_L[TYPE]-ICC[TYPE],0) + #~ + #~ # make sure that throughfall is never negative + #~ self.throughfall = pcr.max(0.0, self.throughfall) + #~ + #~ # update interception storage after throughfall + #~ self.interceptStor = pcr.max(0.0, self.interceptStor + \ + #~ meteo.precipitation - \ + #~ self.throughfall) # original Rens line: INTS_L[TYPE] = max(0,INTS_L[TYPE]+PRPTOT-PRP) + #~ + #~ # partitioning throughfall into snowfall and liquid Precipitation: + #~ estimSnowfall = pcr.ifthenelse(meteo.temperature < self.freezingT, \ + #~ meteo.precipitation, 0.0) # original Rens line: SNOW = if(TA 0.0, self.throughfall/totalPrec, 0.0)) + #~ # - liquid throughfall passing the canopy + #~ self.liquidPrecip = pcr.max(0.0,\ + #~ self.throughfall - self.snowfall) # original Rens line: PRP = PRP-SNOW +#~ + #~ # potential interception flux (m/day) + #~ self.potInterceptionFlux = self.potTranspiration # Rens only uses potTranspiration + #~ + #~ # evaporation from intercepted water (based on potInterceptionFlux) + #~ self.interceptEvap = pcr.min(self.interceptStor, \ + #~ self.potInterceptionFlux * \ + #~ pcr.ifthenelse(self.interceptCap > 0.0, (self.interceptStor/self.interceptCap), 0.0) ** (2.0/3.0)) + #~ # EACT_L[TYPE] = min(INTS_L[TYPE],(T_p[TYPE]*if(ICC[TYPE]>0,INTS_L[TYPE]/ICC[TYPE],0)**(2/3))) + #~ + #~ # make sure evaporation does not exceed available enerrgy + #~ self.interceptEvap = pcr.min(self.interceptEvap, self.potInterceptionFlux) + #~ + #~ # update interception storage + #~ self.interceptStor = pcr.max(0.0, \ + #~ self.interceptStor - self.interceptEvap) # INTS_L[TYPE] = INTS_L[TYPE]-EACT_L[TYPE] + #~ + #~ # update potTranspiration + #~ self.potTranspiration = pcr.max(0.0, self.potTranspiration - self.interceptEvap) # original Rens line: T_p[TYPE]= max(0,T_p[TYPE]-EACT_L[TYPE]) +#~ + #~ # update actual evaporation (after interceptEvap) + #~ self.actualET = 0. # interceptEvap is the first flux in ET + #~ self.actualET += self.interceptEvap +#~ + #~ if self.debugWaterBalance: + #~ vos.waterBalanceCheck([self.throughfall],\ + #~ [self.snowfall,self.liquidPrecip],\ + #~ [],\ + #~ [],\ + #~ 'rain-snow-partitioning',\ + #~ True,\ + #~ currTimeStep.fulldate,threshold=1e-5) + #~ vos.waterBalanceCheck([meteo.precipitation], + #~ [self.throughfall,self.interceptEvap], + #~ prevStates,\ + #~ [self.interceptStor],\ + #~ 'interceptStor',\ + #~ True,\ + #~ currTimeStep.fulldate,threshold=1e-4) + + pass + + def snowMeltHBVSimple(self,meteo,currTimeStep): + + if self.debugWaterBalance: + prevStates = [self.snowCoverSWE,self.snowFreeWater] + prevSnowCoverSWE = self.snowCoverSWE + prevSnowFreeWater = self.snowFreeWater + + # changes in snow cover: - melt ; + gain in snow or refreezing + deltaSnowCover = \ + pcr.ifthenelse(meteo.temperature <= self.freezingT, \ + self.refreezingCoeff*self.snowFreeWater, \ + -pcr.min(self.snowCoverSWE, \ + pcr.max(meteo.temperature - self.freezingT, 0.0) * \ + self.degreeDayFactor)*1.0*1.0) # DSC[TYPE] = if(TA<=TT,CFR*SCF_L[TYPE], + # -min(SC_L[TYPE],max(TA-TT,0)*CFMAX*Duration*timeslice())) + #~ deltaSnowCover = \ + #~ pcr.ifthenelse(meteo.temperature > self.freezingT, -pcr.min(self.snowCoverSWE, \ + #~ pcr.max(meteo.temperature - self.freezingT, 0.0) * \ + #~ self.degreeDayFactor)*1.0*1.0, \ + #~ self.refreezingCoeff*self.snowFreeWater) + + # update snowCoverSWE + self.snowCoverSWE = pcr.max(0.0, self.snowfall + deltaSnowCover + self.snowCoverSWE) + # SC_L[TYPE] = max(0.0, SC_L[TYPE]+DSC[TYPE]+SNOW) + + # for reporting snow melt in m/day + self.snowMelt = pcr.ifthenelse(deltaSnowCover < 0.0, deltaSnowCover * pcr.scalar(-1.0), pcr.scalar(0.0)) + + # update snowFreeWater = liquid water stored above snowCoverSWE + self.snowFreeWater = self.snowFreeWater - deltaSnowCover + \ + self.liquidPrecip # SCF_L[TYPE] = SCF_L[TYPE]-DSC[TYPE]+PRP; + + # netLqWaterToSoil = net liquid transferred to soil + self.netLqWaterToSoil = pcr.max(0., self.snowFreeWater - \ + self.snowWaterHoldingCap * self.snowCoverSWE) # Pn = max(0,SCF_L[TYPE]-CWH*SC_L[TYPE]) + + # update snowFreeWater (after netLqWaterToSoil) + self.snowFreeWater = pcr.max(0., self.snowFreeWater - \ + self.netLqWaterToSoil) # SCF_L[TYPE] = max(0,SCF_L[TYPE]-Pn) + + # evaporation from snowFreeWater (based on potBareSoilEvap) + self.actSnowFreeWaterEvap = pcr.min(self.snowFreeWater, \ + self.potBareSoilEvap) # ES_a[TYPE] = min(SCF_L[TYPE],ES_p[TYPE]) + + # update snowFreeWater and potBareSoilEvap + self.snowFreeWater = pcr.max(0.0, \ + self.snowFreeWater - self.actSnowFreeWaterEvap) + # SCF_L[TYPE]= SCF_L[TYPE]-ES_a[TYPE] + self.potBareSoilEvap = pcr.max(0, \ + self.potBareSoilEvap - self.actSnowFreeWaterEvap) + # ES_p[TYPE]= max(0,ES_p[TYPE]-ES_a[TYPE]) + + # update actual evaporation (after evaporation from snowFreeWater) + self.actualET += self.actSnowFreeWaterEvap # EACT_L[TYPE]= EACT_L[TYPE]+ES_a[TYPE]; + + if self.debugWaterBalance: + vos.waterBalanceCheck([self.snowfall, self.liquidPrecip], + [self.netLqWaterToSoil,\ + self.actSnowFreeWaterEvap], + prevStates,\ + [self.snowCoverSWE, self.snowFreeWater],\ + 'snow module',\ + True,\ + currTimeStep.fulldate,threshold=1e-4) + vos.waterBalanceCheck([self.snowfall, deltaSnowCover],\ + [pcr.scalar(0.0)],\ + [prevSnowCoverSWE],\ + [self.snowCoverSWE],\ + 'snowCoverSWE',\ + True,\ + currTimeStep.fulldate,threshold=5e-4) + vos.waterBalanceCheck([self.liquidPrecip], + [deltaSnowCover, self.actSnowFreeWaterEvap, self.netLqWaterToSoil], + [prevSnowFreeWater],\ + [self.snowFreeWater],\ + 'snowFreeWater',\ + True,\ + currTimeStep.fulldate,threshold=5e-4) + + def getSoilStates(self): + + if self.numberOfLayers == 2: + + # initial total soilWaterStorage + self.soilWaterStorage = pcr.max(0.,\ + self.storUpp + \ + self.storLow ) + + # effective degree of saturation (-) + self.effSatUpp = pcr.max(0., self.storUpp/ self.parameters.storCapUpp) # THEFF1= max(0,S1_L[TYPE]/SC1[TYPE]); + self.effSatLow = pcr.max(0., self.storLow/ self.parameters.storCapLow) # THEFF2= max(0,S2_L[TYPE]/SC2[TYPE]); + self.effSatUpp = pcr.min(1., self.effSatUpp) + self.effSatLow = pcr.min(1., self.effSatLow) + self.effSatUpp = pcr.cover(self.effSatUpp, 1.0) + self.effSatLow = pcr.cover(self.effSatLow, 1.0) + + # matricSuction (m) + self.matricSuctionUpp = self.parameters.airEntryValueUpp*\ + (pcr.max(0.01,self.effSatUpp)**-self.parameters.poreSizeBetaUpp) + self.matricSuctionLow = self.parameters.airEntryValueLow*\ + (pcr.max(0.01,self.effSatLow)**-self.parameters.poreSizeBetaLow) # PSI1= PSI_A1[TYPE]*max(0.01,THEFF1)**-BCH1[TYPE]; + # PSI2= PSI_A2[TYPE]*max(0.01,THEFF2)**-BCH2[TYPE]; + + # kUnsat (m.day-1): unsaturated hydraulic conductivity + #~ KUnSatUpp = pcr.max(0.,pcr.max(self.parameters.THEFF1_50,\ + #~ effSatUpp)**\ + #~ self.parameters.campbellBeta1*self.parameters.KSat1) # DW's code + #~ KUnSatLow = pcr.max(0.,pcr.max(parameters.THEFF2_50,\ + #~ effSatLow)**\ + #~ self.parameters.campbellBeta2*self.parameters.KSat2) # DW's code + # + self.kUnsatUpp = pcr.max(0.,(self.effSatUpp**\ + self.parameters.campbellBetaUpp)*self.parameters.kSatUpp) # original Rens's code: KTHEFF1= max(0,THEFF1**BCB1[TYPE]*KS1[TYPE]) + self.kUnsatLow = pcr.max(0.,(self.effSatLow**\ + self.parameters.campbellBetaLow)*self.parameters.kSatLow) # original Rens's code: KTHEFF2= max(0,THEFF2**BCB2[TYPE]*KS2[TYPE]) + self.kUnsatUpp = pcr.min(self.kUnsatUpp,self.parameters.kSatUpp) + self.kUnsatLow = pcr.min(self.kUnsatLow,self.parameters.kSatLow) + + # kThVert (m.day-1) = unsaturated conductivity capped at field capacity + # - exchange between layers capped at field capacity + self.kThVertUppLow = pcr.min(\ + pcr.sqrt(self.kUnsatUpp*self.kUnsatLow),\ + (self.kUnsatUpp*self.kUnsatLow* \ + self.parameters.kUnsatAtFieldCapUpp*\ + self.parameters.kUnsatAtFieldCapLow)**0.25) + # KTHVERT = min(sqrt(KTHEFF1*KTHEFF2),(KTHEFF1*KTHEFF2*KTHEFF1_FC*KTHEFF2_FC)**0.25) + + # gradient for capillary rise (index indicating target store to its underlying store) + self.gradientUppLow = pcr.max(0.0,\ + (self.matricSuctionUpp-self.matricSuctionLow)*2./\ + (self.parameters.thickUpp+self.parameters.thickLow)-pcr.scalar(1.0)) + self.gradientUppLow = pcr.cover(self.gradientUppLow, 0.0) + # GRAD = max(0,2*(PSI1-PSI2)/(Z1[TYPE]+Z2[TYPE])-1); + + # readily available water in the root zone (upper soil layers) + #~ readAvlWater = \ + #~ (pcr.max(0.,\ + #~ effSatUpp -self.parameters.THEFF1_WP))*\ + #~ (parameters.satVolWC1 -parameters.resVolWC1) *\ + #~ pcr.min(parameters.storCapUpp,self.maxRootDepth) + \ + #~ (pcr.max(0.,\ + #~ effSatLow -self.parameters.THEFF2_WP))*\ + #~ (parameters.satVolWC2 -parameters.resVolWC2) *\ + #~ pcr.min(parameters.storCapLow,\ + #~ pcr.max(self.maxRootDepth-self.parameters.storCapUpp,0.)) # DW's code (using storCapUpp and storCapLow). Edwin does not agree with this. + # + self.readAvlWater = \ + (pcr.max(0.,\ + self.effSatUpp - self.parameters.effSatAtWiltPointUpp))*\ + (self.parameters.satVolMoistContUpp - self.parameters.resVolMoistContUpp )*\ + pcr.min(self.parameters.thickUpp,self.maxRootDepth) + \ + (pcr.max(0.,\ + self.effSatLow - self.parameters.effSatAtWiltPointLow))*\ + (self.parameters.satVolMoistContLow - self.parameters.resVolMoistContLow )*\ + pcr.min(self.parameters.thickLow,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp,0.)) # Edwin modified this line. Edwin uses soil thickness thickUpp & thickLow (instead of storCapUpp & storCapLow). + # And Rens support this. + + if self.numberOfLayers == 3: + + # initial total soilWaterStorage + self.soilWaterStorage = pcr.max(0.,\ + self.storUpp000005 + \ + self.storUpp005030 + \ + self.storLow030150 ) + + # effective degree of saturation (-) + self.effSatUpp000005 = pcr.max(0., self.storUpp000005/ self.parameters.storCapUpp000005) + self.effSatUpp005030 = pcr.max(0., self.storUpp005030/ self.parameters.storCapUpp005030) + self.effSatLow030150 = pcr.max(0., self.storLow030150/ self.parameters.storCapLow030150) + self.effSatUpp000005 = pcr.min(1., self.effSatUpp000005) + self.effSatUpp005030 = pcr.min(1., self.effSatUpp005030) + self.effSatLow030150 = pcr.min(1., self.effSatLow030150) + + # matricSuction (m) + self.matricSuctionUpp000005 = self.parameters.airEntryValueUpp000005*(pcr.max(0.01,self.effSatUpp000005)**-self.parameters.poreSizeBetaUpp000005) + self.matricSuctionUpp005030 = self.parameters.airEntryValueUpp005030*(pcr.max(0.01,self.effSatUpp005030)**-self.parameters.poreSizeBetaUpp005030) + self.matricSuctionLow030150 = self.parameters.airEntryValueLow030150*(pcr.max(0.01,self.effSatLow030150)**-self.parameters.poreSizeBetaLow030150) + + # kUnsat (m.day-1): unsaturated hydraulic conductivity + self.kUnsatUpp000005 = pcr.max(0.,(self.effSatUpp000005**self.parameters.campbellBetaUpp000005)*self.parameters.kSatUpp000005) + self.kUnsatUpp005030 = pcr.max(0.,(self.effSatUpp005030**self.parameters.campbellBetaUpp005030)*self.parameters.kSatUpp005030) + self.kUnsatLow030150 = pcr.max(0.,(self.effSatLow030150**self.parameters.campbellBetaLow030150)*self.parameters.kSatLow030150) + + self.kUnsatUpp000005 = pcr.min(self.kUnsatUpp000005,self.parameters.kSatUpp000005) + self.kUnsatUpp005030 = pcr.min(self.kUnsatUpp005030,self.parameters.kSatUpp005030) + self.kUnsatLow030150 = pcr.min(self.kUnsatLow030150,self.parameters.kSatLow030150) + + # kThVert (m.day-1) = unsaturated conductivity capped at field capacity + # - exchange between layers capped at field capacity + # between Upp000005Upp005030 + self.kThVertUpp000005Upp005030 = pcr.min(\ + pcr.sqrt(self.kUnsatUpp000005*self.kUnsatUpp005030),\ + (self.kUnsatUpp000005*self.kUnsatUpp005030* \ + self.parameters.kUnsatAtFieldCapUpp000005*\ + self.parameters.kUnsatAtFieldCapUpp005030)**0.25) + # between Upp005030Low030150 + self.kThVertUpp005030Low030150 = pcr.min(\ + pcr.sqrt(self.kUnsatUpp005030*self.kUnsatLow030150),\ + (self.kUnsatUpp005030*self.kUnsatLow030150* \ + self.parameters.kUnsatAtFieldCapUpp005030*\ + self.parameters.kUnsatAtFieldCapLow030150)**0.25) + + # gradient for capillary rise (index indicating target store to its underlying store) + # between Upp000005Upp005030 + self.gradientUpp000005Upp005030 = pcr.max(0.,2.*\ + (self.matricSuctionUpp000005-self.matricSuctionUpp005030)/\ + (self.parameters.thickUpp000005+ self.parameters.thickUpp005030)-1.) + # between Upp005030Low030150 + self.gradientUpp005030Low030150 = pcr.max(0.,2.*\ + (self.matricSuctionUpp005030-self.matricSuctionLow030150)/\ + (self.parameters.thickUpp005030+ self.parameters.thickLow030150)-1.) + + # readily available water in the root zone (upper soil layers) + self.readAvlWater = \ + (pcr.max(0.,\ + self.effSatUpp000005 - self.parameters.effSatAtWiltPointUpp000005))*\ + (self.parameters.satVolMoistContUpp000005 - self.parameters.resVolMoistContUpp000005 )*\ + pcr.min(self.parameters.thickUpp000005,self.maxRootDepth) + \ + (pcr.max(0.,\ + self.effSatUpp005030 - self.parameters.effSatAtWiltPointUpp005030))*\ + (self.parameters.satVolMoistContUpp005030 - self.parameters.resVolMoistContUpp005030 )*\ + pcr.min(self.parameters.thickUpp005030,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp000005)) + \ + (pcr.max(0.,\ + self.effSatLow030150 - self.parameters.effSatAtWiltPointLow030150))*\ + (self.parameters.satVolMoistContLow030150 - self.parameters.resVolMoistContLow030150 )*\ + pcr.min(self.parameters.thickLow030150,\ + pcr.max(self.maxRootDepth-self.parameters.thickUpp005030,0.)) + + # RvB: initialize satAreaFrac + self.satAreaFrac= None + + def calculateWaterDemand(self, nonIrrGrossDemandDict, \ + swAbstractionFractionDict, \ + groundwater, \ + routing, \ + allocSegments, \ + currTimeStep, \ + desalinationWaterUse,\ + groundwater_pumping_region_ids,regionalAnnualGroundwaterAbstractionLimit): + + # irrigation water demand (unit: m/day) for paddy and non-paddy + self.irrGrossDemand = pcr.scalar(0.) + if (self.name == 'irrPaddy' or self.name == 'irr_paddy') and self.includeIrrigation: + self.irrGrossDemand = \ + pcr.ifthenelse(self.cropKC > 0.75, \ + pcr.max(0.0,self.minTopWaterLayer - \ + (self.topWaterLayer )), 0.) # a function of cropKC (evaporation and transpiration), + # topWaterLayer (water available in the irrigation field) + + if (self.name == 'irrNonPaddy' or self.name == 'irr_non_paddy' or self.name == "irr_non_paddy_crops") and self.includeIrrigation: + + #~ adjDeplFactor = \ + #~ pcr.max(0.1,\ + #~ pcr.min(0.8,(self.cropDeplFactor + \ + #~ 40.*(0.005-self.totalPotET)))) # from Wada et al. (2014) + adjDeplFactor = \ + pcr.max(0.1,\ + pcr.min(0.8,(self.cropDeplFactor + \ + 0.04*(5.-self.totalPotET*1000.)))) # original formula based on Allen et al. (1998) + # see: http://www.fao.org/docrep/x0490e/x0490e0e.htm# + # + #~ # alternative 1: irrigation demand (to fill the entire totAvlWater, maintaining the field capacity) - NOT USED + #~ self.irrGrossDemand = \ + #~ pcr.ifthenelse( self.cropKC > 0.20, \ + #~ pcr.ifthenelse( self.readAvlWater < \ + #~ adjDeplFactor*self.totAvlWater, \ + #~ pcr.max(0.0, self.totAvlWater-self.readAvlWater),0.),0.) # a function of cropKC and totalPotET (evaporation and transpiration), + #~ # readAvlWater (available water in the root zone) + + # alternative 2: irrigation demand (to fill the entire totAvlWater, maintaining the field capacity, + # but with the correction of totAvlWater based on the rooting depth) + # - as the proxy of rooting depth, we use crop coefficient + self.irrigation_factor = pcr.ifthenelse(self.cropKC > 0.0,\ + pcr.min(1.0, self.cropKC / 1.0), 0.0) + self.irrGrossDemand = \ + pcr.ifthenelse( self.cropKC > 0.20, \ + pcr.ifthenelse( self.readAvlWater < \ + adjDeplFactor*self.irrigation_factor*self.totAvlWater, \ + pcr.max(0.0, self.totAvlWater*self.irrigation_factor-self.readAvlWater),0.),0.) + + # irrigation demand is implemented only if there is deficit in transpiration and/or evaporation + deficit_factor = 1.00 + evaporationDeficit = pcr.max(0.0, (self.potBareSoilEvap + self.potTranspiration)*deficit_factor -\ + self.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True)) + transpirationDeficit = pcr.max(0.0, + self.potTranspiration*deficit_factor -\ + self.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True, returnTotalTranspirationOnly = True)) + deficit = pcr.max(evaporationDeficit, transpirationDeficit) + # + # treshold to initiate irrigation + deficit_treshold = 0.20 * self.totalPotET + need_irrigation = pcr.ifthenelse(deficit > deficit_treshold, pcr.boolean(1),\ + pcr.ifthenelse(self.soilWaterStorage == 0.000, pcr.boolean(1), pcr.boolean(0))) + need_irrigation = pcr.cover(need_irrigation, pcr.boolean(0.0)) + # + self.irrGrossDemand = pcr.ifthenelse(need_irrigation, self.irrGrossDemand, 0.0) + + # demand is limited by potential evaporation for the next coming days + # - objective: to avoid too high and unrealistic demand + max_irrigation_interval = 15.0 + min_irrigation_interval = 7.0 + irrigation_interval = pcr.min(max_irrigation_interval, \ + pcr.max(min_irrigation_interval, \ + pcr.ifthenelse(self.totalPotET > 0.0, \ + pcr.roundup((self.irrGrossDemand + pcr.max(self.readAvlWater, self.soilWaterStorage))/ self.totalPotET), 1.0))) + # - irrigation demand - limited by potential evaporation for the next coming days + self.irrGrossDemand = pcr.min(pcr.max(0.0,\ + self.totalPotET * irrigation_interval - pcr.max(self.readAvlWater, self.soilWaterStorage)),\ + self.irrGrossDemand) + + # assume that smart farmers do not irrigate higher than infiltration capacities + if self.numberOfLayers == 2: self.irrGrossDemand = pcr.min(self.irrGrossDemand, self.parameters.kSatUpp) + if self.numberOfLayers == 3: self.irrGrossDemand = pcr.min(self.irrGrossDemand, self.parameters.kSatUpp000005) + + # irrigation efficiency, minimum demand for start irrigating and maximum value to cap excessive demand + if self.includeIrrigation: + + # irrigation efficiency # TODO: Improve the concept of irrigation efficiency + self.irrigationEfficiencyUsed = pcr.min(1.0, pcr.max(0.10, self.irrigationEfficiency)) + # demand, including its inefficiency + self.irrGrossDemand = pcr.cover(self.irrGrossDemand / pcr.min(1.0, self.irrigationEfficiencyUsed), 0.0) + + # the following irrigation demand is not limited to available water + self.irrGrossDemand = pcr.ifthen(self.landmask, self.irrGrossDemand) + + # reduce irrGrossDemand by netLqWaterToSoil + self.irrGrossDemand = pcr.max(0.0, self.irrGrossDemand - self.netLqWaterToSoil) + + # minimum demand for start irrigating + minimum_demand = 0.005 # unit: m/day # TODO: set the minimum demand in the ini/configuration file. + if self.name == 'irrPaddy' or\ + self.name == 'irr_paddy': minimum_demand = pcr.min(self.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. + self.irrGrossDemand = pcr.ifthenelse(self.irrGrossDemand > minimum_demand, \ + self.irrGrossDemand , 0.0) + + maximum_demand = 0.025 # unit: m/day # TODO: set the maximum demand in the ini/configuration file. + if self.name == 'irrPaddy' or\ + self.name == 'irr_paddy': maximum_demand = pcr.min(self.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. + self.irrGrossDemand = pcr.min(maximum_demand, self.irrGrossDemand) + + # ignore small irrigation demand (less than 1 mm) + self.irrGrossDemand = pcr.rounddown( self.irrGrossDemand *1000.)/1000. + + # irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? + self.irrGrossDemand = pcr.ifthenelse(self.fracVegCover > 0.0, self.irrGrossDemand, 0.0) + + # total irrigation gross demand (m) per cover types (not limited by available water) + self.totalPotentialMaximumIrrGrossDemandPaddy = 0.0 + self.totalPotentialMaximumIrrGrossDemandNonPaddy = 0.0 + if self.name == 'irrPaddy' or self.name == 'irr_paddy': self.totalPotentialMaximumIrrGrossDemandPaddy = self.irrGrossDemand + if self.name == 'irrNonPaddy' or self.name == 'irr_non_paddy' or self.name == 'irr_non_paddy_crops': self.totalPotentialMaximumIrrGrossDemandNonPaddy = self.irrGrossDemand + + # non irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? + nonIrrGrossDemandDict['potential_demand']['domestic'] = pcr.ifthenelse(self.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] , 0.0) + nonIrrGrossDemandDict['potential_demand']['industry'] = pcr.ifthenelse(self.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['industry'] , 0.0) + nonIrrGrossDemandDict['potential_demand']['livestock'] = pcr.ifthenelse(self.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['livestock'], 0.0) + + # non irrigation water demand, including the livestock (not limited by available water) + self.nonIrrGrossDemand = nonIrrGrossDemandDict['potential_demand']['domestic'] +\ + nonIrrGrossDemandDict['potential_demand']['industry'] +\ + nonIrrGrossDemandDict['potential_demand']['livestock'] + + # total irrigation and livestock demand (not limited by available water) + totalIrrigationLivestockDemand = self.irrGrossDemand + nonIrrGrossDemandDict['potential_demand']['livestock'] + + # totalGrossDemand (m): irrigation and non irrigation (not limited by available water) - these values will not be reduced + self.totalPotentialMaximumGrossDemand = self.irrGrossDemand + self.nonIrrGrossDemand + # - irrigation (excluding livestock) + self.totalPotentialMaximumIrrGrossDemand = self.irrGrossDemand + # - non irrigation (including livestock) + self.totalPotentialMaximumNonIrrGrossDemand = self.nonIrrGrossDemand + + # the following value will be reduced by available/accesible water + self.totalPotentialGrossDemand = self.totalPotentialMaximumGrossDemand + + # Abstraction and Allocation of DESALINATED WATER + # ################################################################################################################## + # - desalination water to satisfy water demand + if self.usingAllocSegments: # using zone/segments at which networks are defined (as defined in the landSurface options) + # + logger.debug("Allocation of supply from desalination water.") + # + volDesalinationAbstraction, volDesalinationAllocation = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = self.totalPotentialGrossDemand*routing.cellArea,\ + available_water_volume = pcr.max(0.00, desalinationWaterUse*routing.cellArea),\ + allocation_zones = allocSegments,\ + zone_area = self.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + # + self.desalinationAbstraction = volDesalinationAbstraction / routing.cellArea + self.desalinationAllocation = volDesalinationAllocation / routing.cellArea + # + else: + # + logger.debug("Supply from desalination water is only for satisfying local demand (no network).") + self.desalinationAbstraction = pcr.min(desalinationWaterUse, self.totalPotentialGrossDemand) + self.desalinationAllocation = self.desalinationAbstraction + # + self.desalinationAbstraction = pcr.ifthen(self.landmask, self.desalinationAbstraction) + self.desalinationAllocation = pcr.ifthen(self.landmask, self.desalinationAllocation) + # ################################################################################################################## + # - end of Abstraction and Allocation of DESALINATED WATER + + + # water demand that have been satisfied (unit: m/day) - after desalination + ################################################################################################################################ + # - for irrigation (excluding livestock) + satisfiedIrrigationDemand = vos.getValDivZero(self.irrGrossDemand, self.totalPotentialGrossDemand) * self.desalinationAllocation + # - for domestic, industry and livestock + satisfiedNonIrrDemand = pcr.max(0.00, self.desalinationAllocation - satisfiedIrrigationDemand) + # - for domestic + satisfiedDomesticDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['domestic'], + self.totalPotentialMaximumNonIrrGrossDemand) + # - for industry + satisfiedIndustryDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['industry'], + self.totalPotentialMaximumNonIrrGrossDemand) + # - for livestock + satisfiedLivestockDemand = pcr.max(0.0, satisfiedNonIrrDemand - satisfiedDomesticDemand - satisfiedIndustryDemand) + + + # total remaining gross demand (m/day) after desalination + ################################################################################################################################ + self.totalGrossDemandAfterDesalination = pcr.max(0.0, self.totalPotentialGrossDemand - self.desalinationAllocation) + # the remaining water demand per sector + # - for domestic + remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # - for industry + remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # - for livestock + remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - total for livestock and irrigation + remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # - total for industrial and domestic (excluding livestock) + remainingIndustrialDomestic = pcr.max(0.0, self.totalGrossDemandAfterDesalination - remainingIrrigationLivestock) + + + # Abstraction and Allocation of SURFACE WATER + ############################################################################################################################## + # calculate the estimate of surface water demand (considering by swAbstractionFractionDict) + # - for industrial and domestic + swAbstractionFraction_industrial_domestic = pcr.min(swAbstractionFractionDict['max_for_non_irrigation'],\ + swAbstractionFractionDict['estimate']) + if swAbstractionFractionDict['non_irrigation'] is not None: + swAbstractionFraction_industrial_domestic = swAbstractionFractionDict['non_irrigation'] + + surface_water_demand_estimate = swAbstractionFraction_industrial_domestic * remainingIndustrialDomestic + # - for irrigation and livestock + surface_water_irrigation_demand_estimate = swAbstractionFractionDict['irrigation'] * remainingIrrigationLivestock + # - surface water source as priority if groundwater irrigation fraction is relatively low + surface_water_irrigation_demand_estimate = \ + pcr.ifthenelse(swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_maximize_irrigation_surface_water'],\ + remainingIrrigationLivestock, surface_water_irrigation_demand_estimate) + # - update estimate of surface water demand withdrawal (unit: m/day) + surface_water_demand_estimate += surface_water_irrigation_demand_estimate + # - prioritize surface water use in non productive aquifers that have limited groundwater supply + surface_water_demand_estimate = pcr.ifthenelse(groundwater.productive_aquifer, surface_water_demand_estimate,\ + pcr.max(0.0, remainingIrrigationLivestock - \ + pcr.min(groundwater.avgAllocationShort, groundwater.avgAllocation))) + # - maximize/optimize surface water use in areas with the overestimation of groundwater supply + surface_water_demand_estimate += pcr.max(0.0, pcr.max(groundwater.avgAllocationShort, groundwater.avgAllocation) -\ + (1.0 - swAbstractionFractionDict['irrigation']) * totalIrrigationLivestockDemand -\ + (1.0 - swAbstractionFraction_industrial_domestic) * (self.totalPotentialMaximumGrossDemand - totalIrrigationLivestockDemand)) + # + # total demand (unit: m/day) that should be allocated from surface water + # (corrected/limited by swAbstractionFractionDict and limited by the remaining demand) + surface_water_demand_estimate = pcr.min(self.totalGrossDemandAfterDesalination, surface_water_demand_estimate) + correctedRemainingIrrigationLivestock = pcr.min(surface_water_demand_estimate, remainingIrrigationLivestock) + correctedRemainingIndustrialDomestic = pcr.min(remainingIndustrialDomestic,\ + pcr.max(0.0, surface_water_demand_estimate - remainingIrrigationLivestock)) + correctedSurfaceWaterDemandEstimate = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic + surface_water_demand = correctedSurfaceWaterDemandEstimate + # + # if surface water abstraction as the first priority + if self.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination + # + if self.usingAllocSegments: # using zone/segment at which supply network is defined + # + logger.debug("Allocation of surface water abstraction.") + # + volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = surface_water_demand*routing.cellArea,\ + available_water_volume = pcr.max(0.00, routing.readAvlChannelStorage),\ + allocation_zones = allocSegments,\ + zone_area = self.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + + self.actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea + self.allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea + # + else: + logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") + self.actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ + surface_water_demand) # unit: m + self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m + # + self.actSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.actSurfaceWaterAbstract) + self.allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.allocSurfaceWaterAbstract) + ################################################################################################################################ + # - end of Abstraction and Allocation of SURFACE WATER + + + # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply + ################################################################################################################################ + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromSurfaceWater = self.allocSurfaceWaterAbstract * \ + vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedSurfaceWaterDemandEstimate) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromSurfaceWater = satisfiedIrrigationLivestockDemandFromSurfaceWater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract - satisfiedIrrigationDemandFromSurfaceWater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromSurfaceWater - \ + satisfiedIrrigationDemandFromSurfaceWater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract -\ + satisfiedIrrigationLivestockDemandFromSurfaceWater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + + + + ###################################################################################################################### + # water demand (unit: m) that must be satisfied by groundwater abstraction (not limited to available water) + self.potGroundwaterAbstract = pcr.max(0.0, self.totalGrossDemandAfterDesalination - self.allocSurfaceWaterAbstract) + ###################################################################################################################### + # water demand per sector + # - for domestic + remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # - for industry + remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # - for livestock + remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - total for livestock and irrigation + remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # - total for industrial and domestic (excluding livestock) + remainingIndustrialDomestic = remainingIndustry + remainingDomestic + + + + # Abstraction and Allocation of GROUNDWATER (fossil and non fossil) + ######################################################################################################################### + # estimating groundwater water demand: + # - demand for industrial and domestic sectors + # (all remaining demand for these sectors should be satisfied) + groundwater_demand_estimate = remainingIndustrialDomestic + # - demand for irrigation and livestock sectors + # (only part of them will be satisfied, as they may be too high due to the uncertainty in the irrigation scheme) + irrigationLivestockGroundwaterDemand = pcr.min(remainingIrrigationLivestock, \ + pcr.max(0.0, \ + (1.0 - swAbstractionFractionDict['irrigation'])*totalIrrigationLivestockDemand)) + groundwater_demand_estimate += irrigationLivestockGroundwaterDemand + + + ##################################################################################################### + # water demand that must be satisfied by groundwater abstraction (not limited to available water) + self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, groundwater_demand_estimate) + ##################################################################################################### + + # constraining groundwater abstraction with the regional annual pumping capacity + if groundwater.limitRegionalAnnualGroundwaterAbstraction: + + logger.debug('Total groundwater abstraction is limited by regional annual pumping capacity.') + + # estimate of total groundwater abstraction (m3) from the last 365 days: + tolerating_days = 0. + annualGroundwaterAbstraction = groundwater.avgAbstraction * routing.cellArea *\ + pcr.min(pcr.max(0.0, 365.0 - tolerating_days), routing.timestepsToAvgDischarge) + # total groundwater abstraction (m3) from the last 365 days at the regional scale + regionalAnnualGroundwaterAbstraction = pcr.areatotal(pcr.cover(annualGroundwaterAbstraction, 0.0), groundwater_pumping_region_ids) + + #~ # reduction factor to reduce groundwater abstraction/demand + #~ reductionFactorForPotGroundwaterAbstract = pcr.cover(\ + #~ pcr.ifthenelse(regionalAnnualGroundwaterAbstractionLimit > 0.0, + #~ pcr.max(0.000, regionalAnnualGroundwaterAbstractionLimit -\ + #~ regionalAnnualGroundwaterAbstraction) / + #~ regionalAnnualGroundwaterAbstractionLimit , 0.0), 0.0) + + #~ # reduced potential groundwater abstraction (after pumping capacity) + #~ self.potGroundwaterAbstract = pcr.min(1.00, reductionFactorForPotGroundwaterAbstract) * self.potGroundwaterAbstract + + #~ # alternative: reduced potential groundwater abstraction (after pumping capacity) and considering the average recharge (baseflow) + #~ potGroundwaterAbstract = pcr.min(1.00, reductionFactorForPotGroundwaterAbstract) * self.potGroundwaterAbstract + #~ self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, + #~ potGroundwaterAbstract + pcr.max(0.0, routing.avgBaseflow / routing.cellArea)) + + + + ################## NEW METHOD ################################################################################################################# + # the remaining pumping capacity (unit: m3) at the regional scale + remainingRegionalAnnualGroundwaterAbstractionLimit = pcr.max(0.0, regionalAnnualGroundwaterAbstractionLimit - \ + regionalAnnualGroundwaterAbstraction) + # considering safety factor (residence time in day-1) + remainingRegionalAnnualGroundwaterAbstractionLimit *= 0.33 + + # the remaining pumping capacity (unit: m3) limited by self.potGroundwaterAbstract (at the regional scale) + remainingRegionalAnnualGroundwaterAbstractionLimit = pcr.min(remainingRegionalAnnualGroundwaterAbstractionLimit,\ + pcr.areatotal(self.potGroundwaterAbstract * routing.cellArea, groundwater_pumping_region_ids)) + + # the remaining pumping capacity (unit: m3) at the pixel scale - downscaled using self.potGroundwaterAbstract + remainingPixelAnnualGroundwaterAbstractionLimit = remainingRegionalAnnualGroundwaterAbstractionLimit * \ + vos.getValDivZero(self.potGroundwaterAbstract * routing.cellArea, pcr.areatotal(self.potGroundwaterAbstract * routing.cellArea, groundwater_pumping_region_ids)) + + # reduced (after pumping capacity) potential groundwater abstraction/demand (unit: m) and considering the average recharge (baseflow) + self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, \ + remainingPixelAnnualGroundwaterAbstractionLimit/routing.cellArea + pcr.max(0.0, routing.avgBaseflow / routing.cellArea)) + ################## end of NEW METHOD (but still under development) ########################################################################################################## + + + + #~ # Shall we will always try to fulfil the industrial and domestic demand? + #~ self.potGroundwaterAbstract = pcr.max(remainingIndustrialDomestic, self.potGroundwaterAbstract) + + + else: + logger.debug('NO LIMIT for regional groundwater (annual) pumping. It may result too high groundwater abstraction.') + + + # Abstraction and Allocation of NON-FOSSIL GROUNDWATER + # ############################################################################################################################# + # available storGroundwater (non fossil groundwater) that can be accessed (unit: m) + readAvlStorGroundwater = pcr.cover(pcr.max(0.00, groundwater.storGroundwater), 0.0) + # - considering maximum daily groundwater abstraction + readAvlStorGroundwater = pcr.min(readAvlStorGroundwater, groundwater.maximumDailyGroundwaterAbstraction) + # - ignore groundwater storage in non-productive aquifer + readAvlStorGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, readAvlStorGroundwater, 0.0) + + # for non-productive aquifer, reduce readAvlStorGroundwater to the current recharge/baseflow rate + readAvlStorGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, \ + readAvlStorGroundwater, pcr.min(readAvlStorGroundwater, pcr.max(routing.avgBaseflow, 0.0))) + + # avoid the condition that the entire groundwater volume abstracted instantaneously + readAvlStorGroundwater *= 0.75 + + if groundwater.usingAllocSegments: + + logger.debug('Allocation of non fossil groundwater abstraction.') + + # TODO: considering aquifer productivity while doing the allocation (e.g. using aquifer transmissivity/conductivity) + + # non fossil groundwater abstraction and allocation in volume (unit: m3) + volActGroundwaterAbstract, volAllocGroundwaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = self.potGroundwaterAbstract*routing.cellArea,\ + available_water_volume = pcr.max(0.00, readAvlStorGroundwater*routing.cellArea),\ + allocation_zones = groundwater.allocSegments,\ + zone_area = groundwater.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + + # non fossil groundwater abstraction and allocation in meter + self.nonFossilGroundwaterAbs = volActGroundwaterAbstract / routing.cellArea + self.allocNonFossilGroundwater = volAllocGroundwaterAbstract/ routing.cellArea + + else: + + logger.debug('Non fossil groundwater abstraction is only for satisfying local demand.') + self.nonFossilGroundwaterAbs = pcr.min(readAvlStorGroundwater, self.potGroundwaterAbstract) + self.allocNonFossilGroundwater = self.nonFossilGroundwaterAbs + ################################################################################################################################ + # - end of Abstraction and Allocation of NON FOSSIL GROUNDWATER + + + ################################################################################################################################ + # variable to reduce capillary rise in order to ensure there is always enough water to supply non fossil groundwater abstraction + self.reducedCapRise = self.nonFossilGroundwaterAbs + # TODO: Check do we need this for runs with MODFLOW ??? + ################################################################################################################################ + + + + # water demand that have been satisfied (unit: m/day) - after desalination, surface water and non-fossil groundwater supply + ################################################################################################################################ + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromNonFossilGroundwater = self.allocNonFossilGroundwater * \ + vos.getValDivZero(irrigationLivestockGroundwaterDemand, groundwater_demand_estimate) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromNonFossilGroundwater = satisfiedIrrigationLivestockDemandFromNonFossilGroundwater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromNonFossilGroundwater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromNonFossilGroundwater = pcr.max(0.0, self.allocNonFossilGroundwater - satisfiedIrrigationLivestockDemandFromNonFossilGroundwater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromNonFossilGroundwater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromNonFossilGroundwater - \ + satisfiedIrrigationDemandFromNonFossilGroundwater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromNonFossilGroundwater = pcr.max(0.0, self.allocNonFossilGroundwater -\ + satisfiedIrrigationLivestockDemandFromNonFossilGroundwater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromNonFossilGroundwater * vos.getValDivZero(remainingDomestic, remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromNonFossilGroundwater * vos.getValDivZero(remainingIndustry, remainingIndustrialDomestic) + + + + ###################################################################################################################### + ###################################################################################################################### + # water demand that must be satisfied by fossil groundwater abstraction (unit: m, not limited to available water) + self.potFossilGroundwaterAbstract = pcr.max(0.0, self.potGroundwaterAbstract - \ + self.allocNonFossilGroundwater) + ###################################################################################################################### + ###################################################################################################################### + + + # For a run using MODFLOW, the concept of fossil groundwater abstraction is abandoned (self.limitAbstraction == True): + if groundwater.useMODFLOW or self.limitAbstraction: + logger.debug('Fossil groundwater abstractions are NOT allowed') + self.fossilGroundwaterAbstr = pcr.scalar(0.0) + self.fossilGroundwaterAlloc = pcr.scalar(0.0) + + + # Abstraction and Allocation of FOSSIL GROUNDWATER + # ##################################################################################################################################### + + if self.limitAbstraction == False: # TODO: For runs without any water use, we can exclude this. + + logger.debug('Fossil groundwater abstractions are allowed.') + + # the remaining water demand (m/day) for all sectors - NOT limited to self.potFossilGroundwaterAbstract + ##################################################################################################################### + # - for domestic + remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # - for industry + remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # - for livestock + remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - total for livestock and irrigation + remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # - total for industrial and domestic (excluding livestock) + remainingIndustrialDomestic = remainingIndustry + remainingDomestic + # - remaining total demand + remainingTotalDemand = remainingIrrigationLivestock + remainingIndustrialDomestic + + + # constraining fossil groundwater abstraction with regional pumping capacity + if groundwater.limitRegionalAnnualGroundwaterAbstraction and self.limitAbstraction == False: + + logger.debug('Fossil groundwater abstraction is allowed, BUT limited by the regional annual pumping capacity.') + + # estimate of total groundwater abstraction (m3) from the last 365 days: + # - considering abstraction from non fossil groundwater + annualGroundwaterAbstraction += self.nonFossilGroundwaterAbs*routing.cellArea + # at the regional scale + regionalAnnualGroundwaterAbstraction = pcr.areatotal(pcr.cover(annualGroundwaterAbstraction, 0.0), groundwater_pumping_region_ids) + + # fossil groundwater demand/asbtraction reduced by pumping capacity (unit: m/day) + # - safety factor to avoid the remaining limit abstracted at once (due to overestimation of groundwater demand) + safety_factor_for_fossil_abstraction = 1.00 + self.potFossilGroundwaterAbstract *= pcr.min(1.00,\ + pcr.cover(\ + pcr.ifthenelse(regionalAnnualGroundwaterAbstractionLimit > 0.0, + pcr.max(0.000, regionalAnnualGroundwaterAbstractionLimit * safety_factor_for_fossil_abstraction-\ + regionalAnnualGroundwaterAbstraction) / + regionalAnnualGroundwaterAbstractionLimit , 0.0), 0.0)) + + #~ # Shall we will always try to fulfil the remaining industrial and domestic demand? + #~ self.potFossilGroundwaterAbstract = pcr.max(remainingIndustrialDomestic, self.potFossilGroundwaterAbstract) + + + + if self.limitAbstraction == False: # TODO: For runs without any water use, we can exclude this. + + + ############################################################################################################################### + # estimate the remaining total demand (unit: m/day) LIMITED to self.potFossilGroundwaterAbstract + ############################################################################################################################### + + correctedRemainingTotalDemand = pcr.min(self.potFossilGroundwaterAbstract, remainingTotalDemand) + + # the remaining industrial and domestic demand and livestock (unit: m/day) limited to self.potFossilGroundwaterAbstract + # - no correction, we will always try to fulfil these demands + correctedRemainingIndustrialDomesticLivestock = pcr.min(remainingIndustrialDomestic + remainingLivestock, correctedRemainingTotalDemand) + + # the remaining irrigation demand limited to self.potFossilGroundwaterAbstract + correctedRemainingIrrigation = pcr.min(remainingIrrigation, \ + pcr.max(0.0, correctedRemainingTotalDemand - correctedRemainingIndustrialDomesticLivestock)) + # - ignore small irrigation demand (less than 1 mm) + correctedRemainingIrrigation = pcr.rounddown(correctedRemainingIrrigation*1000.)/1000. + + # the (corrected) remaining total demand (limited to self.potFossilGroundwaterAbstract) + correctedRemainingTotalDemand = correctedRemainingIndustrialDomesticLivestock + correctedRemainingIrrigation + + # the (corrected) remaining industrial and domestic demand (excluding livestock) + correctedRemainingIndustrialDomestic = pcr.min(remainingIndustrialDomestic, correctedRemainingTotalDemand) + + # the remaining irrigation and livestock water demand limited to self.potFossilGroundwaterAbstract + correctedRemainingIrrigationLivestock = pcr.min(remainingIrrigationLivestock, \ + pcr.max(0.0, correctedRemainingTotalDemand - correctedRemainingIndustrialDomestic)) + + # the (corrected) remaining total demand (unit: m/day) limited to self.potFossilGroundwaterAbstract + correctedRemainingTotalDemand = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic + + # TODO: Do the water balance check: correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic <= self.potFossilGroundwaterAbstract + + # constrain the irrigation groundwater demand with groundwater source fraction + correctedRemainingIrrigationLivestock = pcr.min((1.0 - swAbstractionFractionDict['irrigation']) * remainingIrrigationLivestock,\ + correctedRemainingIrrigationLivestock) + correctedRemainingIrrigationLivestock = pcr.max(0.0,\ + pcr.min(correctedRemainingIrrigationLivestock,\ + pcr.max(0.0, totalIrrigationLivestockDemand) * (1.0 - swAbstractionFractionDict['irrigation']) - satisfiedIrrigationDemandFromNonFossilGroundwater)) + + # ignore fossil groundwater abstraction in irrigation areas dominated by swAbstractionFractionDict['irrigation'] + correctedRemainingIrrigationLivestock = pcr.ifthenelse(\ + swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_minimize_fossil_groundwater_irrigation'], 0.0,\ + correctedRemainingIrrigationLivestock) + + # reduce the fossil irrigation and livestock demands with enough supply of non fossil groundwater (in order to minimize unrealistic areas of fossil groundwater abstraction) + # - supply from the average recharge (baseflow) and non fossil groundwater allocation + nonFossilGroundwaterSupply = pcr.max(pcr.max(0.0, routing.avgBaseflow) / routing.cellArea, \ + groundwater.avgNonFossilAllocationShort, groundwater.avgNonFossilAllocation) + # - irrigation supply from the non fossil groundwater + nonFossilIrrigationGroundwaterSupply = nonFossilGroundwaterSupply * vos.getValDivZero(remainingIrrigationLivestock, remainingTotalDemand) + # - the corrected/reduced irrigation and livestock demand + correctedRemainingIrrigationLivestock = pcr.max(0.0, correctedRemainingIrrigationLivestock - nonFossilIrrigationGroundwaterSupply) + + # the corrected remaining total demand (unit: m/day) + correctedRemainingTotalDemand = correctedRemainingIndustrialDomestic + correctedRemainingIrrigationLivestock + + ############################################################################################################################### + + # water demand that must be satisfied by fossil groundwater abstraction + self.potFossilGroundwaterAbstract = pcr.min(self.potFossilGroundwaterAbstract, correctedRemainingTotalDemand) + + if groundwater.limitFossilGroundwaterAbstraction == False and self.limitAbstraction == False: + + # Note: If limitFossilGroundwaterAbstraction == False, + # allocation of fossil groundwater abstraction is not needed. + msg = 'Fossil groundwater abstractions are without limit for satisfying local demand. ' + msg = 'Allocation for fossil groundwater abstraction is NOT needed/implemented. ' + msg += 'However, the fossil groundwater abstraction rate still consider the maximumDailyGroundwaterAbstraction.' + logger.debug(msg) + + # fossil groundwater abstraction (unit: m/day) + self.fossilGroundwaterAbstr = self.potFossilGroundwaterAbstract + self.fossilGroundwaterAbstr = \ + pcr.min(\ + pcr.max(0.0, groundwater.maximumDailyGroundwaterAbstraction - self.nonFossilGroundwaterAbs), self.fossilGroundwaterAbstr) + + # fossil groundwater allocation (unit: m/day) + self.fossilGroundwaterAlloc = self.fossilGroundwaterAbstr + + if groundwater.limitFossilGroundwaterAbstraction and self.limitAbstraction == False: + + logger.debug('Fossil groundwater abstractions are allowed, but with limit.') + + # accesible fossil groundwater (unit: m/day) + readAvlFossilGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, groundwater.storGroundwaterFossil, 0.0) + # - residence time (day-1) or safety factor (to avoid 'unrealistic' zero fossil groundwater) + readAvlFossilGroundwater *= 0.10 + # - considering maximum daily groundwater abstraction + readAvlFossilGroundwater = pcr.min(readAvlFossilGroundwater, groundwater.maximumDailyFossilGroundwaterAbstraction, \ + pcr.max(0.0, groundwater.maximumDailyGroundwaterAbstraction - self.nonFossilGroundwaterAbs)) + readAvlFossilGroundwater = pcr.max(pcr.cover(readAvlFossilGroundwater, 0.0), 0.0) + + if groundwater.usingAllocSegments: + + logger.debug('Allocation of fossil groundwater abstraction.') + + # TODO: considering aquifer productivity while doing the allocation. + + # fossil groundwater abstraction and allocation in volume (unit: m3) + volActGroundwaterAbstract, volAllocGroundwaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = self.potFossilGroundwaterAbstract*routing.cellArea,\ + available_water_volume = pcr.max(0.00, readAvlFossilGroundwater*routing.cellArea),\ + allocation_zones = groundwater.allocSegments,\ + zone_area = groundwater.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + + # fossil groundwater abstraction and allocation in meter + self.fossilGroundwaterAbstr = volActGroundwaterAbstract /routing.cellArea + self.fossilGroundwaterAlloc = volAllocGroundwaterAbstract/routing.cellArea + + else: + + logger.debug('Fossil groundwater abstraction is only for satisfying local demand. NO Allocation for fossil groundwater abstraction.') + + self.fossilGroundwaterAbstr = pcr.min(pcr.max(0.0, readAvlFossilGroundwater), self.potFossilGroundwaterAbstract) + self.fossilGroundwaterAlloc = self.fossilGroundwaterAbstr + + + # water demand that have been satisfied (m/day) - after desalination, surface water, non fossil groundwater & fossil groundwater + ################################################################################################################################ + + # from fossil groundwater, we should prioritize domestic and industrial water demand + prioritizeFossilGroundwaterForDomesticIndutrial = False # TODO: Define this in the configuration file. + + if prioritizeFossilGroundwaterForDomesticIndutrial: + + # - first priority: for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromFossilGroundwater = pcr.min(self.fossilGroundwaterAlloc, \ + remainingIndustrialDomestic) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + # - for irrigation and livestock demand + satisfiedIrrigationLivestockDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - \ + satisfiedIndustrialDomesticDemandFromFossilGroundwater) + # - for irrigation + satisfiedIrrigationDemand += satisfiedIrrigationLivestockDemandFromFossilGroundwater * vos.getValDivZero(remainingIrrigation, \ + remainingIrrigationLivestock) + # - for livestock + satisfiedLivestockDemand += satisfiedIrrigationLivestockDemandFromFossilGroundwater * vos.getValDivZero(remainingLivestock, \ + remainingIrrigationLivestock) + + else: + + # Distribute fossil water proportionaly based on the amount of each sector + + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromFossilGroundwater = self.fossilGroundwaterAlloc * \ + vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedRemainingTotalDemand) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromFossilGroundwater = satisfiedIrrigationLivestockDemandFromFossilGroundwater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromFossilGroundwater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - satisfiedIrrigationDemandFromFossilGroundwater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromFossilGroundwater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromFossilGroundwater - \ + satisfiedIrrigationDemandFromFossilGroundwater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - \ + satisfiedIrrigationLivestockDemandFromFossilGroundwater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + + # water demand limited to available/allocated water + self.totalPotentialGrossDemand = self.fossilGroundwaterAlloc +\ + self.allocNonFossilGroundwater +\ + self.allocSurfaceWaterAbstract +\ + self.desalinationAllocation + + # total groundwater abstraction and allocation (unit: m/day) + self.totalGroundwaterAllocation = self.allocNonFossilGroundwater + self.fossilGroundwaterAlloc + self.totalGroundwaterAbstraction = self.fossilGroundwaterAbstr + self.nonFossilGroundwaterAbs + + # irrigation water demand (excluding livestock) limited to available/allocated water (unit: m/day) + self.irrGrossDemand = satisfiedIrrigationDemand # not including livestock + + # irrigation gross demand (m) per cover type (limited by available water) + self.irrGrossDemandPaddy = 0.0 + self.irrGrossDemandNonPaddy = 0.0 + if self.name == 'irrPaddy' or self.name == "irr_paddy": self.irrGrossDemandPaddy = self.irrGrossDemand + if self.name == 'irrNonPaddy' or self.name == "irr_non_paddy" or self.name == "irr_non_paddy_crops": self.irrGrossDemandNonPaddy = self.irrGrossDemand + + # non irrigation water demand (including livestock) limited to available/allocated water (unit: m/day) + self.nonIrrGrossDemand = pcr.max(0.0, \ + self.totalPotentialGrossDemand - self.irrGrossDemand) # livestock, domestic and industry + self.domesticWaterWithdrawal = satisfiedDomesticDemand + self.industryWaterWithdrawal = satisfiedIndustryDemand + self.livestockWaterWithdrawal = satisfiedLivestockDemand + + # return flow (unit: m/day) from non irrigation withdrawal (from domestic, industry and livestock) + self.nonIrrReturnFlow = nonIrrGrossDemandDict['return_flow_fraction']['domestic'] * self.domesticWaterWithdrawal +\ + nonIrrGrossDemandDict['return_flow_fraction']['industry'] * self.industryWaterWithdrawal +\ + nonIrrGrossDemandDict['return_flow_fraction']['livestock']* self.livestockWaterWithdrawal + # - ignore very small return flow (less than 0.1 mm) + self.nonIrrReturnFlow = pcr.rounddown(self.nonIrrReturnFlow * 10000.)/10000. + self.nonIrrReturnFlow = pcr.min(self.nonIrrReturnFlow, self.nonIrrGrossDemand) + + if self.debugWaterBalance: + vos.waterBalanceCheck([self.irrGrossDemand,\ + self.nonIrrGrossDemand],\ + [self.totalPotentialGrossDemand],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)] ,\ + 'waterAllocationForAllSectors',True,\ + currTimeStep.fulldate,threshold=1e-4) + vos.waterBalanceCheck([self.domesticWaterWithdrawal,\ + self.industryWaterWithdrawal,\ + self.livestockWaterWithdrawal],\ + [self.nonIrrGrossDemand],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)] ,\ + 'waterAllocationForNonIrrigationSectors',True,\ + currTimeStep.fulldate,threshold=1e-4) + vos.waterBalanceCheck([self.irrGrossDemand,\ + self.domesticWaterWithdrawal,\ + self.industryWaterWithdrawal,\ + self.livestockWaterWithdrawal],\ + [self.totalPotentialGrossDemand],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)] ,\ + 'waterAllocationPerSector',True,\ + currTimeStep.fulldate,threshold=1e-4) + + # TODO: Perform water balance checks for all sources: desalination, surface water, non-fossil groundwater and fossil groundwater + + + + + + def calculateDirectRunoff(self): + + # topWaterLater is partitioned into directRunoff (and infiltration) + self.directRunoff = self.improvedArnoScheme(\ + iniWaterStorage = self.soilWaterStorage, \ + inputNetLqWaterToSoil = self.topWaterLayer, \ + directRunoffReductionMethod = self.improvedArnoSchemeMethod) + self.directRunoff = pcr.min(self.topWaterLayer, self.directRunoff) + + # Yet, we minimize directRunoff in the irrigation areas: + if self.name.startswith('irr') and self.includeIrrigation: self.directRunoff = pcr.scalar(0.0) + + # update topWaterLayer (above soil) after directRunoff + self.topWaterLayer = pcr.max(0.0, self.topWaterLayer - self.directRunoff) + + def improvedArnoScheme(self, iniWaterStorage, inputNetLqWaterToSoil, directRunoffReductionMethod = "Default"): + + # arnoBeta = BCF = b coefficient of soil water storage capacity distribution + # + # WMIN = root zone water storage capacity, minimum values + # WMAX = root zone water storage capacity, area-averaged values + # W = actual water storage in root zone + # WRANGE = WMAX - WMIN + # DW = WMAX-W + # WFRAC = DW/WRANGE ; WFRAC capped at 1 + # WFRACB = DW/WRANGE raised to the power (1/(b+1)) + # SATFRAC = fractional saturated area + # WACT = actual water storage within rootzone + + self.satAreaFracOld = self.satAreaFrac + + Pn = iniWaterStorage + \ + inputNetLqWaterToSoil # Pn = W[TYPE]+Pn; + Pn = Pn - pcr.max(self.rootZoneWaterStorageMin, \ + iniWaterStorage) # Pn = Pn-max(WMIN[TYPE],W[TYPE]); + soilWaterStorage = pcr.ifthenelse(Pn < 0.,\ + self.rootZoneWaterStorageMin+Pn, \ + pcr.max(iniWaterStorage,self.rootZoneWaterStorageMin)) # W[TYPE]= if(Pn<0,WMIN[TYPE]+Pn,max(W[TYPE],WMIN[TYPE])); + Pn = pcr.max(0.,Pn) # Pn = max(0,Pn); + # + DW = pcr.max(0.0,self.parameters.rootZoneWaterStorageCap - \ + soilWaterStorage) # DW = max(0,WMAX[TYPE]-W[TYPE]); + + #~ WFRAC = pcr.min(1.0,DW/self.rootZoneWaterStorageRange) # WFRAC = min(1,DW/WRANGE[TYPE]); + # modified by Edwin ; to solve problems with rootZoneWaterStorageRange = 0.0 + WFRAC = pcr.ifthenelse(self.rootZoneWaterStorageRange > 0.0, pcr.min(1.0,DW/self.rootZoneWaterStorageRange), 1.0) + + self.WFRACB = WFRAC**(1./(1.+self.arnoBeta)) # WFRACB = WFRAC**(1/(1+BCF[TYPE])); + # + self.satAreaFrac = pcr.ifthenelse(self.WFRACB > 0.,\ + 1.-self.WFRACB**self.arnoBeta,\ + 1.) # SATFRAC_L = if(WFRACB>0,1-WFRACB**BCF[TYPE],1); + # make sure that 0.0 <= satAreaFrac <= 1.0 + self.satAreaFrac = pcr.min(self.satAreaFrac, 1.0) + self.satAreaFrac = pcr.max(self.satAreaFrac, 0.0) + + actualW = (self.arnoBeta+1.0)*self.parameters.rootZoneWaterStorageCap - \ + self.arnoBeta*self.rootZoneWaterStorageMin - \ + (self.arnoBeta+1.0)*self.rootZoneWaterStorageRange*self.WFRACB + # WACT_L = (BCF[TYPE]+1)*WMAX[TYPE]- BCF[TYPE]*WMIN[TYPE]- (BCF[TYPE]+1)*WRANGE[TYPE]*WFRACB; + + + directRunoffReduction = pcr.scalar(0.0) # as in the "Original" work of van Beek et al. (2011) + if directRunoffReductionMethod == "Default": + if self.numberOfLayers == 2: directRunoffReduction = pcr.min(self.kUnsatLow,\ + pcr.sqrt(self.kUnsatLow*self.parameters.kUnsatAtFieldCapLow)) + if self.numberOfLayers == 3: directRunoffReduction = pcr.min(self.kUnsatLow030150,\ + pcr.sqrt(self.kUnsatLow030150*self.parameters.kUnsatAtFieldCapLow030150)) + # Rens: # In order to maintain full saturation and + # continuous groundwater recharge/percolation, + # the amount of directRunoff may be reduced. + # In this case, this reduction is estimated + # based on (for two layer case) percLow = pcr.min(KUnSatLow,\ + # pcr.sqrt(self.parameters.KUnSatFC2*KUnSatLow)) + + if directRunoffReductionMethod == "Modified": + if self.numberOfLayers == 2: directRunoffReduction = pcr.min(self.kUnsatLow,\ + pcr.sqrt(self.kUnsatLow*self.parameters.kUnsatAtFieldCapLow)) + if self.numberOfLayers == 3: directRunoffReduction = pcr.min(self.kUnsatLow030150,\ + pcr.sqrt(self.kUnsatLow030150*self.parameters.kUnsatAtFieldCapLow030150)) + # the reduction of directRunoff (preferential flow groundwater) + # is only introduced if the soilWaterStorage near its saturation + # - this is in order to maintain the saturation + saturation_treshold = 0.999 + directRunoffReduction = pcr.ifthenelse(vos.getValDivZero(soilWaterStorage,self.parameters.rootZoneWaterStorageCap) > saturation_treshold, directRunoffReduction, 0.0) + + # directRunoff + condition = (self.arnoBeta+pcr.scalar(1.))*self.rootZoneWaterStorageRange* self.WFRACB + directRunoff = pcr.max(0.0, \ + Pn -\ + (self.parameters.rootZoneWaterStorageCap+directRunoffReduction-soilWaterStorage) + \ + pcr.ifthenelse(Pn >= condition, + pcr.scalar(0.0), \ + self.rootZoneWaterStorageRange*(self.WFRACB-\ + Pn / ((self.arnoBeta+1.)*\ + self.rootZoneWaterStorageRange))**(self.arnoBeta+1.))) + # Q1_L[TYPE]= max(0,Pn-(WMAX[TYPE]+P2_L[TYPE]-W[TYPE])+ + # if(Pn>=(BCF[TYPE]+1)*WRANGE[TYPE]*WFRACB, 0, + # WRANGE[TYPE]*(WFRACB-Pn/((BCF[TYPE]+1)*WRANGE[TYPE]))**(BCF[TYPE]+1))); #* + # make sure that there is always value + directRunoff = pcr.cover(directRunoff, 0.0) + + return directRunoff + + def calculateOpenWaterEvap(self): + + # update topWaterLayer (above soil) + # - with netLqWaterToSoil and irrGrossDemand + self.topWaterLayer += pcr.max(0.,self.netLqWaterToSoil + self.irrGrossDemand) + + # potential evaporation for openWaterEvap + remainingPotETP = self.potBareSoilEvap + self.potTranspiration # Edwin's principle: LIMIT = self.potBareSoilEvap +self.potTranspiration + # remainingPotETP = self.totalPotET # DW, RvB, and YW use self.totalPotETP + + # openWaterEvap is ONLY for evaporation from paddy field areas + self.openWaterEvap = pcr.spatial(pcr.scalar(0.)) + + if self.name == 'irrPaddy' or self.name == "irr_paddy": # only open water evaporation from the paddy field + self.openWaterEvap = \ + pcr.min(\ + pcr.max(0.,self.topWaterLayer), remainingPotETP) + + # update potBareSoilEvap & potTranspiration (after openWaterEvap) + # - CHECK; WHY DO WE USE COVER ABOVE? Edwin replaced them using the following lines: + self.potBareSoilEvap = pcr.cover(\ + pcr.max(0.0, self.potBareSoilEvap -\ + vos.getValDivZero(self.potBareSoilEvap, remainingPotETP)*self.openWaterEvap ), 0.0) + self.potTranspiration = pcr.cover(\ + pcr.max(0.0, self.potTranspiration -\ + vos.getValDivZero(self.potTranspiration, remainingPotETP)*self.openWaterEvap), 0.0) + + # update top water layer after openWaterEvap + self.topWaterLayer = pcr.max(0.,self.topWaterLayer - self.openWaterEvap) + + def calculateInfiltration(self): + + # infiltration, limited with KSat1 and available water in topWaterLayer + if self.numberOfLayers == 2: + self.infiltration = pcr.min(self.topWaterLayer,self.parameters.kSatUpp) # P0_L = min(P0_L,KS1*Duration*timeslice()); + + if self.numberOfLayers == 3: + self.infiltration = pcr.min(self.topWaterLayer,self.parameters.kSatUpp000005) # P0_L = min(P0_L,KS1*Duration*timeslice()); + + # for paddy, infiltration should consider percolation losses + if (self.name == 'irrPaddy' or self.name == "irr_paddy") and self.includeIrrigation: + infiltration_loss = pcr.max(self.design_percolation_loss, + ((1./self.irrigationEfficiencyUsed) - 1.) * self.topWaterLayer) + self.infiltration = pcr.min(infiltration_loss, self.infiltration) + + # update top water layer after infiltration + self.topWaterLayer = pcr.max(0.0,\ + self.topWaterLayer - self.infiltration) + + # release excess topWaterLayer above minTopWaterLayer as additional direct runoff + self.directRunoff += pcr.max(0.0,\ + self.topWaterLayer - self.minTopWaterLayer) + + # update topWaterLayer after additional direct runoff + self.topWaterLayer = pcr.min( self.topWaterLayer , \ + self.minTopWaterLayer) + + def estimateTranspirationAndBareSoilEvap(self, returnTotalEstimation = False, returnTotalTranspirationOnly = False): + + # TRANSPIRATION + # + # - fractions for distributing transpiration (based on rott fraction and actual layer storages) + # + if self.numberOfLayers == 2: + dividerTranspFracs = pcr.max( 1e-9, self.adjRootFrUpp*self.storUpp +\ + self.adjRootFrLow*self.storLow ) + transpFracUpp = \ + pcr.ifthenelse((self.storUpp + self.storLow) > 0.,\ + self.adjRootFrUpp*self.storUpp/ dividerTranspFracs, \ + self.adjRootFrUpp) + transpFracLow = \ + pcr.ifthenelse((self.storUpp + self.storLow) > 0.,\ + self.adjRootFrLow*self.storLow/ dividerTranspFracs, \ + self.adjRootFrLow) # WF1= if((S1_L[TYPE]+S2_L[TYPE])>0,RFW1[TYPE]*S1_L[TYPE]/ + # max(1e-9,RFW1[TYPE]*S1_L[TYPE]+RFW2[TYPE]*S2_L[TYPE]),RFW1[TYPE]); + # WF2= if((S1_L[TYPE]+S2_L[TYPE])>0,RFW2[TYPE]*S2_L[TYPE]/ + # max(1e-9,RFW1[TYPE]*S1_L[TYPE]+RFW2[TYPE]*S2_L[TYPE]),RFW2[TYPE]); + if self.numberOfLayers == 3: + dividerTranspFracs = pcr.max( 1e-9, self.adjRootFrUpp000005*self.storUpp000005 +\ + self.adjRootFrUpp005030*self.storUpp005030 +\ + self.adjRootFrLow030150*self.storLow030150) + transpFracUpp000005 = \ + pcr.ifthenelse((self.storUpp000005 + \ + self.storUpp005030 + \ + self.storLow030150) > 0.,\ + self.adjRootFrUpp000005*self.storUpp000005/ dividerTranspFracs, \ + self.adjRootFrUpp000005) + transpFracUpp005030 = \ + pcr.ifthenelse((self.storUpp000005 + \ + self.storUpp005030 + \ + self.storLow030150) > 0.,\ + self.adjRootFrUpp005030*self.storUpp005030/ dividerTranspFracs, \ + self.adjRootFrUpp005030) + transpFracLow030150 = \ + pcr.ifthenelse((self.storUpp000005 + \ + self.storUpp005030 + \ + self.storLow030150) > 0.,\ + self.adjRootFrLow030150*self.storLow030150/ dividerTranspFracs, \ + self.adjRootFrLow030150) + + relActTranspiration = pcr.scalar(1.0) # no reduction in case of returnTotalEstimation + if returnTotalEstimation == False: + # reduction factor for transpiration + # + # - relActTranspiration = fraction actual transpiration over potential transpiration + relActTranspiration = (self.parameters.rootZoneWaterStorageCap + \ + self.arnoBeta*self.rootZoneWaterStorageRange*(1.- \ + (1.+self.arnoBeta)/self.arnoBeta*self.WFRACB)) / \ + (self.parameters.rootZoneWaterStorageCap + \ + self.arnoBeta*self.rootZoneWaterStorageRange*(1.- self.WFRACB)) # original Rens's line: + # FRACTA[TYPE] = (WMAX[TYPE]+BCF[TYPE]*WRANGE[TYPE]*(1-(1+BCF[TYPE])/BCF[TYPE]*WFRACB))/ + # (WMAX[TYPE]+BCF[TYPE]*WRANGE[TYPE]*(1-WFRACB)); + relActTranspiration = (1.-self.satAreaFrac) / \ + (1.+(pcr.max(0.01,relActTranspiration)/self.effSatAt50)**\ + (self.effPoreSizeBetaAt50*pcr.scalar(-3.0))) # original Rens's line: + # FRACTA[TYPE] = (1-SATFRAC_L)/(1+(max(0.01,FRACTA[TYPE])/THEFF_50[TYPE])**(-3*BCH_50)); + relActTranspiration = pcr.max(0.0, relActTranspiration) + relActTranspiration = pcr.min(1.0, relActTranspiration) + + # an idea by Edwin - 23 March 2015: no transpiration reduction in irrigated areas: + if self.name.startswith('irr') and self.includeIrrigation: relActTranspiration = pcr.scalar(1.0) + + + #~ ####################################################################################################################################### + #~ # estimates of actual transpiration fluxes - OLD METHOD (not used anymore, after Rens provided his original script, 30 July 2015) + #~ if self.numberOfLayers == 2: + #~ actTranspiUpp = \ + #~ relActTranspiration*transpFracUpp*self.potTranspiration + #~ actTranspiLow = \ + #~ relActTranspiration*transpFracLow*self.potTranspiration + #~ if self.numberOfLayers == 3: + #~ actTranspiUpp000005 = \ + #~ relActTranspiration*transpFracUpp000005*self.potTranspiration + #~ actTranspiUpp005030 = \ + #~ relActTranspiration*transpFracUpp005030*self.potTranspiration + #~ actTranspiLow030150 = \ + #~ relActTranspiration*transpFracLow030150*self.potTranspiration + #~ ####################################################################################################################################### + + + # partitioning potential tranpiration (based on Rens's oldcalc script provided 30 July 2015) + if self.numberOfLayers == 2: + potTranspirationUpp = pcr.min(transpFracUpp*self.potTranspiration, self.potTranspiration) + potTranspirationLow = pcr.max(0.0, self.potTranspiration - potTranspirationUpp) + if self.numberOfLayers == 3: + potTranspirationUpp000005 = pcr.min(transpFracUpp000005*self.potTranspiration, self.potTranspiration) + potTranspirationUpp005030 = pcr.min(transpFracUpp005030*self.potTranspiration, pcr.max(0.0, self.potTranspiration - potTranspirationUpp000005)) + potTranspirationLow030150 = pcr.max(0.0, self.potTranspiration - potTranspirationUpp000005 - potTranspirationUpp005030) + + # estimate actual transpiration fluxes + if self.numberOfLayers == 2: + actTranspiUpp = pcr.cover(relActTranspiration*potTranspirationUpp, 0.0) + actTranspiLow = pcr.cover(relActTranspiration*potTranspirationLow, 0.0) + if self.numberOfLayers == 3: + actTranspiUpp000005 = pcr.cover(relActTranspiration*potTranspirationUpp000005, 0.0) + actTranspiUpp005030 = pcr.cover(relActTranspiration*potTranspirationUpp005030, 0.0) + actTranspiLow030150 = pcr.cover(relActTranspiration*potTranspirationLow030150, 0.0) + + + # BARE SOIL EVAPORATION + # + # actual bare soil evaporation (potential) # no reduction in case of returnTotalEstimation + actBareSoilEvap = self.potBareSoilEvap + if self.numberOfLayers == 2 and returnTotalEstimation == False: + actBareSoilEvap = self.satAreaFrac * pcr.min(\ + self.potBareSoilEvap,self.parameters.kSatUpp) + \ + (1.-self.satAreaFrac)* pcr.min(\ + self.potBareSoilEvap,self.kUnsatUpp) # ES_a[TYPE] = SATFRAC_L *min(ES_p[TYPE],KS1[TYPE]*Duration*timeslice())+ + # (1-SATFRAC_L)*min(ES_p[TYPE],KTHEFF1*Duration*timeslice()); + if self.numberOfLayers == 3 and returnTotalEstimation == False: + actBareSoilEvap = self.satAreaFrac * pcr.min(\ + self.potBareSoilEvap,self.parameters.kSatUpp000005) + \ + (1.-self.satAreaFrac)* pcr.min(\ + self.potBareSoilEvap,self.kUnsatUpp000005) + actBareSoilEvap = pcr.max(0.0, actBareSoilEvap) + actBareSoilEvap = pcr.min(actBareSoilEvap,self.potBareSoilEvap) + actBareSoilEvap = pcr.cover(actBareSoilEvap, 0.0) + + # no bare soil evaporation in the inundated paddy field + if self.name == 'irrPaddy' or self.name == "irr_paddy": + # no bare soil evaporation if topWaterLayer is above treshold + #~ treshold = 0.0005 # unit: m ; + treshold = self.potBareSoilEvap + self.potTranspiration # an idea by Edwin on 23 march 2015 + actBareSoilEvap = pcr.ifthenelse(self.topWaterLayer > treshold, 0.0, actBareSoilEvap) + + # return the calculated variables: + if self.numberOfLayers == 2: + if returnTotalEstimation: + if returnTotalTranspirationOnly: + return actTranspiUpp+ actTranspiLow + else: + return actBareSoilEvap+ actTranspiUpp+ actTranspiLow + else: + return actBareSoilEvap, actTranspiUpp, actTranspiLow + if self.numberOfLayers == 3: + if returnTotalEstimation: + if returnTotalTranspirationOnly: + return actTranspiUpp000005+ actTranspiUpp005030+ actTranspiLow030150 + else: + return actBareSoilEvap+ actTranspiUpp000005+ actTranspiUpp005030+ actTranspiLow030150 + else: + return actBareSoilEvap, actTranspiUpp000005, actTranspiUpp005030, actTranspiLow030150 + + def estimateSoilFluxes(self,capRiseFrac,groundwater): + + # Given states, we estimate all fluxes. + ################################################################ + + if self.numberOfLayers == 2: + + # - percolation from storUpp to storLow + self.percUpp = self.kThVertUppLow * 1. + self.percUpp = \ + pcr.ifthenelse( self.effSatUpp > self.parameters.effSatAtFieldCapUpp, \ + pcr.min(pcr.max(0., self.effSatUpp - self.parameters.effSatAtFieldCapUpp)*self.parameters.storCapUpp, self.percUpp), self.percUpp) + \ + pcr.max(0.,self.infiltration - \ + (self.parameters.storCapUpp-self.storUpp)) # original Rens's line: + # P1_L[TYPE] = KTHVERT*Duration*timeslice(); + # P1_L[TYPE] = if(THEFF1 > THEFF1_FC[TYPE],min(max(0,THEFF1-THEFF1_FC[TYPE])*SC1[TYPE], + # P1_L[TYPE]),P1_L[TYPE])+max(0,P0_L[TYPE]-(SC1[TYPE]-S1_L[TYPE])); + # - percolation from storLow to storGroundwater + self.percLow = pcr.min(self.kUnsatLow, pcr.sqrt(\ + self.kUnsatLow*self.parameters.kUnsatAtFieldCapLow)) + # original Rens's line: + # P2_L[TYPE] = min(KTHEFF2,sqrt(KTHEFF2*KTHEFF2_FC[TYPE]))*Duration*timeslice() + + # - capillary rise to storUpp from storLow + self.capRiseUpp = \ + pcr.min(pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp - \ + self.effSatUpp)*self.parameters.storCapUpp,\ + self.kThVertUppLow * self.gradientUppLow) # original Rens's line: + # CR1_L[TYPE] = min(max(0,THEFF1_FC[TYPE]-THEFF1)*SC1[TYPE],KTHVERT*GRAD*Duration*timeslice()); + + # - capillary rise to storLow from storGroundwater (m) + self.capRiseLow = 0.5*(self.satAreaFrac + capRiseFrac)*\ + pcr.min((1.-self.effSatLow)*\ + pcr.sqrt(self.parameters.kSatLow* \ + self.kUnsatLow),\ + pcr.max(0.0,self.parameters.effSatAtFieldCapLow- \ + self.effSatLow)*\ + self.parameters.storCapLow) # original Rens's line: + # CR2_L[TYPE] = 0.5*(SATFRAC_L+CRFRAC)*min((1-THEFF2)*sqrt(KS2[TYPE]*KTHEFF2)*Duration*timeslice(), + # max(0,THEFF2_FC[TYPE]-THEFF2)*SC2[TYPE]); + + # - no capillary rise from non productive aquifer + self.capRiseLow = pcr.ifthenelse(groundwater.productive_aquifer,\ + self.capRiseLow, 0.0) + + # - interflow (m) + percToInterflow = self.parameters.percolationImp*(\ + self.percUpp+self.capRiseLow-\ + (self.percLow+self.capRiseUpp)) + self.interflow = pcr.max(\ + self.parameters.interflowConcTime*percToInterflow +\ + (pcr.scalar(1.)-self.parameters.interflowConcTime)*self.interflow, 0.0) + + if self.numberOfLayers == 3: + + # - percolation from storUpp000005 to storUpp005030 (m) + self.percUpp000005 = self.kThVertUpp000005Upp005030 * 1. + self.percUpp000005 = \ + pcr.ifthenelse( self.effSatUpp000005 > self.parameters.effSatAtFieldCapUpp000005, \ + pcr.min(pcr.max(0., self.effSatUpp000005 - self.parameters.effSatAtFieldCapUpp000005)*self.parameters.storCapUpp000005, self.percUpp000005), self.percUpp000005) + \ + pcr.max(0.,self.infiltration - \ + (self.parameters.storCapUpp000005-self.storUpp000005)) + + # - percolation from storUpp005030 to storLow030150 (m) + self.percUpp005030 = self.kThVertUpp005030Low030150 * 1. + self.percUpp005030 = \ + pcr.ifthenelse( self.effSatUpp005030 > self.parameters.effSatAtFieldCapUpp005030, \ + pcr.min(pcr.max(0., self.effSatUpp005030 - self.parameters.effSatAtFieldCapUpp005030)*self.parameters.storCapUpp005030, self.percUpp005030), self.percUpp005030) + \ + pcr.max(0.,self.percUpp000005 - \ + (self.parameters.storCapUpp005030-self.storUpp005030)) + + # - percolation from storLow030150 to storGroundwater (m) + self.percLow030150 = pcr.min(self.kUnsatLow030150,pcr.sqrt(\ + self.parameters.kUnsatAtFieldCapLow030150*\ + self.kUnsatLow030150)) + + # - capillary rise to storUpp000005 from storUpp005030 (m) + self.capRiseUpp000005 = pcr.min(pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp000005 - \ + self.effSatUpp000005)* \ + self.parameters.storCapUpp000005, \ + self.kThVertUpp000005Upp005030* \ + self.gradientUpp000005Upp005030) + + # - capillary rise to storUpp005030 from storLow030150 (m) + self.capRiseUpp005030 = pcr.min(pcr.max(0.,\ + self.parameters.effSatAtFieldCapUpp005030 - \ + self.effSatUpp005030)* \ + self.parameters.storCapUpp005030, \ + self.kThVertUpp005030Low030150* \ + self.gradientUpp005030Low030150) + + # - capillary rise to storLow030150 from storGroundwater (m) + self.capRiseLow030150 = 0.5*(self.satAreaFrac + capRiseFrac)*\ + pcr.min((1.-self.effSatLow030150)*\ + pcr.sqrt(self.parameters.kSatLow030150* \ + self.kUnsatLow030150),\ + pcr.max(0.0,self.parameters.effSatAtFieldCapLow030150- \ + self.effSatLow030150)*\ + self.parameters.storCapLow030150) + + # - no capillary rise from non productive aquifer + self.capRiseLow030150 = pcr.ifthenelse(groundwater.productive_aquifer,\ + self.capRiseLow030150, 0.0) + + # - interflow (m) + percToInterflow = self.parameters.percolationImp*(\ + self.percUpp005030+self.capRiseLow030150-\ + (self.percLow030150+self.capRiseUpp005030)) + self.interflow = pcr.max(\ + self.parameters.interflowConcTime*percToInterflow +\ + (pcr.scalar(1.)-self.parameters.interflowConcTime)*self.interflow, 0.0) + + + def scaleAllFluxes(self, groundwater): + + # We re-scale all fluxes (based on available water). + ######################################################################################################################################## + # + + if self.numberOfLayers == 2: + + # scale fluxes (for Upp) + ADJUST = self.actBareSoilEvap + self.actTranspiUpp + self.percUpp + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp + \ + self.infiltration) / ADJUST),0.) + ADJUST = pcr.cover(ADJUST, 0.0) + self.actBareSoilEvap = ADJUST*self.actBareSoilEvap + self.percUpp = ADJUST*self.percUpp + self.actTranspiUpp = ADJUST*self.actTranspiUpp + # original Rens's line: + # ADJUST = ES_a[TYPE]+T_a1[TYPE]+P1_L[TYPE]; + # ADJUST = if(ADJUST>0,min(1,(max(0,S1_L[TYPE]+P0_L[TYPE]))/ADJUST),0); + # ES_a[TYPE] = ADJUST*ES_a[TYPE]; + # T_a1[TYPE] = ADJUST*T_a1[TYPE]; + # P1_L[TYPE] = ADJUST*P1_L[TYPE]; + + # scale fluxes (for Low) + ADJUST = self.actTranspiLow + self.percLow + self.interflow + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow + \ + self.percUpp)/ADJUST),0.) + ADJUST = pcr.cover(ADJUST, 0.0) + self.percLow = ADJUST*self.percLow + self.actTranspiLow = ADJUST*self.actTranspiLow + self.interflow = ADJUST*self.interflow + # original Rens's line: + # ADJUST = T_a2[TYPE]+P2_L[TYPE]+Q2_L[TYPE]; + # ADJUST = if(ADJUST>0,min(1,max(S2_L[TYPE]+P1_L[TYPE],0)/ADJUST),0); + # T_a2[TYPE] = ADJUST*T_a2[TYPE]; + # P2_L[TYPE] = ADJUST*P2_L[TYPE]; + # Q2_L[TYPE] = ADJUST*Q2_L[TYPE]; + + # capillary rise to storLow is limited to available storGroundwater + # + # The following is for a conservative approach (used by Rens) + # - using fracVegCover as "safectyFactor". # EHS (02 Sep 2013): NOT NEEDED + #~ self.capRiseLow = \ + #~ pcr.min(self.fracVegCover*\ + #~ groundwater.storGroundwater,\ + #~ self.capRiseLow) # CR2_L[TYPE]= min(VEGFRAC[TYPE]*S3,CR2_L[TYPE]) + # + #~ # - without fracVegCover (without safetyFactor) + #~ self.capRiseLow = pcr.max(0.,\ + #~ pcr.min(\ + #~ groundwater.storGroundwater,self.capRiseLow)) # This line is not necessary. + # + # also limited with reducedCapRise + # + self.capRiseLow = pcr.max(0.,\ + pcr.min(\ + pcr.max(0.,\ + groundwater.storGroundwater-self.reducedCapRise),self.capRiseLow)) + + # capillary rise to storUpp is limited to available storLow + # + estimateStorLowBeforeCapRise = pcr.max(0,self.storLow + self.percUpp - \ + (self.actTranspiLow + self.percLow + self.interflow )) + self.capRiseUpp = pcr.min(\ + estimateStorLowBeforeCapRise,self.capRiseUpp) # original Rens's line: + # CR1_L[TYPE] = min(max(0,S2_L[TYPE]+P1_L[TYPE]-(T_a2[TYPE]+P2_L[TYPE]+Q2_L[TYPE])),CR1_L[TYPE]) + + if self.numberOfLayers == 3: + + # scale fluxes (for Upp000005) + ADJUST = self.actBareSoilEvap + self.actTranspiUpp000005 + self.percUpp000005 + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp000005 + \ + self.infiltration) / ADJUST),0.) + self.actBareSoilEvap = ADJUST*self.actBareSoilEvap + self.percUpp000005 = ADJUST*self.percUpp000005 + self.actTranspiUpp000005 = ADJUST*self.actTranspiUpp000005 + + # scale fluxes (for Upp005030) + ADJUST = self.actTranspiUpp005030 + self.percUpp005030 + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp005030 + \ + self.percUpp000005)/ ADJUST),0.) + self.percUpp005030 = ADJUST*self.percUpp005030 + self.actTranspiUpp005030 = ADJUST*self.actTranspiUpp005030 + + # scale fluxes (for Low030150) + ADJUST = self.actTranspiLow030150 + self.percLow030150 + self.interflow + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow030150 + \ + self.percUpp005030)/ADJUST),0.) + self.percLow030150 = ADJUST*self.percLow030150 + self.actTranspiLow030150 = ADJUST*self.actTranspiLow030150 + self.interflow = ADJUST*self.interflow + + # capillary rise to storLow is limited to available storGroundwater + # and also limited with reducedCapRise + # + self.capRiseLow030150 = pcr.max(0.,\ + pcr.min(\ + pcr.max(0.,\ + groundwater.storGroundwater-\ + self.reducedCapRise),\ + self.capRiseLow030150)) + + # capillary rise to storUpp005030 is limited to available storLow030150 + # + estimateStorLow030150BeforeCapRise = pcr.max(0,self.storLow030150 + self.percUpp005030 - \ + (self.actTranspiLow030150 + self.percLow030150 + self.interflow )) + self.capRiseUpp005030 = pcr.min(\ + estimateStorLow030150BeforeCapRise,self.capRiseUpp005030) + + # capillary rise to storUpp000005 is limited to available storUpp005030 + # + estimateStorUpp005030BeforeCapRise = pcr.max(0,self.storUpp005030 + self.percUpp000005 - \ + (self.actTranspiUpp005030 + self.percUpp005030)) + self.capRiseUpp000005 = pcr.min(\ + estimateStorUpp005030BeforeCapRise,self.capRiseUpp000005) + + + def scaleAllFluxesForIrrigatedAreas(self, groundwater): + + # for irrigation areas: interflow will be minimized + if self.name.startswith('irr'): self.interflow = 0. + + #~ # deep percolation should consider losses during application in non paddy areas + #~ if self.name == 'irrNonPaddy': + #~ startingCropKC = 0.00 + #~ maxADJUST = 100. + #~ if self.numberOfLayers == 2: + #~ minimum_deep_percolation = pcr.min(self.potential_irrigation_loss, self.storLow) + #~ deep_percolation = pcr.max(minimum_deep_percolation, \ + #~ self.percLow + self.interflow) + #~ ADJUST = self.percLow + self.interflow + #~ ADJUST = pcr.ifthenelse(ADJUST > 0., \ + #~ pcr.min(maxADJUST,pcr.max(0.0, deep_percolation)/ADJUST),0.) + #~ ADJUST = pcr.ifthenelse(self.cropKC > startingCropKC, ADJUST, 1.) + #~ self.percLow = ADJUST*self.percLow + #~ self.interflow = ADJUST*self.interflow + #~ if self.numberOfLayers == 3: + #~ minimum_deep_percolation = pcr.min(self.potential_irrigation_loss, self.storLow030150) + #~ deep_percolation = pcr.max(minimum_deep_percolation, \ + #~ self.percLow030150 + self.interflow) + #~ ADJUST = self.percLow030150 + self.interflow + #~ ADJUST = pcr.ifthenelse(ADJUST > 0., \ + #~ pcr.min(maxADJUST,pcr.max(0.0, deep_percolation)/ADJUST),0.) + #~ ADJUST = pcr.ifthenelse(self.cropKC > startingCropKC, ADJUST, 1.) + #~ self.percLow030150 = ADJUST*self.percLow030150 + #~ self.interflow = ADJUST*self.interflow + + #~ # idea on 9 May 2015 + #~ # deep percolation should consider losses during application in non paddy areas + #~ if self.name == "irrNonPaddy": + #~ startingKC = 0.20 # starting crop coefficient indicate the growing season + #~ if self.numberOfLayers == 2: + #~ deep_percolation_loss = self.percLow + #~ deep_percolation_loss = pcr.max(deep_percolation_loss, \ + #~ pcr.min(self.readAvlWater, self.storLow) * ((1./self.irrigationEfficiencyUsed) - 1.)) + #~ self.percLow = pcr.ifthenelse(self.cropKC > startingKC, deep_percolation_loss, \ + #~ pcr.ifthenelse(self.cropKC < self.prevCropKC, self.percLow, deep_percolation_loss)) + #~ if self.numberOfLayers == 3: + #~ deep_percolation_loss = self.percLow030150 + #~ deep_percolation_loss = pcr.max(deep_percolation_loss, \ + #~ pcr.min(self.readAvlWater, self.storLow030150) * ((1./self.irrigationEfficiencyUsed) - 1.)) + #~ self.percLow030150 = pcr.ifthenelse(self.cropKC > startingKC, deep_percolation_loss, \ + #~ pcr.ifthenelse(self.cropKC < self.prevCropKC, self.percLow030150, deep_percolation_loss)) + + # idea on 16 June 2015 + # deep percolation should consider irrigation application losses + if self.name.startswith('irr'): + + startingKC = 0.20 # starting crop coefficient indicate the growing season + + if self.numberOfLayers == 2: + deep_percolation_loss = self.percLow + deep_percolation_loss = pcr.max(deep_percolation_loss, \ + pcr.max(0.0, self.storLow) * ((1./self.irrigationEfficiencyUsed) - 1.)) + self.percLow = pcr.ifthenelse(self.cropKC > startingKC, deep_percolation_loss, self.percLow) + + if self.numberOfLayers == 3: + deep_percolation_loss = self.percLow030150 + deep_percolation_loss = pcr.max(deep_percolation_loss, \ + pcr.max(0.0, self.storLow030150) * ((1./self.irrigationEfficiencyUsed) - 1.)) + self.percLow030150 = pcr.ifthenelse(self.cropKC > startingKC, deep_percolation_loss, self.percLow030150) + + + # idea on 24 June 2015 + # the total bare soil evaporation and deep percolation losses should be limited by irrigation efficiency and total transpiration + #~ if self.name.startswith('irr'): + #~ + #~ # starting crop coefficient indicate the growing season + #~ startingKC = 0.20 +#~ + #~ # estimate of total transpiration (unit: m) + #~ if self.numberOfLayers == 2: total_transpiration = self.actTranspiUpp + self.actTranspiLow + #~ if self.numberOfLayers == 3: total_transpiration = self.actTranspiUpp000005 +\ + #~ self.actTranspiUpp005030 +\ + #~ self.actTranspiLow030150 + #~ + #~ # potential/maximum irrigation loss (unit: m) + #~ potential_irrigation_loss_from_soil = total_transpiration * ((1./self.irrigationEfficiencyUsed) - 1.) + #~ # - some has evaporated through openWaterEvap (from paddy fields) + #~ potential_irrigation_loss_from_soil = pcr.max(0.0, potential_irrigation_loss_from_soil - self.openWaterEvap) + #~ + #~ # deep percolation loss as it is estimated (no reduction/changes) + #~ if self.numberOfLayers == 2: deep_percolation_loss = self.percLow + #~ if self.numberOfLayers == 3: deep_percolation_loss = self.percLow030150 +#~ + #~ # bare soil evaporation (unit: m), limited by the (remaining) potential_irrigation_loss_from_soil and the estimate of deep percolation + #~ self.actBareSoilEvap = pcr.ifthenelse(self.cropKC > startingKC, \ + #~ pcr.min(self.actBareSoilEvap, \ + #~ pcr.max(0.0, potential_irrigation_loss_from_soil - deep_percolation_loss)), self.actBareSoilEvap) + + + #~ # idea on 25 June 2015 + #~ # the minimum deep percolation losses is determined by irrigation efficiency and total transpiration + #~ if self.name.startswith('irr'): + #~ + #~ # starting crop coefficient indicate the growing season + #~ startingKC = 0.20 +#~ + #~ # estimate of total transpiration (unit: m) + #~ if self.numberOfLayers == 2: total_transpiration = self.actTranspiUpp + self.actTranspiLow + #~ if self.numberOfLayers == 3: total_transpiration = self.actTranspiUpp000005 +\ + #~ self.actTranspiUpp005030 +\ + #~ self.actTranspiLow030150 + #~ + #~ # potential/maximum irrigation loss (unit: m) + #~ potential_irrigation_loss_from_soil = total_transpiration * ((1./self.irrigationEfficiencyUsed) - 1.) + #~ # - some has evaporated through openWaterEvap (from paddy fields) + #~ potential_irrigation_loss_from_soil = pcr.max(0.0, potential_irrigation_loss_from_soil - self.openWaterEvap) + #~ + #~ # bare soil evaporation (unit: m), limited by the potential_irrigation_loss_from_soil + #~ self.actBareSoilEvap = pcr.ifthenelse(self.cropKC > startingKC, \ + #~ pcr.min(self.actBareSoilEvap, potential_irrigation_loss_from_soil), self.actBareSoilEvap) +#~ + #~ # minimum deep percolation loss is the (remaining) potential_irrigation_loss_from_soil + #~ deep_percolation_loss = pcr.max(potential_irrigation_loss_from_soil - self.actBareSoilEvap) + #~ if self.numberOfLayers == 2: + #~ deep_percolation_loss = pcr.min(deep_percolation_loss, \ + #~ pcr.max(0.0, self.storLow) * ((1./self.irrigationEfficiencyUsed) - 1.)) + #~ self.percLow = pcr.ifthenelse(self.cropKC > startingKC, pcr.max(deep_percolation_loss, self.percLow), self.percLow) + #~ if self.numberOfLayers == 3: + #~ deep_percolation_loss = pcr.min(deep_percolation_loss, \ + #~ pcr.max(0.0, self.storLow030150) * ((1./self.irrigationEfficiencyUsed) - 1.)) + #~ self.percLow030150 = pcr.ifthenelse(self.cropKC > startingKC, pcr.max(deep_percolation_loss, self.percLow030150), self.percLow030150) + + + # scale all fluxes based on available water + # - alternative 1: + self.scaleAllFluxes(groundwater) + #~ # - alternative 2: + #~ self.scaleAllFluxesOptimizeEvaporationTranspiration(groundwater) + + def scaleAllFluxesOptimizeEvaporationTranspiration(self, groundwater): + + # We re-scale all fluxes (based on available water). + # - in irrigated areas, evaporation fluxes are priority + # - percolation and interfflow losses depend on the remaining water + ######################################################################################################################################## + + # remaining total energy for evaporation fluxes: + remainingPotET = self.potBareSoilEvap + self.potTranspiration + + # scaling all fluxes based on available water + + if self.numberOfLayers == 2: + + # scale fluxes (for Upp) + # - potential transpiration will be used to boost the transpiration process + ADJUST = self.actBareSoilEvap + self.potTranspiration + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp + \ + self.infiltration) / ADJUST),0.) + self.actBareSoilEvap = ADJUST*self.actBareSoilEvap + self.actTranspiUpp = ADJUST*self.potTranspiration + # + # - allowing more transpiration + remainingPotET = pcr.max(0.0, remainingPotET -\ + (self.actBareSoilEvap + self.actTranspiUpp)) + extraTranspiration = pcr.min(remainingPotET,\ + pcr.max(0.0, self.storUpp + self.infiltration - \ + self.actBareSoilEvap - \ + self.actTranspiUpp)) + self.actTranspiUpp += extraTranspiration + remainingPotET = pcr.max(0.0, remainingPotET - extraTranspiration) + # + # - percolation fluxes depend on the remaining water + self.percUpp = pcr.min(self.percUpp,\ + pcr.max(0.0, self.storUpp + self.infiltration - \ + self.actBareSoilEvap - \ + self.actTranspiUpp)) + + # scale fluxes (for Low) + # - remaining potential evaporation will be used to boost the transpiration process + ADJUST = remainingPotET + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow + \ + self.percUpp)/ADJUST),0.) + self.actTranspiLow = ADJUST*remainingPotET + # - percolation and interflow fluxes depend on the remaining water + ADJUST = self.percLow + self.interflow + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow + \ + self.percUpp - self.actTranspiLow)/ADJUST),0.) + self.percLow = ADJUST*self.percLow + self.interflow = ADJUST*self.interflow + + # capillary rise to storLow is limited to available storGroundwater + # - also limited with reducedCapRise + self.capRiseLow = pcr.max(0.,\ + pcr.min(\ + pcr.max(0.,\ + groundwater.storGroundwater-self.reducedCapRise),self.capRiseLow)) + + # capillary rise to storUpp is limited to available storLow + estimateStorLowBeforeCapRise = pcr.max(0,self.storLow + self.percUpp - \ + (self.actTranspiLow + self.percLow + self.interflow )) + self.capRiseUpp = pcr.min(\ + estimateStorLowBeforeCapRise,self.capRiseUpp) # original Rens's line: + # CR1_L[TYPE] = min(max(0,S2_L[TYPE]+P1_L[TYPE]-(T_a2[TYPE]+P2_L[TYPE]+Q2_L[TYPE])),CR1_L[TYPE]) + + if self.numberOfLayers == 3: + + # scale fluxes (for Upp000005) + # - potential transpiration will be used to boost the transpiration process + ADJUST = self.actBareSoilEvap + self.potTranspiration + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp000005 + \ + self.infiltration) / ADJUST),0.) + self.actBareSoilEvap = ADJUST*self.actBareSoilEvap + self.actTranspiUpp000005 = ADJUST*self.potTranspiration + # + # - allowing more transpiration + remainingPotET = pcr.max(0.0, remainingPotET -\ + (self.actBareSoilEvap + self.actTranspiUpp000005)) + extraTranspiration = pcr.min(remainingPotET,\ + pcr.max(0.0, self.storUpp000005 + self.infiltration - \ + self.actBareSoilEvap - \ + self.actTranspiUpp000005)) + self.actTranspiUpp000005 += extraTranspiration + remainingPotET = pcr.max(0.0, remainingPotET - extraTranspiration) + # + # - percolation fluxes depend on the remaining water + self.percUpp000005 = pcr.min(self.percUpp000005,\ + pcr.max(0.0, self.storUpp000005 + self.infiltration - \ + self.actBareSoilEvap - \ + self.actTranspiUpp000005)) + + # scale fluxes (for Upp005030) + # - remaining potential evaporation will be used to boost the transpiration process + ADJUST = remainingPotET + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp005030 + \ + self.percUpp000005)/ADJUST),0.) + self.actTranspiUpp005030 = ADJUST*remainingPotET + # - percolation fluxes depend on the remaining water + self.percUpp005030 = pcr.min(self.percUpp005030,\ + pcr.max(0.0, self.storUpp005030 + self.percUpp000005 - \ + self.actTranspiUpp005030)) + + # scale fluxes (for Low030150) + # - remaining potential evaporation will be used to boost the transpiration process + remainingPotET = pcr.max(0.0, remainingPotET - self.actTranspiUpp005030) + ADJUST = remainingPotET + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow030150 + \ + self.percUpp005030)/ADJUST),0.) + self.actTranspiLow030150 = ADJUST*remainingPotET + # - percolation and interflow fluxes depend on the remaining water + ADJUST = self.percLow030150 + self.interflow + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow030150 + \ + self.percUpp005030 - self.actTranspiLow030150)/ADJUST),0.) + self.percLow030150 = ADJUST*self.percLow030150 + self.interflow = ADJUST*self.interflow + + # capillary rise to storLow is limited to available storGroundwater + # - also limited with reducedCapRise + # + self.capRiseLow030150 = pcr.max(0.,\ + pcr.min(\ + pcr.max(0.,\ + groundwater.storGroundwater-\ + self.reducedCapRise),\ + self.capRiseLow030150)) + + # capillary rise to storUpp005030 is limited to available storLow030150 + # + estimateStorLow030150BeforeCapRise = pcr.max(0,self.storLow030150 + self.percUpp005030 - \ + (self.actTranspiLow030150 + self.percLow030150 + self.interflow )) + self.capRiseUpp005030 = pcr.min(\ + estimateStorLow030150BeforeCapRise,self.capRiseUpp005030) + + # capillary rise to storUpp000005 is limited to available storUpp005030 + # + estimateStorUpp005030BeforeCapRise = pcr.max(0,self.storUpp005030 + self.percUpp000005 - \ + (self.actTranspiUpp005030 + self.percUpp005030)) + self.capRiseUpp000005 = pcr.min(\ + estimateStorUpp005030BeforeCapRise,self.capRiseUpp000005) + + def scaleAllFluxesOptimizeEvaporationVersion27April2014(self, groundwater): + + # We re-scale all fluxes (based on available water). + # - in irrigated areas, evaporation fluxes are priority + # - percolation and interfflow losses depend on the remaining water + ######################################################################################################################################## + + # remaining total energy for evaporation fluxes: + remainingPotET = self.potBareSoilEvap + self.potTranspiration + + # for irrigation areas: interflow will be minimized + if self.name.startswith('irr'): self.interflow = 0. + + # an idea: deep percolation should consider losses during application in non paddy areas + # + if self.name == 'irrNonPaddy': + startingCropKC = 0.75 + minimum_deep_percolation = pcr.min(self.infiltration, self.potential_irrigation_loss) + maxADJUST = 2.0 + # + if self.numberOfLayers == 2: + deep_percolation = pcr.max(minimum_deep_percolation, \ + self.percLow + self.interflow) + ADJUST = self.percLow + self.interflow + ADJUST = pcr.ifthenelse(ADJUST > 0., \ + pcr.min(maxADJUST,pcr.max(0.0, deep_percolation)/ADJUST),0.) + ADJUST = pcr.ifthenelse(self.cropKC > startingCropKC, ADJUST, 1.) + self.percLow = ADJUST*self.percLow + self.interflow = ADJUST*self.interflow + if self.numberOfLayers == 3: + deep_percolation = pcr.max(minimum_deep_percolation, \ + self.percLow030150 + self.interflow) + ADJUST = self.percLow030150 + self.interflow + ADJUST = pcr.ifthenelse(ADJUST > 0., \ + pcr.min(maxADJUST,pcr.max(0.0, deep_percolation)/ADJUST),0.) + ADJUST = pcr.ifthenelse(self.cropKC > startingCropKC, ADJUST, 1.) + self.percLow030150 = ADJUST*self.percLow030150 + self.interflow = ADJUST*self.interflow + + + + # scaling all fluxes based on available water + + if self.numberOfLayers == 2: + + # scale fluxes (for Upp) + # - potential transpiration will be used to boost the transpiration process + ADJUST = self.actBareSoilEvap + self.potTranspiration + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp + \ + self.infiltration) / ADJUST),0.) + self.actBareSoilEvap = ADJUST*self.actBareSoilEvap + self.actTranspiUpp = ADJUST*self.potTranspiration + # + # - allowing more transpiration + remainingPotET = pcr.max(0.0, remainingPotET -\ + (self.actBareSoilEvap + self.actTranspiUpp)) + extraTranspiration = pcr.min(remainingPotET,\ + pcr.max(0.0, self.storUpp + self.infiltration - \ + self.actBareSoilEvap - \ + self.actTranspiUpp)) + self.actTranspiUpp += extraTranspiration + remainingPotET = pcr.max(0.0, remainingPotET - extraTranspiration) + # + # - percolation fluxes depend on the remaining water + self.percUpp = pcr.min(self.percUpp,\ + pcr.max(0.0, self.storUpp + self.infiltration - \ + self.actBareSoilEvap - \ + self.actTranspiUpp)) + + # scale fluxes (for Low) + # - remaining potential evaporation will be used to boost the transpiration process + ADJUST = remainingPotET + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow + \ + self.percUpp)/ADJUST),0.) + self.actTranspiLow = ADJUST*remainingPotET + # - percolation and interflow fluxes depend on the remaining water + ADJUST = self.percLow + self.interflow + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow + \ + self.percUpp - self.actTranspiLow)/ADJUST),0.) + self.percLow = ADJUST*self.percLow + self.interflow = ADJUST*self.interflow + + # capillary rise to storLow is limited to available storGroundwater + # - also limited with reducedCapRise + self.capRiseLow = pcr.max(0.,\ + pcr.min(\ + pcr.max(0.,\ + groundwater.storGroundwater-self.reducedCapRise),self.capRiseLow)) + + # capillary rise to storUpp is limited to available storLow + estimateStorLowBeforeCapRise = pcr.max(0,self.storLow + self.percUpp - \ + (self.actTranspiLow + self.percLow + self.interflow )) + self.capRiseUpp = pcr.min(\ + estimateStorLowBeforeCapRise,self.capRiseUpp) # original Rens's line: + # CR1_L[TYPE] = min(max(0,S2_L[TYPE]+P1_L[TYPE]-(T_a2[TYPE]+P2_L[TYPE]+Q2_L[TYPE])),CR1_L[TYPE]) + + if self.numberOfLayers == 3: + + # scale fluxes (for Upp000005) + # - potential transpiration will be used to boost the transpiration process + ADJUST = self.actBareSoilEvap + self.potTranspiration + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp000005 + \ + self.infiltration) / ADJUST),0.) + self.actBareSoilEvap = ADJUST*self.actBareSoilEvap + self.actTranspiUpp000005 = ADJUST*self.potTranspiration + # + # - allowing more transpiration + remainingPotET = pcr.max(0.0, remainingPotET -\ + (self.actBareSoilEvap + self.actTranspiUpp000005)) + extraTranspiration = pcr.min(remainingPotET,\ + pcr.max(0.0, self.storUpp000005 + self.infiltration - \ + self.actBareSoilEvap - \ + self.actTranspiUpp000005)) + self.actTranspiUpp000005 += extraTranspiration + remainingPotET = pcr.max(0.0, remainingPotET - extraTranspiration) + # + # - percolation fluxes depend on the remaining water + self.percUpp000005 = pcr.min(self.percUpp000005,\ + pcr.max(0.0, self.storUpp000005 + self.infiltration - \ + self.actBareSoilEvap - \ + self.actTranspiUpp000005)) + + # scale fluxes (for Upp005030) + # - remaining potential evaporation will be used to boost the transpiration process + ADJUST = remainingPotET + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storUpp005030 + \ + self.percUpp000005)/ADJUST),0.) + self.actTranspiUpp005030 = ADJUST*remainingPotET + # - percolation fluxes depend on the remaining water + self.percUpp005030 = pcr.min(self.percUpp005030,\ + pcr.max(0.0, self.storUpp005030 + self.percUpp000005 - \ + self.actTranspiUpp005030)) + + # scale fluxes (for Low030150) + # - remaining potential evaporation will be used to boost the transpiration process + remainingPotET = pcr.max(0.0, remainingPotET - self.actTranspiUpp005030) + ADJUST = remainingPotET + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow030150 + \ + self.percUpp005030)/ADJUST),0.) + self.actTranspiLow030150 = ADJUST*remainingPotET + # - percolation and interflow fluxes depend on the remaining water + ADJUST = self.percLow030150 + self.interflow + ADJUST = pcr.ifthenelse(ADJUST>0.0, \ + pcr.min(1.0,pcr.max(0.0, self.storLow030150 + \ + self.percUpp005030 - self.actTranspiLow030150)/ADJUST),0.) + self.percLow030150 = ADJUST*self.percLow030150 + self.interflow = ADJUST*self.interflow + + # capillary rise to storLow is limited to available storGroundwater + # - also limited with reducedCapRise + # + self.capRiseLow030150 = pcr.max(0.,\ + pcr.min(\ + pcr.max(0.,\ + groundwater.storGroundwater-\ + self.reducedCapRise),\ + self.capRiseLow030150)) + + # capillary rise to storUpp005030 is limited to available storLow030150 + # + estimateStorLow030150BeforeCapRise = pcr.max(0,self.storLow030150 + self.percUpp005030 - \ + (self.actTranspiLow030150 + self.percLow030150 + self.interflow )) + self.capRiseUpp005030 = pcr.min(\ + estimateStorLow030150BeforeCapRise,self.capRiseUpp005030) + + # capillary rise to storUpp000005 is limited to available storUpp005030 + # + estimateStorUpp005030BeforeCapRise = pcr.max(0,self.storUpp005030 + self.percUpp000005 - \ + (self.actTranspiUpp005030 + self.percUpp005030)) + self.capRiseUpp000005 = pcr.min(\ + estimateStorUpp005030BeforeCapRise,self.capRiseUpp000005) + + def updateSoilStates(self): + + # We give new states and make sure that no storage capacities will be exceeded. + ################################################################################# + + if self.numberOfLayers == 2: + + # update storLow after the following fluxes: + # + percUpp + # + capRiseLow + # - percLow + # - interflow + # - actTranspiLow + # - capRiseUpp + # + self.storLow = pcr.max(0., self.storLow + \ + self.percUpp + \ + self.capRiseLow - \ + (self.percLow + self.interflow + \ + self.actTranspiLow +\ + self.capRiseUpp)) # S2_L[TYPE]= max(0,S2_L[TYPE]+P1_L[TYPE]+CR2_L[TYPE]- + # (P2_L[TYPE]+Q2_L[TYPE]+CR1_L[TYPE]+T_a2[TYPE])); + # + # If necessary, reduce percolation input: + percUpp = self.percUpp + + if self.allowNegativePercolation: + # this is as defined in the original oldcalc script of Rens + self.percUpp = percUpp - \ + pcr.max(0.,self.storLow - \ + self.parameters.storCapLow) + # Rens's line: P1_L[TYPE] = P1_L[TYPE]-max(0,S2_L[TYPE]-SC2[TYPE]); + # PS: In the original Rens's code, P1 can be negative. + else: + # alternative, proposed by Edwin: avoid negative percolation + self.percUpp = pcr.max(0., percUpp - \ + pcr.max(0.,self.storLow - \ + self.parameters.storCapLow)) + self.storLow = self.storLow - percUpp + \ + self.percUpp + # If necessary, reduce capRise input: + capRiseLow = self.capRiseLow + self.capRiseLow = pcr.max(0.,capRiseLow - \ + pcr.max(0.,self.storLow - \ + self.parameters.storCapLow)) + self.storLow = self.storLow - capRiseLow + \ + self.capRiseLow + # If necessary, increase interflow outflow: + addInterflow = pcr.max(0.,\ + self.storLow - self.parameters.storCapLow) + self.interflow += addInterflow + self.storLow -= addInterflow + # + self.storLow = pcr.min(self.storLow, self.parameters.storCapLow) + + # + # update storUpp after the following fluxes: + # + infiltration + # + capRiseUpp + # - percUpp + # - actTranspiUpp + # - actBareSoilEvap + # + self.storUpp = pcr.max(0.,self.storUpp + \ + self.infiltration + \ + self.capRiseUpp - \ + (self.percUpp + \ + self.actTranspiUpp + self.actBareSoilEvap)) # Rens's line: S1_L[TYPE]= max(0,S1_L[TYPE]+P0_L[TYPE]+CR1_L[TYPE]- + # (P1_L[TYPE]+T_a1[TYPE]+ES_a[TYPE])); #* + # + # any excess above storCapUpp is handed to topWaterLayer + self.satExcess = pcr.max(0.,self.storUpp - \ + self.parameters.storCapUpp) + self.topWaterLayer = self.topWaterLayer + self.satExcess + + # any excess above minTopWaterLayer is released as directRunoff + self.directRunoff = self.directRunoff + \ + pcr.max(0.,self.topWaterLayer - self.minTopWaterLayer) + + # make sure that storage capacities are not exceeded + self.topWaterLayer = pcr.min( self.topWaterLayer , \ + self.minTopWaterLayer) + self.storUpp = pcr.min(self.storUpp,\ + self.parameters.storCapUpp) + self.storLow = pcr.min(self.storLow,\ + self.parameters.storCapLow) + + # total actual evaporation + transpiration + self.actualET += self.actBareSoilEvap + \ + self.openWaterEvap + \ + self.actTranspiUpp + \ + self.actTranspiLow + + # total actual transpiration + self.actTranspiTotal = self.actTranspiUpp + \ + self.actTranspiLow + + # net percolation between upperSoilStores (positive indicating downward direction) + self.netPercUpp = self.percUpp - self.capRiseUpp + + # groundwater recharge (positive indicating downward direction) + self.gwRecharge = self.percLow - self.capRiseLow + + # the following variables introduced for the comparison with threeLayer model output + self.storUppTotal = self.storUpp + self.storLowTotal = self.storLow + self.actTranspiUppTotal = self.actTranspiUpp + self.actTranspiLowTotal = self.actTranspiLow + self.interflowTotal = self.interflow + + if self.numberOfLayers == 3: + + # update storLow030150 after the following fluxes: + # + percUpp005030 + # + capRiseLow030150 + # - percLow030150 + # - interflow + # - actTranspiLow030150 + # - capRiseUpp005030 + # + self.storLow030150 = pcr.max(0., self.storLow030150 + \ + self.percUpp005030 + \ + self.capRiseLow030150 - \ + (self.percLow030150 + self.interflow + \ + self.actTranspiLow030150 +\ + self.capRiseUpp005030)) + # + # If necessary, reduce percolation input: + percUpp005030 = self.percUpp005030 + self.percUpp005030 = pcr.max(0., percUpp005030 - \ + pcr.max(0.,self.storLow030150 - \ + self.parameters.storCapLow030150)) + self.storLow030150 = self.storLow030150 - \ + percUpp005030 + \ + self.percUpp005030 + # + # If necessary, reduce capRise input: + capRiseLow030150 = self.capRiseLow030150 + self.capRiseLow030150 = pcr.max(0.,capRiseLow030150 - \ + pcr.max(0.,self.storLow030150 - \ + self.parameters.storCapLow030150)) + self.storLow030150 = self.storLow030150 - \ + capRiseLow030150 + \ + self.capRiseLow030150 + # + # If necessary, increase interflow outflow: + addInterflow = pcr.max(0.,\ + self.storLow030150 - self.parameters.storCapLow030150) + self.interflow += addInterflow + self.storLow030150 -= addInterflow + + self.storLow030150 = pcr.min(self.storLow030150,\ + self.parameters.storCapLow030150) + + # update storUpp005030 after the following fluxes: + # + percUpp000005 + # + capRiseUpp005030 + # - percUpp005030 + # - actTranspiUpp005030 + # - capRiseUpp000005 + # + self.storUpp005030 = pcr.max(0., self.storUpp005030 + \ + self.percUpp000005 + \ + self.capRiseUpp005030 - \ + (self.percUpp005030 + \ + self.actTranspiUpp005030 + \ + self.capRiseUpp000005)) + # + # If necessary, reduce percolation input: + percUpp000005 = self.percUpp000005 + self.percUpp000005 = pcr.max(0., percUpp000005 - \ + pcr.max(0.,self.storUpp005030 - \ + self.parameters.storCapUpp005030)) + self.storUpp005030 = self.storUpp005030 - \ + percUpp000005 + \ + self.percUpp000005 + # + # If necessary, reduce capRise input: + capRiseUpp005030 = self.capRiseUpp005030 + self.capRiseUpp005030 = pcr.max(0.,capRiseUpp005030 - \ + pcr.max(0.,self.storUpp005030 - \ + self.parameters.storCapUpp005030)) + self.storUpp005030 = self.storUpp005030 - \ + capRiseUpp005030 + \ + self.capRiseUpp005030 + # + # If necessary, introduce interflow outflow: + self.interflowUpp005030 = pcr.max(0.,\ + self.storUpp005030 - self.parameters.storCapUpp005030) + self.storUpp005030 = self.storUpp005030 - \ + self.interflowUpp005030 + + # update storUpp000005 after the following fluxes: + # + infiltration + # + capRiseUpp000005 + # - percUpp000005 + # - actTranspiUpp000005 + # - actBareSoilEvap + # + self.storUpp000005 = pcr.max(0.,self.storUpp000005 + \ + self.infiltration + \ + self.capRiseUpp000005 - \ + (self.percUpp000005 + \ + self.actTranspiUpp000005 + \ + self.actBareSoilEvap)) + # + # any excess above storCapUpp is handed to topWaterLayer + self.satExcess = pcr.max(0.,self.storUpp000005 - \ + self.parameters.storCapUpp000005) + self.topWaterLayer = self.topWaterLayer + self.satExcess + + # any excess above minTopWaterLayer is released as directRunoff + self.directRunoff = self.directRunoff + \ + pcr.max(0.,self.topWaterLayer - \ + self.minTopWaterLayer) + + # make sure that storage capacities are not exceeded + self.topWaterLayer = pcr.min( self.topWaterLayer , \ + self.minTopWaterLayer) + self.storUpp000005 = pcr.min(self.storUpp000005,\ + self.parameters.storCapUpp000005) + self.storUpp005030 = pcr.min(self.storUpp005030,\ + self.parameters.storCapUpp005030) + self.storLow030150 = pcr.min(self.storLow030150,\ + self.parameters.storCapLow030150) + + # total actual evaporation + transpiration + self.actualET += self.actBareSoilEvap + \ + self.openWaterEvap + \ + self.actTranspiUpp000005 + \ + self.actTranspiUpp005030 + \ + self.actTranspiLow030150 + + # total actual transpiration + self.actTranspiUppTotal = self.actTranspiUpp000005 + \ + self.actTranspiUpp005030 + + # total actual transpiration + self.actTranspiTotal = self.actTranspiUppTotal + \ + self.actTranspiLow030150 + + # net percolation between upperSoilStores (positive indicating downward direction) + self.netPercUpp000005 = self.percUpp000005 - self.capRiseUpp000005 + self.netPercUpp005030 = self.percUpp005030 - self.capRiseUpp005030 + + # groundwater recharge + self.gwRecharge = self.percLow030150 - self.capRiseLow030150 + + # the following variables introduced for the comparison with twoLayer model output + self.storUppTotal = self.storUpp000005 + self.storUpp005030 + self.storLowTotal = self.storLow030150 + self.actTranspiUppTotal = self.actTranspiUpp000005 + self.actTranspiUpp005030 + self.actTranspiLowTotal = self.actTranspiLow030150 + self.interflowTotal = self.interflow + self.interflowUpp005030 + + # variables / states that are defined the twoLayer and threeLayer model: + ######################################################################## + + # landSurfaceRunoff (needed for routing) + self.landSurfaceRunoff = self.directRunoff + self.interflowTotal + + def upperSoilUpdate(self,meteo,groundwater,routing,\ + capRiseFrac,\ + nonIrrGrossDemandDict,swAbstractionFractionDict,\ + currTimeStep,\ + allocSegments,\ + desalinationWaterUse,\ + groundwater_pumping_region_ids,regionalAnnualGroundwaterAbstractionLimit): + + if self.debugWaterBalance: + netLqWaterToSoil = self.netLqWaterToSoil # input + preTopWaterLayer = self.topWaterLayer + if self.numberOfLayers == 2: + preStorUpp = self.storUpp + preStorLow = self.storLow + if self.numberOfLayers == 3: + preStorUpp000005 = self.storUpp000005 + preStorUpp005030 = self.storUpp005030 + preStorLow030150 = self.storLow030150 + + # given soil storages, we can calculate several derived states, such as + # effective degree of saturation, unsaturated hydraulic conductivity, and + # readily available water within the root zone. + self.getSoilStates() + + # calculate water demand (including partitioning to different source) + self.calculateWaterDemand(nonIrrGrossDemandDict, swAbstractionFractionDict, \ + groundwater, routing, \ + allocSegments, currTimeStep,\ + desalinationWaterUse,\ + groundwater_pumping_region_ids,regionalAnnualGroundwaterAbstractionLimit) + + # calculate openWaterEvap: open water evaporation from the paddy field, + # and update topWaterLayer after openWaterEvap. + self.calculateOpenWaterEvap() + + # calculate directRunoff and infiltration, based on the improved Arno scheme (Hageman and Gates, 2003): + # and update topWaterLayer (after directRunoff and infiltration). + self.calculateDirectRunoff() + self.calculateInfiltration() + + # estimate bare soil evaporation and transpiration: + if self.numberOfLayers == 2: + self.actBareSoilEvap, self.actTranspiUpp, self.actTranspiLow = \ + self.estimateTranspirationAndBareSoilEvap() + if self.numberOfLayers == 3: + self.actBareSoilEvap, self.actTranspiUpp000005, self.actTranspiUpp005030, self.actTranspiLow030150 = \ + self.estimateTranspirationAndBareSoilEvap() + + # estimate percolation and capillary rise, as well as interflow + self.estimateSoilFluxes(capRiseFrac,groundwater) + + # all fluxes are limited to available (source) storage + if self.name.startswith('irr') and self.includeIrrigation: + self.scaleAllFluxesForIrrigatedAreas(groundwater) + #~ self.scaleAllFluxes(groundwater) + else: + self.scaleAllFluxes(groundwater) + + # update all soil states (including get final/corrected fluxes) + self.updateSoilStates() + + # reporting irrigation transpiration deficit + self.irrigationTranspirationDeficit = 0.0 + if self.name.startswith('irr'): self.irrigationTranspirationDeficit = pcr.max(0.0, self.potTranspiration - self.actTranspiTotal) + + if self.debugWaterBalance: + # + vos.waterBalanceCheck([netLqWaterToSoil ,\ + self.irrGrossDemand ,\ + self.satExcess ],\ + [self.directRunoff ,\ + self.openWaterEvap ,\ + self.infiltration] ,\ + [ preTopWaterLayer ],\ + [self.topWaterLayer ] ,\ + 'topWaterLayer',True,\ + currTimeStep.fulldate,threshold=1e-4) + + if self.numberOfLayers == 2: + # + vos.waterBalanceCheck([self.infiltration, + self.capRiseUpp],\ + [self.actTranspiUpp, + self.percUpp, + self.actBareSoilEvap, + self.satExcess],\ + [ preStorUpp],\ + [self.storUpp],\ + 'storUpp',\ + True,\ + currTimeStep.fulldate,threshold=1e-5) + # + vos.waterBalanceCheck([self.percUpp],\ + [self.actTranspiLow, + self.gwRecharge, + self.interflow, + self.capRiseUpp],\ + [ preStorLow],\ + [self.storLow],\ + 'storLow',\ + True,\ + currTimeStep.fulldate,threshold=1e-5) + # + vos.waterBalanceCheck([self.infiltration,\ + self.capRiseLow],\ + [self.satExcess, + self.interflow, + self.percLow, + self.actTranspiUpp, + self.actTranspiLow, + self.actBareSoilEvap],\ + [ preStorUpp, + preStorLow],\ + [self.storUpp, + self.storLow],\ + 'entireSoilLayers',\ + True,\ + currTimeStep.fulldate,threshold=1e-4) + # + vos.waterBalanceCheck([netLqWaterToSoil, + self.capRiseLow, + self.irrGrossDemand],\ + [self.directRunoff, + self.interflow, + self.percLow, + self.actTranspiUpp, + self.actTranspiLow, + self.actBareSoilEvap, + self.openWaterEvap],\ + [ preTopWaterLayer, + preStorUpp, + preStorLow],\ + [self.topWaterLayer, + self.storUpp, + self.storLow],\ + 'allLayers',\ + True,\ + currTimeStep.fulldate,threshold=5e-4) + + if self.numberOfLayers == 3: + vos.waterBalanceCheck([self.infiltration, + self.capRiseUpp000005],\ + [self.actTranspiUpp000005, + self.percUpp000005, + self.actBareSoilEvap, + self.satExcess],\ + [ preStorUpp000005],\ + [self.storUpp000005],\ + 'storUpp000005',True,\ + currTimeStep.fulldate,threshold=1e-5) + + # + vos.waterBalanceCheck([self.percUpp000005, + self.capRiseUpp005030],\ + [self.actTranspiUpp005030, + self.percUpp005030, + self.interflowUpp005030, + self.capRiseUpp000005],\ + [ preStorUpp005030],\ + [self.storUpp005030],\ + 'storUpp005030',True,\ + currTimeStep.fulldate,threshold=1e-5) + # + vos.waterBalanceCheck([self.percUpp005030],\ + [self.actTranspiLow030150, + self.gwRecharge, + self.interflow, + self.capRiseUpp005030],\ + [ preStorLow030150],\ + [self.storLow030150],\ + 'storLow030150',True,\ + currTimeStep.fulldate,threshold=1e-5) + # + vos.waterBalanceCheck([self.infiltration,\ + self.capRiseLow030150],\ + [self.satExcess, + self.interflow, + self.interflowUpp005030, + self.percLow030150, + self.actTranspiUpp000005, + self.actTranspiUpp005030, + self.actTranspiLow030150, + self.actBareSoilEvap],\ + [ preStorUpp000005, + preStorUpp005030, + preStorLow030150],\ + [self.storUpp000005, + self.storUpp005030, + self.storLow030150],\ + 'entireSoilLayers',True,\ + currTimeStep.fulldate,threshold=1e-4) + # + vos.waterBalanceCheck([netLqWaterToSoil, + self.capRiseLow030150, + self.irrGrossDemand],\ + [self.directRunoff, + self.interflow, + self.interflowUpp005030, + self.percLow030150, + self.actTranspiUpp000005, + self.actTranspiUpp005030, + self.actTranspiLow030150, + self.actBareSoilEvap, + self.openWaterEvap],\ + [ preTopWaterLayer, + preStorUpp000005, + preStorUpp005030, + preStorLow030150],\ + [self.topWaterLayer, + self.storUpp000005, + self.storUpp005030, + self.storLow030150],\ + 'allLayers',True,\ + currTimeStep.fulldate,threshold=1e-4) diff --git a/model/landSurfaceEdwin.py b/model/landSurfaceEdwin.py new file mode 100644 index 000000000..2ea05e719 --- /dev/null +++ b/model/landSurfaceEdwin.py @@ -0,0 +1,1723 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model +# +# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, +# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, +# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, +# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens +# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import types +import pcraster as pcr +import virtualOS as vos + +import logging +logger = logging.getLogger(__name__) + +from ncConverter import * + +import landCover as lc +import parameterSoilAndTopo as parSoilAndTopo + +class LandSurface(object): + + def getState(self): + result = {} + + if self.numberOfSoilLayers == 2: + for coverType in self.coverTypes: + result[coverType] = {} + result[coverType]['interceptStor'] = \ + self.landCoverObj[coverType].interceptStor + result[coverType]['snowCoverSWE' ] = \ + self.landCoverObj[coverType].snowCoverSWE + result[coverType]['snowFreeWater'] = \ + self.landCoverObj[coverType].snowFreeWater + result[coverType]['topWaterLayer'] = \ + self.landCoverObj[coverType].topWaterLayer + result[coverType]['storUpp'] = \ + self.landCoverObj[coverType].storUpp + result[coverType]['storLow'] = \ + self.landCoverObj[coverType].storLow + result[coverType]['interflow' ] = \ + self.landCoverObj[coverType].interflow + + if self.numberOfSoilLayers == 3: + for coverType in self.coverTypes: + result[coverType] = {} + result[coverType]['interceptStor'] = \ + self.landCoverObj[coverType].interceptStor + result[coverType]['snowCoverSWE' ] = \ + self.landCoverObj[coverType].snowCoverSWE + result[coverType]['snowFreeWater'] = \ + self.landCoverObj[coverType].snowFreeWater + result[coverType]['topWaterLayer'] = \ + self.landCoverObj[coverType].topWaterLayer + result[coverType]['storUpp000005'] = \ + self.landCoverObj[coverType].storUpp000005 + result[coverType]['storUpp005030'] = \ + self.landCoverObj[coverType].storUpp005030 + result[coverType]['storLow030150'] = \ + self.landCoverObj[coverType].storLow030150 + result[coverType]['interflow' ] = \ + self.landCoverObj[coverType].interflow + + return result + + def getPseudoState(self): + result = {} + + if self.numberOfSoilLayers == 2: + result['interceptStor'] = self.interceptStor + result['snowCoverSWE'] = self.snowCoverSWE + result['snowFreeWater'] = self.snowFreeWater + result['topWaterLayer'] = self.topWaterLayer + result['storUpp'] = self.storUpp + result['storLow'] = self.storLow + + if self.numberOfSoilLayers == 3: + result['interceptStor'] = self.interceptStor + result['snowCoverSWE'] = self.snowCoverSWE + result['snowFreeWater'] = self.snowFreeWater + result['topWaterLayer'] = self.topWaterLayer + result['storUpp000005'] = self.storUpp000005 + result['storUpp005030'] = self.storUpp005030 + result['storLow030150'] = self.storLow030150 + + return result + + def __init__(self,iniItems,landmask,initialState=None): + object.__init__(self) + + # clone map, temporary directory, absolute path of input directory, and landmask + self.cloneMap = iniItems.cloneMap + self.tmpDir = iniItems.tmpDir + self.inputDir = iniItems.globalOptions['inputDir'] + self.landmask = landmask + + # make iniItems available for the other methods/functions: + self.iniItems = iniItems + + # cellArea (unit: m2) + self.cellArea = vos.readPCRmapClone(iniItems.routingOptions['cellAreaMap'], \ + self.cloneMap, self.tmpDir, self.inputDir) + self.cellArea = pcr.ifthen(self.landmask, self.cellArea) + + # number of soil layers: + self.numberOfSoilLayers = int(iniItems.landSurfaceOptions['numberOfUpperSoilLayers']) + + # list of aggregated variables that MUST be defined in the module: + # - aggregated from landCover modules + # - some are needed for water balance checking + # - some are needed in other modules (e.g. routing, groundwater) + # - some are needed for initialConditions + # + # main state variables (unit: m) + self.mainStates = ['interceptStor',\ + 'snowCoverSWE' ,\ + 'snowFreeWater',\ + 'topWaterLayer'] + # + # state variables (unit: m) + self.stateVars = ['storUppTotal', + 'storLowTotal', + 'satDegUppTotal', + 'satDegLowTotal', + 'satDegTotal'] + # + # flux variables (unit: m/day) + self.fluxVars = ['infiltration','gwRecharge','netLqWaterToSoil', + 'totalPotET', + 'actualET', + 'interceptEvap', + 'openWaterEvap', + 'actSnowFreeWaterEvap', + 'actBareSoilEvap', + 'actTranspiUppTotal', + 'actTranspiLowTotal', + 'actTranspiTotal', + 'directRunoff', + 'interflow', + 'interflowTotal', + 'irrGrossDemand', + 'nonIrrGrossDemand', + 'totalPotentialGrossDemand', + 'actSurfaceWaterAbstract', + 'allocSurfaceWaterAbstract', + 'desalinationAbstraction', + 'desalinationAllocation', + 'nonFossilGroundwaterAbs', + 'allocNonFossilGroundwater', + 'fossilGroundwaterAbstr', + 'fossilGroundwaterAlloc', + 'landSurfaceRunoff', + 'satExcess', + 'snowMelt', + 'totalGroundwaterAbstraction', + 'totalGroundwaterAllocation', + 'totalPotentialMaximumGrossDemand', + 'totalPotentialMaximumIrrGrossDemand', + 'totalPotentialMaximumIrrGrossDemandPaddy', + 'totalPotentialMaximumIrrGrossDemandNonPaddy', + 'totalPotentialMaximumNonIrrGrossDemand', + 'irrGrossDemandPaddy', + 'irrGrossDemandNonPaddy', + 'domesticWaterWithdrawal', + 'industryWaterWithdrawal', + 'livestockWaterWithdrawal', + 'nonIrrReturnFlow', + 'irrigationTranspirationDeficit'] + # + # specific variables for 2 and 3 layer soil models: + # + if self.numberOfSoilLayers == 2: + self.mainStates += ['storUpp','storLow'] + self.stateVars += self.mainStates + self.fluxVars += ['actTranspiUpp','actTranspiLow','netPercUpp'] + # + if self.numberOfSoilLayers == 3: + self.mainStates += ['storUpp000005', 'storUpp005030', 'storLow030150'] + self.stateVars += self.mainStates + self.fluxVars += ['actTranspiUpp000005','actTranspiUpp005030','actTranspiLow030150', + 'netPercUpp000005', 'netPercUpp005030', + 'interflowUpp005030'] + + # list of all variables that will be calculated/reported in landSurface.py + self.aggrVars = self.stateVars + self.fluxVars + if self.numberOfSoilLayers == 2: self.aggrVars += ['satDegUpp','satDegLow'] + if self.numberOfSoilLayers == 3: self.aggrVars += ['satDegUpp000005','satDegUpp005030','satDegLow030150'] + + self.debugWaterBalance = iniItems.landSurfaceOptions['debugWaterBalance'] + # TDOD: Perform water balance checks for aggregates values (from values of each land cover type). + + # limitAbstraction + self.limitAbstraction = False + if iniItems.landSurfaceOptions['limitAbstraction'] == "True": self.limitAbstraction = True + + # landCover types included in the simulation: + self.coverTypes = ["forest","grassland"] + # + self.includeIrrigation = False + if iniItems.landSurfaceOptions['includeIrrigation'] == "True": + self.includeIrrigation = True + self.coverTypes += ["irrPaddy","irrNonPaddy"] + logger.info("Irrigation is included/considered in this run.") + else: + logger.info("Irrigation is NOT included/considered in this run.") + + # if user define their land cover types: + if 'landCoverTypes' in list(iniItems.landSurfaceOptions.keys()): + self.coverTypes = iniItems.landSurfaceOptions['landCoverTypes'].split(",") + + # water demand options: irrigation efficiency, non irrigation water demand, and desalination supply + self.waterDemandOptions(iniItems) + + # TODO: Make an option so that users can easily perform natural runs (without water user, without reservoirs). + + # pre-defined surface water source fraction for satisfying irrigation and livestock water demand + self.swAbstractionFractionData = None + self.swAbstractionFractionDataQuality = None + if 'irrigationSurfaceWaterAbstractionFractionData' in list(iniItems.landSurfaceOptions.keys()) and\ + 'irrigationSurfaceWaterAbstractionFractionDataQuality' in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions['irrigationSurfaceWaterAbstractionFractionData'] not in ["None", "False"] or\ + iniItems.landSurfaceOptions['irrigationSurfaceWaterAbstractionFractionDataQuality'] not in ["None", "False"]: + + logger.info('Using/incorporating the predefined surface water source of Siebert et al. (2010) for satisfying irrigation and livestock demand.') + self.swAbstractionFractionData = pcr.cover(\ + vos.readPCRmapClone(iniItems.landSurfaceOptions['irrigationSurfaceWaterAbstractionFractionData'],\ + self.cloneMap,self.tmpDir,self.inputDir), 0.0) + self.swAbstractionFractionData = pcr.ifthen(self.swAbstractionFractionData >= 0.0, \ + self.swAbstractionFractionData ) + self.swAbstractionFractionDataQuality = \ + pcr.cover(\ + vos.readPCRmapClone(iniItems.landSurfaceOptions['irrigationSurfaceWaterAbstractionFractionDataQuality'],\ + self.cloneMap,self.tmpDir,self.inputDir), 0.0) + # ignore value with the quality above 5 (very bad) + # - Note: The resulting map has values only in cells with the data auality <= 5.0 + self.swAbstractionFractionData = pcr.ifthen(self.swAbstractionFractionDataQuality <= 5.0, \ + self.swAbstractionFractionData) + + # maximum pre-defined surface water source fraction for satisfying industrial and domestic water demand: + # - if not defined (default), set it to the maximum + self.maximumNonIrrigationSurfaceWaterAbstractionFractionData = pcr.scalar(1.0) + if 'maximumNonIrrigationSurfaceWaterAbstractionFractionData' in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions['maximumNonIrrigationSurfaceWaterAbstractionFractionData'] != "None" or\ + iniItems.landSurfaceOptions['maximumNonIrrigationSurfaceWaterAbstractionFractionData'] != "False": + + logger.info('Set the maximum fraction for predefined surface water source for satisfying domestic and industrial demand.') + self.maximumNonIrrigationSurfaceWaterAbstractionFractionData = pcr.min(1.0,\ + pcr.cover(\ + vos.readPCRmapClone(iniItems.landSurfaceOptions['maximumNonIrrigationSurfaceWaterAbstractionFractionData'],\ + self.cloneMap,self.tmpDir,self.inputDir), 1.0)) + + # pre-defined surface water source fraction for satisfying industrial and domestic water demand + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData = None + if 'predefinedNonIrrigationSurfaceWaterAbstractionFractionData' in list(iniItems.landSurfaceOptions.keys()) and \ + (iniItems.landSurfaceOptions['predefinedNonIrrigationSurfaceWaterAbstractionFractionData'] != "None" or \ + iniItems.landSurfaceOptions['predefinedNonIrrigationSurfaceWaterAbstractionFractionData'] != "False"): + + logger.info('Set the predefined fraction of surface water source for satisfying domestic and industrial demand.') + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData = pcr.min(1.0,\ + pcr.cover(\ + vos.readPCRmapClone(iniItems.landSurfaceOptions['predefinedNonIrrigationSurfaceWaterAbstractionFractionData'],\ + self.cloneMap,self.tmpDir,self.inputDir), 1.0)) + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData = pcr.max(0.0, \ + pcr.min(self.maximumNonIrrigationSurfaceWaterAbstractionFractionData, \ + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData)) + + # threshold values defining the preference for irrigation water source (unit: fraction/percentage) + self.treshold_to_maximize_irrigation_surface_water = \ + vos.readPCRmapClone(iniItems.landSurfaceOptions['treshold_to_maximize_irrigation_surface_water'],\ + self.cloneMap,self.tmpDir,self.inputDir) + self.treshold_to_minimize_fossil_groundwater_irrigation = \ + vos.readPCRmapClone(iniItems.landSurfaceOptions['treshold_to_minimize_fossil_groundwater_irrigation'],\ + self.cloneMap,self.tmpDir,self.inputDir) + + # assign the topography and soil parameters + self.soil_topo_parameters = {} + # - default values used for all land cover types + self.soil_topo_parameters['default'] = parSoilAndTopo.SoilAndTopoParameters(iniItems,self.landmask) + self.soil_topo_parameters['default'].read(iniItems) + # - specific soil and topography parameter (per land cover type) + for coverType in self.coverTypes: + name_of_section_given_in_ini_file = str(coverType)+'Options' + dictionary_of_land_cover_settings = iniItems.__getattribute__(name_of_section_given_in_ini_file) + + if 'usingSpecificSoilTopo' not in list(dictionary_of_land_cover_settings.keys()): dictionary_of_land_cover_settings['usingSpecificSoilTopo'] = "False" + if dictionary_of_land_cover_settings['usingSpecificSoilTopo'] == "True": + + msg = "Using a specific set of soil and topo parameters " + msg += "as defined in the "+name_of_section_given_in_ini_file+" of the ini/configuration file." + + self.soil_topo_parameters[coverType] = parSoilAndTopo.SoilAndTopoParameters(iniItems,self.landmask) + self.soil_topo_parameters[coverType].read(iniItems, dictionary_of_land_cover_settings) + else: + + msg = "Using the default set of soil and topo parameters " + msg += "as defined in the landSurfaceOptions of the ini/configuration file." + + self.soil_topo_parameters[coverType] = self.soil_topo_parameters['default'] + + logger.info(msg) + + + # instantiate self.landCoverObj[coverType] + self.landCoverObj = {} + for coverType in self.coverTypes: + self.landCoverObj[coverType] = lc.LandCover(iniItems,\ + str(coverType)+'Options',\ + self.soil_topo_parameters[coverType],self.landmask,\ + self.irrigationEfficiency,\ + self.usingAllocSegments) + + # rescale landCover Fractions + # - by default, the land cover fraction will always be corrected (to ensure the total of all fractions = 1.0) + self.noLandCoverFractionCorrection = False + if "noLandCoverFractionCorrection" in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions["noLandCoverFractionCorrection"] == "True": self.noLandCoverFractionCorrection = True + # - rescaling land cover fractions + if self.noLandCoverFractionCorrection == False: + self.scaleNaturalLandCoverFractions() + if self.includeIrrigation: self.scaleModifiedLandCoverFractions() + + # an option to introduce changes of land cover parameters (not only fracVegCover) + self.noAnnualChangesInLandCoverParameter = True + if 'annualChangesInLandCoverParameters' in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions['annualChangesInLandCoverParameters'] == "True": self.noAnnualChangesInLandCoverParameter = False + + # Note that "dynamicIrrigationArea" CANNOT be combined with "noLandCoverFractionCorrection" + if self.noLandCoverFractionCorrection: self.dynamicIrrigationArea = False + + # Also note that "noAnnualChangesInLandCoverParameter = False" must be followed by "noLandCoverFractionCorrection" + if self.noAnnualChangesInLandCoverParameter == False and self.noLandCoverFractionCorrection == False: + self.noLandCoverFractionCorrection = True + msg = "WARNING! No land cover fraction correction will be performed. Please make sure that the 'total' of all fracVegCover adds to one." + logger.warning(msg) + logger.warning(msg) + logger.warning(msg) + logger.warning(msg) + logger.warning(msg) + + ######################################################################################################################################################################################### + # 29 July 2014: + # + # If using historical/dynamic irrigation file (changing every year), we have to get fraction over irrigation area + # (in order to calculate irrigation area for each irrigation type) + # + # Note that: totalIrrAreaFrac = fraction irrigated areas (e.g. paddy + nonPaddy) over the entire cell area (dimensionless) ; this value changes (if self.dynamicIrrigationArea = True) + # irrTypeFracOverIrr = fraction each land cover type (paddy or nonPaddy) over the irrigation area (dimensionless) ; this value is constant for the entire simulation + # + if self.dynamicIrrigationArea: + + logger.info('Determining fraction of total irrigated areas over each cell') + # Note that this is needed ONLY if historical irrigation areas are used (if self.dynamicIrrigationArea = True). + + # total irrigated area fraction (over the entire cell) + totalIrrAreaFrac = 0.0 + for coverType in self.coverTypes: + if coverType.startswith('irr'): + totalIrrAreaFrac += self.landCoverObj[coverType].fracVegCover + + # fraction over irrigation area + for coverType in self.coverTypes: + if coverType.startswith('irr'): + self.landCoverObj[coverType].irrTypeFracOverIrr = vos.getValDivZero(self.landCoverObj[coverType].fracVegCover,\ + totalIrrAreaFrac, vos.smallNumber) + + # get the initial conditions (for every land cover type) + self.getInitialConditions(iniItems, initialState) + + # initiate old style reporting (this is useful for debuging) + self.initiate_old_style_land_surface_reporting(iniItems) + + + def initiate_old_style_land_surface_reporting(self,iniItems): + + self.report = True + try: + self.outDailyTotNC = iniItems.landSurfaceOptions['outDailyTotNC'].split(",") + self.outMonthTotNC = iniItems.landSurfaceOptions['outMonthTotNC'].split(",") + self.outMonthAvgNC = iniItems.landSurfaceOptions['outMonthAvgNC'].split(",") + self.outMonthEndNC = iniItems.landSurfaceOptions['outMonthEndNC'].split(",") + self.outAnnuaTotNC = iniItems.landSurfaceOptions['outAnnuaTotNC'].split(",") + self.outAnnuaAvgNC = iniItems.landSurfaceOptions['outAnnuaAvgNC'].split(",") + self.outAnnuaEndNC = iniItems.landSurfaceOptions['outAnnuaEndNC'].split(",") + except: + self.report = False + if self.report == True: + self.outNCDir = iniItems.outNCDir + self.netcdfObj = PCR2netCDF(iniItems) + # + # daily output in netCDF files: + if self.outDailyTotNC[0] != "None": + for var in self.outDailyTotNC: + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_dailyTot.nc",\ + var,"undefined") + # MONTHly output in netCDF files: + # - cummulative + if self.outMonthTotNC[0] != "None": + for var in self.outMonthTotNC: + # initiating monthlyVarTot (accumulator variable): + vars(self)[var+'MonthTot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthTot.nc",\ + var,"undefined") + # - average + if self.outMonthAvgNC[0] != "None": + for var in self.outMonthAvgNC: + # initiating monthlyTotAvg (accumulator variable) + vars(self)[var+'MonthTot'] = None + # initiating monthlyVarAvg: + vars(self)[var+'MonthAvg'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthAvg.nc",\ + var,"undefined") + # - last day of the month + if self.outMonthEndNC[0] != "None": + for var in self.outMonthEndNC: + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthEnd.nc",\ + var,"undefined") + # YEARly output in netCDF files: + # - cummulative + if self.outAnnuaTotNC[0] != "None": + for var in self.outAnnuaTotNC: + # initiating yearly accumulator variable: + vars(self)[var+'AnnuaTot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaTot.nc",\ + var,"undefined") + # - average + if self.outAnnuaAvgNC[0] != "None": + for var in self.outAnnuaAvgNC: + # initiating annualyVarAvg: + vars(self)[var+'AnnuaAvg'] = None + # initiating annualyTotAvg (accumulator variable) + vars(self)[var+'AnnuaTot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaAvg.nc",\ + var,"undefined") + # - last day of the year + if self.outAnnuaEndNC[0] != "None": + for var in self.outAnnuaEndNC: + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaEnd.nc",\ + var,"undefined") + + def getInitialConditions(self, iniItems, iniConditions = None): + + # starting year in integer + starting_year = int(iniItems.globalOptions['startTime'][0:4]) + # + # check if the run start at the first day of the year: + start_on_1_Jan = False + if iniItems.globalOptions['startTime'][-5:] == "01-01": start_on_1_Jan = True + + # condition to consider previous year land cover fraction + consider_previous_year_land_cover_fraction = False + + + ####################################################################################################################################### + # obtaining initial land cover fractions for runs with dynamicIrrigationArea + # + # For non spin-up runs that start at the first day of the year (1 January), + # - we have to consider the previous year land cover fractions, specifically if we consider the dynamic/expansion of irrigation areas + # + if iniConditions == None and start_on_1_Jan == True and \ + self.dynamicIrrigationArea and self.noLandCoverFractionCorrection == False: + # obtain the previous year land cover fractions: + self.scaleDynamicIrrigation(starting_year - 1) # the previous year land cover fractions + consider_previous_year_land_cover_fraction = True + # + # For spin-up runs or for runs that start after 1 January, + # - we do not have to consider the previous year land cover fractions + # + if consider_previous_year_land_cover_fraction == False and \ + self.dynamicIrrigationArea and self.noLandCoverFractionCorrection == False: + # just using the current year land cover fractions: + self.scaleDynamicIrrigation(starting_year) # the current year land cover fractions + # + ################################################################################################################################# + + ####################################################################################################################################### + # obtaining initial land cover fractions for runs with noLandCoverFractionCorrection and annualChangesInLandCoverParameters + # + # For non spin-up runs that start at the first day of the year (1 January), + # - we have to consider the previous year land cover fractions + # + if iniConditions == None and start_on_1_Jan == True and \ + self.noLandCoverFractionCorrection and self.noAnnualChangesInLandCoverParameter == False: + # obtain the previous year land cover fractions: + previous_year = starting_year - 1 + one_january_prev_year = str(previous_year)+"-01-01" + for coverType in self.coverTypes: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].get_land_cover_parameters(date_in_string = one_january_prev_year, \ + get_only_fracVegCover = True) + + #################################################################################################################################################################### + # correcting land cover fractions + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].previousFracVegCover + + if 'grassland' in list(self.landCoverObj.keys()): + self.landCoverObj['grassland'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].previousFracVegCover, 1.0) + + if 'short_natural' in list(self.landCoverObj.keys()): + self.landCoverObj['short_natural'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].previousFracVegCover, 1.0) + + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].previousFracVegCover + + for coverType in self.coverTypes: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].previousFracVegCover / total_fractions + #################################################################################################################################################################### + + consider_previous_year_land_cover_fraction = True + + # For spin-up runs or for runs that start after 1 January, + # - we do not have to consider the previous year land cover fractions + # + if consider_previous_year_land_cover_fraction == False and \ + self.noLandCoverFractionCorrection and self.noAnnualChangesInLandCoverParameter == False: + # just using the current year land cover fractions: + one_january_this_year = str(starting_year)+"-01-01" + for coverType in self.coverTypes: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].get_land_cover_parameters(date_in_string = one_january_this_year, \ + get_only_fracVegCover = True) + + #################################################################################################################################################################### + # correcting land cover fractions + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].previousFracVegCover + + if 'grassland' in list(self.landCoverObj.keys()): + self.landCoverObj['grassland'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].previousFracVegCover, 1.0) + + if 'short_natural' in list(self.landCoverObj.keys()): + self.landCoverObj['short_natural'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].previousFracVegCover, 1.0) + + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].previousFracVegCover + + for coverType in self.coverTypes: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].previousFracVegCover / total_fractions + #################################################################################################################################################################### + + + # get initial conditions + # - first, we set all aggregated states to zero (only the ones in mainStates): + for var in self.mainStates: vars(self)[var] = pcr.scalar(0.0) + # - then we initiate them in the following loop of land cover types: + for coverType in self.coverTypes: + if iniConditions != None: + self.landCoverObj[coverType].getICsLC(iniItems,iniConditions['landSurface'][coverType]) + else: + self.landCoverObj[coverType].getICsLC(iniItems) + # summarize/aggregate the initial states/storages (using the initial land cover fractions: previousFracVegCover) + for var in self.mainStates: + # - initial land cover fractions (dimensionless) + if self.landCoverObj[coverType].previousFracVegCover is None: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].fracVegCover + land_cover_fraction = self.landCoverObj[coverType].previousFracVegCover + # - initial land cover states (unit: m) + land_cover_states = vars(self.landCoverObj[coverType])[var] + vars(self)[var] += land_cover_states * land_cover_fraction + + def waterDemandOptions(self,iniItems): + + # domestic water demand (unit: m/day) + # + self.domesticWaterDemandOption = False + if iniItems.landSurfaceOptions['includeDomesticWaterDemand'] == "True": + logger.info("Domestic water demand is included in the calculation.") + self.domesticWaterDemandOption = True + else: + logger.info("Domestic water demand is NOT included in the calculation.") + # + if self.domesticWaterDemandOption: + self.domesticWaterDemandFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['domesticWaterDemandFile'],self.inputDir,False) + + # industry water demand (unit: m/day) + # + self.industryWaterDemandOption = False + if iniItems.landSurfaceOptions['includeIndustryWaterDemand'] == "True": + logger.info("Industry water demand is included in the calculation.") + self.industryWaterDemandOption = True + else: + logger.info("Industry water demand is NOT included in the calculation.") + # + if self.industryWaterDemandOption: + self.industryWaterDemandFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['industryWaterDemandFile'],self.inputDir,False) + + # livestock water demand (unit: m/day) + self.livestockWaterDemandOption = False + if iniItems.landSurfaceOptions['includeLivestockWaterDemand'] == "True": + logger.info("Livestock water demand is included in the calculation.") + self.livestockWaterDemandOption = True + else: + logger.info("Livestock water demand is NOT included in the calculation.") + # + if self.livestockWaterDemandOption: + self.livestockWaterDemandFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['livestockWaterDemandFile'],self.inputDir,False) + + # historical irrigation area (unit: hectar) + self.dynamicIrrigationArea = False + if iniItems.landSurfaceOptions['historicalIrrigationArea'] != "None": + logger.info("Using the dynamicIrrigationArea option. Extent of irrigation areas is based on the file provided in the 'historicalIrrigationArea'.") + self.dynamicIrrigationArea = True + # + if self.dynamicIrrigationArea: + self.dynamicIrrigationAreaFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['historicalIrrigationArea'],self.inputDir,False) + + # irrigation efficiency map (in percentage) # TODO: Using the time series of efficiency (considering historical technological development). + self.irrigationEfficiency = vos.readPCRmapClone(\ + iniItems.landSurfaceOptions['irrigationEfficiency'], + self.cloneMap,self.tmpDir,self.inputDir) + + extrapolate = True + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False + + if extrapolate: + + # extrapolate efficiency map: # TODO: Make a better extrapolation algorithm (considering cell size, etc.). + + window_size = 1.25 * pcr.clone().cellSize() + window_size = min(window_size, min(pcr.clone().nrRows(), pcr.clone().nrCols())*pcr.clone().cellSize()) + try: + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 0.75)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.00)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.50)) + except: + pass + + #~ self.irrigationEfficiency = pcr.ifthen(self.landmask, self.irrigationEfficiency) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, 1.0) + self.irrigationEfficiency = pcr.max(0.1, self.irrigationEfficiency) + self.irrigationEfficiency = pcr.ifthen(self.landmask, self.irrigationEfficiency) + + # desalination water supply option + self.includeDesalination = False + if iniItems.landSurfaceOptions['desalinationWater'] not in ["None", "False"]: + logger.info("Monthly desalination water is included.") + self.includeDesalination = True + self.desalinationWaterFile = vos.getFullPath(iniItems.landSurfaceOptions['desalinationWater'], self.inputDir) + else: + logger.info("Monthly desalination water is NOT included.") + + # zones at which water allocation (surface and groundwater allocation) is determined + self.usingAllocSegments = False + self.allocSegments = None + if iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'] != "None": + self.usingAllocSegments = True + + self.allocSegments = vos.readPCRmapClone(\ + iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'], + self.cloneMap,self.tmpDir,self.inputDir,isLddMap=False,cover=None,isNomMap=True) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + self.allocSegments = pcr.clump(self.allocSegments) + + extrapolate = True + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False + + if extrapolate: + # extrapolate it + self.allocSegments = pcr.cover(self.allocSegments, \ + pcr.windowmajority(self.allocSegments, 0.5)) + + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # clump it and cover the rests with cell ids + self.allocSegments = pcr.clump(self.allocSegments) + cell_ids = pcr.mapmaximum(pcr.scalar(self.allocSegments)) + pcr.scalar(100.0) + pcr.uniqueid(pcr.boolean(1.0)) + self.allocSegments = pcr.cover(self.allocSegments, pcr.nominal(cell_ids)) + self.allocSegments = pcr.clump(self.allocSegments) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # cell area (unit: m2) + cellArea = vos.readPCRmapClone(\ + iniItems.routingOptions['cellAreaMap'], + self.cloneMap,self.tmpDir,self.inputDir) + cellArea = pcr.ifthen(self.landmask, cellArea) + + # zonal/segment area (unit: m2) + self.segmentArea = pcr.areatotal(pcr.cover(cellArea, 0.0), self.allocSegments) + self.segmentArea = pcr.ifthen(self.landmask, self.segmentArea) + + else: + + logger.info("If there is any, water demand is satisfied by local source only.") + + + def scaleNaturalLandCoverFractions(self): + ''' rescales natural land cover fractions (make sure the total = 1)''' + + # total land cover fractions + pristineAreaFrac = 0.0 + numb_of_lc_types = 0.0 + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + pristineAreaFrac += pcr.cover(self.landCoverObj[coverType].fracVegCover, 0.0) + numb_of_lc_types += 1.0 + + # Fill cells with pristineAreaFrac < 0.0 - with window average value within 0.5 and 1.5 degree + for coverType in self.coverTypes: + + if not coverType.startswith('irr'): + + extrapolate = True + if "noParameterExtrapolation" in self.iniItems.landSurfaceOptions.keys() and self.iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False + + if extrapolate: + filled_fractions = pcr.windowaverage(self.landCoverObj[coverType].fracVegCover,0.5) + filled_fractions = pcr.cover(filled_fractions,\ + pcr.windowaverage(self.landCoverObj[coverType].fracVegCover,1.5)) + filled_fractions = pcr.max(0.0, filled_fractions) + filled_fractions = pcr.min(1.0, filled_fractions) + + self.landCoverObj[coverType].fracVegCover = pcr.ifthen(pristineAreaFrac >= 0.0, self.landCoverObj[coverType].fracVegCover) + self.landCoverObj[coverType].fracVegCover = pcr.cover(\ + self.landCoverObj[coverType].fracVegCover,filled_fractions) + self.landCoverObj[coverType].fracVegCover = pcr.ifthen(self.landmask,\ + self.landCoverObj[coverType].fracVegCover) + + # re-check total land cover fractions + pristineAreaFrac = 0.0 + numb_of_lc_types = 0.0 + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + pristineAreaFrac += pcr.cover(self.landCoverObj[coverType].fracVegCover, 0.0) + numb_of_lc_types += 1.0 + + # Fill cells with pristineAreaFrac = 0.0: + # - NOTE this only works for certain land cover names. TODO: FIX THIS + try: + self.landCoverObj['forest'].fracVegCover = pcr.ifthenelse(pristineAreaFrac > 0.0, self.landCoverObj['forest'].fracVegCover, 0.0) + self.landCoverObj['forest'].fracVegCover = pcr.min(1.0, self.landCoverObj['forest'].fracVegCover) + self.landCoverObj['grassland'].fracVegCover = 1.0 - self.landCoverObj['forest'].fracVegCover + except: + pass + + # recalculate total land cover fractions + pristineAreaFrac = 0.0 + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + pristineAreaFrac += pcr.cover(self.landCoverObj[coverType].fracVegCover, 0.0) + + # correcting + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + self.landCoverObj[coverType].fracVegCover = \ + self.landCoverObj[coverType].fracVegCover / pristineAreaFrac + + pristineAreaFrac = 0.0 # reset + # + # checking pristineAreaFrac (must be equal to 1) + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + pristineAreaFrac += self.landCoverObj[coverType].fracVegCover + self.landCoverObj[coverType].naturalFracVegCover = \ + self.landCoverObj[coverType].fracVegCover + # + # check and make sure that totalArea = 1.0 for all cells + totalArea = pristineAreaFrac + totalArea = pcr.ifthen(self.landmask,totalArea) + totalArea = pcr.cover(totalArea, 1.0) + check_map = totalArea - pcr.scalar(1.0) + a,b,c = vos.getMinMaxMean(check_map) + threshold = 1e-4 + if abs(a) > threshold or abs(b) > threshold: + logger.error("total of 'Natural Area' fractions is not equal to 1.0 ... Min %f Max %f Mean %f" %(a,b,c)) + + def scaleModifiedLandCoverFractions(self): + ''' rescales the land cover fractions with irrigation areas''' + + # calculate irrigatedAreaFrac (fraction of irrigation areas) + irrigatedAreaFrac = pcr.spatial(pcr.scalar(0.0)) + for coverType in self.coverTypes: + if coverType.startswith('irr'): + irrigatedAreaFrac = irrigatedAreaFrac + self.landCoverObj[coverType].fracVegCover + + # correcting/scaling fracVegCover of irrigation if irrigatedAreaFrac > 1 + for coverType in self.coverTypes: + if coverType.startswith('irr'): + self.landCoverObj[coverType].fracVegCover = pcr.ifthenelse(irrigatedAreaFrac > 1.0,\ + self.landCoverObj[coverType].fracVegCover/irrigatedAreaFrac,\ + self.landCoverObj[coverType].fracVegCover) + + # the corrected irrigated area fraction + irrigatedAreaFrac = pcr.spatial(pcr.scalar(0.0)) + for coverType in self.coverTypes: + if coverType.startswith('irr'): + irrigatedAreaFrac += self.landCoverObj[coverType].fracVegCover + + totalArea = pcr.spatial(pcr.scalar(0.0)) + totalArea += irrigatedAreaFrac + + # correction factor for forest and grassland (pristine Areas) + lcFrac = pcr.max(0.0, 1.0 - totalArea) + pristineAreaFrac = pcr.spatial(pcr.scalar(0.0)) + + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + self.landCoverObj[coverType].fracVegCover = 0.0 + self.landCoverObj[coverType].fracVegCover = \ + self.landCoverObj[coverType].naturalFracVegCover * lcFrac + pristineAreaFrac += pcr.cover(\ + self.landCoverObj[coverType].fracVegCover, 0.0) + + # check and make sure that totalArea = 1.0 for all cells + totalArea += pristineAreaFrac + totalArea = pcr.ifthen(self.landmask,totalArea) + totalArea = pcr.cover(totalArea, 1.0) + totalArea = pcr.ifthen(self.landmask,totalArea) + a,b,c = vos.getMinMaxMean(totalArea - pcr.scalar(1.0)) + threshold = 1e-4 + if abs(a) > threshold or abs(b) > threshold: + logger.error("fraction total (from all land cover types) is not equal to 1.0 ... Min %f Max %f Mean %f" %(a,b,c)) + + def obtainNonIrrWaterDemand(self,routing,currTimeStep): + # get NON-Irrigation GROSS water demand and its return flow fraction + + # domestic water demand + if currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1: + if self.domesticWaterDemandOption: + # + if self.domesticWaterDemandFile.endswith(vos.netcdf_suffixes): + # + self.domesticGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.domesticWaterDemandFile,\ + 'domesticGrossDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + # + self.domesticNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.domesticWaterDemandFile,\ + 'domesticNettoDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + else: + string_month = str(currTimeStep.month) + if currTimeStep.month < 10: string_month = "0"+str(currTimeStep.month) + grossFileName = self.domesticWaterDemandFile+"w"+str(currTimeStep.year)+".0"+string_month + self.domesticGrossDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(grossFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + nettoFileName = self.domesticWaterDemandFile+"n"+str(currTimeStep.year)+".0"+string_month + self.domesticNettoDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(nettoFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + else: + self.domesticGrossDemand = pcr.spatial(pcr.scalar(0.0)) + self.domesticNettoDemand = pcr.spatial(pcr.scalar(0.0)) + logger.debug("Domestic water demand is NOT included.") + + # gross and netto domestic water demand in m/day + self.domesticGrossDemand = pcr.cover(self.domesticGrossDemand,0.0) + self.domesticNettoDemand = pcr.cover(self.domesticNettoDemand,0.0) + self.domesticNettoDemand = pcr.min(self.domesticGrossDemand, self.domesticNettoDemand) + + # industry water demand + if currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1: + if self.industryWaterDemandOption: + # + if self.industryWaterDemandFile.endswith(vos.netcdf_suffixes): + # + self.industryGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.industryWaterDemandFile,\ + 'industryGrossDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + # + self.industryNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.industryWaterDemandFile,\ + 'industryNettoDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + else: + grossFileName = self.industryWaterDemandFile+"w"+str(currTimeStep.year)+".map" + self.industryGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.readPCRmapClone(grossFileName,self.cloneMap,self.tmpDir), 0.0)) + nettoFileName = self.industryWaterDemandFile+"n"+str(currTimeStep.year)+".map" + self.industryNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.readPCRmapClone(nettoFileName,self.cloneMap,self.tmpDir), 0.0)) + else: + self.industryGrossDemand = pcr.spatial(pcr.scalar(0.0)) + self.industryNettoDemand = pcr.spatial(pcr.scalar(0.0)) + logger.debug("Industry water demand is NOT included.") + + # gross and netto industrial water demand in m/day + self.industryGrossDemand = pcr.cover(self.industryGrossDemand,0.0) + self.industryNettoDemand = pcr.cover(self.industryNettoDemand,0.0) + self.industryNettoDemand = pcr.min(self.industryGrossDemand, self.industryNettoDemand) + + # livestock water demand + if currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1: + if self.livestockWaterDemandOption: + # + if self.livestockWaterDemandFile.endswith(vos.netcdf_suffixes): + # + self.livestockGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.livestockWaterDemandFile,\ + 'livestockGrossDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + # + self.livestockNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.livestockWaterDemandFile,\ + 'livestockNettoDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + else: + string_month = str(currTimeStep.month) + if currTimeStep.month < 10: string_month = "0"+str(currTimeStep.month) + grossFileName = self.livestockWaterDemandFile+"w"+str(currTimeStep.year)+".0"+string_month + self.livestockGrossDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(grossFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + nettoFileName = self.livestockWaterDemandFile+"n"+str(currTimeStep.year)+".0"+string_month + self.livestockNettoDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(nettoFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + else: + self.livestockGrossDemand = pcr.spatial(pcr.scalar(0.0)) + self.livestockNettoDemand = pcr.spatial(pcr.scalar(0.0)) + logger.debug("Livestock water demand is NOT included.") + + # gross and netto livestock water demand in m/day + self.livestockGrossDemand = pcr.cover(self.livestockGrossDemand,0.0) + self.livestockNettoDemand = pcr.cover(self.livestockNettoDemand,0.0) + self.livestockNettoDemand = pcr.min(self.livestockGrossDemand, self.livestockNettoDemand) + + # GROSS domestic, industrial and livestock water demands (unit: m/day) + self.domesticGrossDemand = pcr.ifthen(self.landmask, self.domesticGrossDemand ) + self.domesticNettoDemand = pcr.ifthen(self.landmask, self.domesticNettoDemand ) + self.industryGrossDemand = pcr.ifthen(self.landmask, self.industryGrossDemand ) + self.industryNettoDemand = pcr.ifthen(self.landmask, self.industryNettoDemand ) + self.livestockGrossDemand = pcr.ifthen(self.landmask, self.livestockGrossDemand) + self.livestockNettoDemand = pcr.ifthen(self.landmask, self.livestockNettoDemand) + + # RETURN FLOW fractions for domestic, industrial and livestock water demands (unit: fraction/percentage) + self.domesticReturnFlowFraction = pcr.min(1.0, pcr.max(0.0, 1.0 - vos.getValDivZero(self.domesticNettoDemand, self.domesticGrossDemand))) + self.industryReturnFlowFraction = pcr.min(1.0, pcr.max(0.0, 1.0 - vos.getValDivZero(self.industryNettoDemand, self.industryGrossDemand))) + self.livestockReturnFlowFraction = pcr.min(1.0, pcr.max(0.0, 1.0 - vos.getValDivZero(self.livestockNettoDemand, self.livestockGrossDemand))) + + # make a dictionary summarizing potential demand (potential withdrawal) and its return flow fraction + nonIrrigationWaterDemandDict = {} + nonIrrigationWaterDemandDict['potential_demand'] = {} + nonIrrigationWaterDemandDict['potential_demand']['domestic'] = self.domesticGrossDemand + nonIrrigationWaterDemandDict['potential_demand']['industry'] = self.industryGrossDemand + nonIrrigationWaterDemandDict['potential_demand']['livestock'] = self.livestockGrossDemand + nonIrrigationWaterDemandDict['return_flow_fraction'] = {} + nonIrrigationWaterDemandDict['return_flow_fraction']['domestic'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.domesticReturnFlowFraction *1000.)/1000.), 1.0) + nonIrrigationWaterDemandDict['return_flow_fraction']['industry'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.industryReturnFlowFraction *1000.)/1000.), 1.0) + nonIrrigationWaterDemandDict['return_flow_fraction']['livestock'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.livestockReturnFlowFraction*1000.)/1000.), 1.0) + + return nonIrrigationWaterDemandDict + + def calculateCapRiseFrac(self,groundwater,routing,currTimeStep): + # calculate cell fraction influenced by capillary rise: + + # relative groundwater head (m) above the minimum elevation within a grid cell + if groundwater.useMODFLOW == True: + + dzGroundwater = groundwater.relativeGroundwaterHead + + # update dzGroundwater from file, from modflow calculation, using the previous time step + # - assumption that it will be updated once every month + + if currTimeStep.day == 1 and currTimeStep.timeStepPCR > 1: + + # for online coupling, we will read files from pcraster maps + directory = self.iniItems.main_output_directory + "/modflow/transient/maps/" + + # - relative groundwater head from MODFLOW + yesterday = str(currTimeStep.yesterday()) + filename = directory + "relativeGroundwaterHead_" + str(yesterday) + ".map" + dzGroundwater = pcr.ifthen(self.landmask, pcr.cover(vos.readPCRmapClone(filename, self.cloneMap, self.tmpDir), 0.0)) + + else: + dzGroundwater = groundwater.storGroundwater/groundwater.specificYield + + # add some tolerance/influence level (unit: m) + dzGroundwater += self.soil_topo_parameters['default'].maxGWCapRise; + + # set minimum value to zero (zero relativeGroundwaterHead indicate no capRiseFrac) + dzGroundwater = pcr.max(0.0, dzGroundwater) + + # approximate cell fraction under influence of capillary rise + + FRACWAT = pcr.spatial(pcr.scalar(0.0)); + if currTimeStep.timeStepPCR > 1: + FRACWAT = pcr.cover(routing.WaterBodies.fracWat, 0.0); + else: + if routing.includeWaterBodies: + if routing.WaterBodies.useNetCDF: + routing.WaterBodies.fracWat = vos.netcdf2PCRobjClone(\ + routing.WaterBodies.ncFileInp,'fracWaterInp', \ + currTimeStep.fulldate, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap) + else: + if routing.WaterBodies.fracWaterInp != "None": + routing.WaterBodies.fracWat = vos.readPCRmapClone(\ + routing.WaterBodies.fracWaterInp+str(currTimeStep.year)+".map", + self.cloneMap,self.tmpDir,self.inputDir) + else: + routing.WaterBodies.fracWat = pcr.spatial(pcr.scalar(0.0)) + else: + if routing.WaterBodies.useNetCDF: + routing.WaterBodies.fracWat = vos.netcdf2PCRobjClone(\ + routing.WaterBodies.ncFileInp,'fracWaterInp', \ + currTimeStep.fulldate, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap) + else: + if routing.WaterBodies.fracWaterInp != "None": + routing.WaterBodies.fracWat = vos.readPCRmapClone(\ + routing.WaterBodies.fracWaterInp, + self.cloneMap,self.tmpDir,self.inputDir) + else: + routing.WaterBodies.fracWat = pcr.spatial(pcr.scalar(0.0)) + # Note that the variable used in the following line is FRACWAT (this may be a 'small' bug fixing to the GMD paper version) + FRACWAT = pcr.cover(routing.WaterBodies.fracWat, 0.0); + FRACWAT = pcr.cover(FRACWAT, 0.0) + + # zero fracwat assumption used for debugging against version 1.0 + if routing.zeroFracWatAllAndAlways: FRACWAT = pcr.scalar(0.0) + + + CRFRAC = pcr.min( 1.0,1.0 -(self.soil_topo_parameters['default'].dzRel0100-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0100-self.soil_topo_parameters['default'].dzRel0090) ); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0090,0.9 -(self.soil_topo_parameters['default'].dzRel0090-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0090-self.soil_topo_parameters['default'].dzRel0080),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0080,0.8 -(self.soil_topo_parameters['default'].dzRel0080-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0080-self.soil_topo_parameters['default'].dzRel0070),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0070,0.7 -(self.soil_topo_parameters['default'].dzRel0070-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0070-self.soil_topo_parameters['default'].dzRel0060),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0060,0.6 -(self.soil_topo_parameters['default'].dzRel0060-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0060-self.soil_topo_parameters['default'].dzRel0050),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0050,0.5 -(self.soil_topo_parameters['default'].dzRel0050-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0050-self.soil_topo_parameters['default'].dzRel0040),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0040,0.4 -(self.soil_topo_parameters['default'].dzRel0040-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0040-self.soil_topo_parameters['default'].dzRel0030),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0030,0.3 -(self.soil_topo_parameters['default'].dzRel0030-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0030-self.soil_topo_parameters['default'].dzRel0020),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0020,0.2 -(self.soil_topo_parameters['default'].dzRel0020-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0020-self.soil_topo_parameters['default'].dzRel0010),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0010,0.1 -(self.soil_topo_parameters['default'].dzRel0010-dzGroundwater)*0.05/pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0010-self.soil_topo_parameters['default'].dzRel0005),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0005,0.05-(self.soil_topo_parameters['default'].dzRel0005-dzGroundwater)*0.04/pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0005-self.soil_topo_parameters['default'].dzRel0001),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0001,0.01-(self.soil_topo_parameters['default'].dzRel0001-dzGroundwater)*0.01/pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0001 ),CRFRAC); + + #~ print(FRACWAT) + + CRFRAC = pcr.ifthenelse(FRACWAT < 1.0,pcr.max(0.0,CRFRAC-FRACWAT)/(1.0-FRACWAT),0.0); + + capRiseFrac = pcr.max(0.0,pcr.min(1.0,CRFRAC)) + + #~ capRiseFrac = 0.0 + + return capRiseFrac + + def partitioningGroundSurfaceAbstraction(self,groundwater,routing): + + # partitioning abstraction sources: groundwater and surface water + # de Graaf et al., 2014 principle: partitioning based on local average baseflow (m3/s) and upstream average discharge (m3/s) + # - estimates of fractions of groundwater and surface water abstractions + averageBaseflowInput = routing.avgBaseflow + averageUpstreamInput = pcr.max(routing.avgDischarge, pcr.cover(pcr.upstream(routing.lddMap, routing.avgDischarge), 0.0)) + + if self.usingAllocSegments: + + averageBaseflowInput = pcr.max(0.0, pcr.ifthen(self.landmask, averageBaseflowInput)) + averageUpstreamInput = pcr.max(0.0, pcr.ifthen(self.landmask, averageUpstreamInput)) + + averageBaseflowInput = pcr.cover(pcr.areaaverage(averageBaseflowInput, self.allocSegments), 0.0) + averageUpstreamInput = pcr.cover(pcr.areamaximum(averageUpstreamInput, self.allocSegments), 0.0) + + else: + logger.debug("Water demand can only be satisfied by local source.") + + swAbstractionFraction = vos.getValDivZero(\ + averageUpstreamInput, + averageUpstreamInput+averageBaseflowInput, vos.smallNumber) + swAbstractionFraction = pcr.roundup(swAbstractionFraction*100.)/100. + swAbstractionFraction = pcr.max(0.0, swAbstractionFraction) + swAbstractionFraction = pcr.min(1.0, swAbstractionFraction) + + if self.usingAllocSegments: + swAbstractionFraction = pcr.areamaximum(swAbstractionFraction, self.allocSegments) + + swAbstractionFraction = pcr.cover(swAbstractionFraction, 1.0) + swAbstractionFraction = pcr.ifthen(self.landmask, swAbstractionFraction) + + # making a dictionary containing the surface water fraction for various purpose + swAbstractionFractionDict = {} + # - the default estimate (based on de Graaf et al., 2014) + swAbstractionFractionDict['estimate'] = swAbstractionFraction + # - for irrigation and livestock purpose + swAbstractionFractionDict['irrigation'] = swAbstractionFraction + # - for industrial and domestic purpose + swAbstractionFractionDict['max_for_non_irrigation'] = swAbstractionFraction + # + # - a treshold fraction value to optimize/maximize surface water withdrawal for irrigation + # Principle: Areas with swAbstractionFractionDict['irrigation'] above this treshold will prioritize surface water use for irrigation purpose. + # A zero treshold value will ignore this principle. + swAbstractionFractionDict['treshold_to_maximize_irrigation_surface_water'] = self.treshold_to_maximize_irrigation_surface_water + # + # - a treshold fraction value to minimize fossil groundwater withdrawal, particularly to remove the unrealistic areas of fossil groundwater abstraction + # Principle: Areas with swAbstractionFractionDict['irrigation'] above this treshold will not extract fossil groundwater. + swAbstractionFractionDict['treshold_to_minimize_fossil_groundwater_irrigation'] = self.treshold_to_minimize_fossil_groundwater_irrigation + + + # the default value of surface water source fraction is None or not defined (in this case, this value will be the 'estimate' and limited with 'max_for_non_irrigation') + swAbstractionFractionDict['non_irrigation'] = None + + # incorporating the pre-defined fraction of surface water sources (e.g. based on Siebert et al., 2014 and McDonald et al., 2014) + if self.swAbstractionFractionData is not None: + + logger.debug('Using/incorporating the predefined fractions of surface water source.') + swAbstractionFractionDict['estimate'] = swAbstractionFraction + swAbstractionFractionDict['irrigation'] = self.partitioningGroundSurfaceAbstractionForIrrigation(swAbstractionFraction,\ + self.swAbstractionFractionData,\ + self.swAbstractionFractionDataQuality) + swAbstractionFractionDict['max_for_non_irrigation'] = self.maximumNonIrrigationSurfaceWaterAbstractionFractionData + + if self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData is not None: + swAbstractionFractionDict['non_irrigation'] = pcr.cover( + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData, \ + swAbstractionFractionDict['estimate']) + swAbstractionFractionDict['non_irrigation'] = pcr.min(\ + swAbstractionFractionDict['non_irrigation'], \ + swAbstractionFractionDict['max_for_non_irrigation']) + + else: + + logger.debug('NOT using/incorporating the predefined fractions of surface water source.') + + return swAbstractionFractionDict + + def partitioningGroundSurfaceAbstractionForIrrigation(self,\ + swAbstractionFractionEstimate,\ + swAbstractionFractionData,\ + swAbstractionFractionDataQuality): + + # surface water source fraction based on Stefan Siebert's map: + factor = 0.5 # using this factor, the minimum value for the following 'data_weight_value' is 0.75 (for swAbstractionFractionDataQuality == 5) + data_weight_value = pcr.scalar(1.0) - \ + (pcr.min(5., pcr.max(0.0, swAbstractionFractionDataQuality))/10.0)*factor + + swAbstractionFractionForIrrigation = data_weight_value * swAbstractionFractionData +\ + (1.0 - data_weight_value) * swAbstractionFractionEstimate + + swAbstractionFractionForIrrigation = pcr.cover(swAbstractionFractionForIrrigation, swAbstractionFractionEstimate) + swAbstractionFractionForIrrigation = pcr.cover(swAbstractionFractionForIrrigation, 1.0) + swAbstractionFractionForIrrigation = pcr.ifthen(self.landmask, swAbstractionFractionForIrrigation) + + return swAbstractionFractionForIrrigation + + def scaleDynamicIrrigation(self,yearInInteger): + # This method is to update fracVegCover of landCover for historical irrigation areas (done at yearly basis). + + + #~ # Available datasets are only from 1960 to 2010 (status on 24 September 2010) + #~ yearInInteger = int(yearInInteger) + #~ if float(yearInInteger) < 1960. or float(yearInInteger) > 2010.: + #~ msg = 'Dataset for the year '+str(yearInInteger)+" is not available. Dataset of historical irrigation areas is only available from 1960 to 2010." + #~ logger.warning(msg) + #~ yearInInteger = min(2010, max(1960, yearInInteger)) + # + # TODO: Generally, I do not need the aforementioned lines as I have defined the functions "findLastYearInNCTime" and "findFirstYearInNCTime" in the module virtualOS.py + # However, Niko still need them for his DA scheme as we somehow his DA scheme cannot handle the netcdf file of historical irrigation areas (and therefore we have to use pcraster map files). + + + yearInString = str(yearInInteger) + + # read historical irrigation areas + if self.dynamicIrrigationAreaFile.endswith(('.nc4','.nc')): + fulldateInString = yearInString+"-01"+"-01" + self.irrigationArea = 10000. * pcr.cover(\ + vos.netcdf2PCRobjClone(self.dynamicIrrigationAreaFile,\ + 'irrigationArea',\ + fulldateInString, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap), 0.0) # unit: m2 (input file is in hectare) + else: + irrigation_pcraster_file = self.dynamicIrrigationAreaFile + yearInString + ".map" + logger.debug('reading irrigation area map from : '+irrigation_pcraster_file) + self.irrigationArea = 10000. * pcr.cover(\ + vos.readPCRmapClone(irrigation_pcraster_file,\ + self.cloneMap,self.tmpDir), 0.0) # unit: m2 (input file is in hectare) + + # TODO: Convert the input file, from hectare to percentage. + # This is to avoid errors if somebody uses 30 min input to run his 5 min model. + + # area of irrigation is limited by cellArea + self.irrigationArea = pcr.max(self.irrigationArea, 0.0) + self.irrigationArea = pcr.min(self.irrigationArea, self.cellArea) # limited by cellArea + + # calculate fracVegCover (for irrigation only) + for coverType in self.coverTypes: + if coverType.startswith('irr'): + + self.landCoverObj[coverType].fractionArea = 0.0 # reset + self.landCoverObj[coverType].fractionArea = self.landCoverObj[coverType].irrTypeFracOverIrr * self.irrigationArea # unit: m2 + self.landCoverObj[coverType].fracVegCover = pcr.min(1.0, self.landCoverObj[coverType].fractionArea/ self.cellArea) + + # avoid small values + self.landCoverObj[coverType].fracVegCover = pcr.rounddown(self.landCoverObj[coverType].fracVegCover * 1000.)/1000. + + # rescale land cover fractions (for all land cover types): + self.scaleModifiedLandCoverFractions() + + def update(self,meteo,groundwater,routing,currTimeStep): + + # updating regional groundwater abstraction limit (at the begining of the year or at the beginning of simulation) + if groundwater.limitRegionalAnnualGroundwaterAbstraction: + + logger.debug('Total groundwater abstraction is limited by regional annual pumping capacity.') + if currTimeStep.doy == 1 or currTimeStep.timeStepPCR == 1: + + self.groundwater_pumping_region_ids = \ + vos.netcdf2PCRobjClone(groundwater.pumpingCapacityNC,'region_ids',\ + currTimeStep.fulldate, useDoy = 'yearly', cloneMapFileName = self.cloneMap) + other_ids = pcr.mapmaximum(self.groundwater_pumping_region_ids) + pcr.scalar(1000.) + pcr.uniqueid(self.landmask) + self.groundwater_pumping_region_ids = pcr.cover(self.groundwater_pumping_region_ids, other_ids) + self.groundwater_pumping_region_ids = pcr.ifthen(self.landmask, pcr.nominal(self.groundwater_pumping_region_ids)) + + self.regionalAnnualGroundwaterAbstractionLimit = \ + pcr.ifthen(self.landmask,\ + pcr.cover(\ + vos.netcdf2PCRobjClone(groundwater.pumpingCapacityNC,'regional_pumping_limit',\ + currTimeStep.fulldate, useDoy = 'yearly', cloneMapFileName = self.cloneMap), 0.0)) + + self.regionalAnnualGroundwaterAbstractionLimit = pcr.areamaximum(self.regionalAnnualGroundwaterAbstractionLimit, self.groundwater_pumping_region_ids) + + self.regionalAnnualGroundwaterAbstractionLimit *= 1000. * 1000. * 1000. # unit: m3/year + self.regionalAnnualGroundwaterAbstractionLimit = pcr.ifthen(self.landmask,\ + self.regionalAnnualGroundwaterAbstractionLimit) + # minimum value (unit: m3/year at the regional scale) + minimum_value = 1000. + self.regionalAnnualGroundwaterAbstractionLimit = pcr.max(minimum_value,\ + self.regionalAnnualGroundwaterAbstractionLimit) + else: + + logger.debug('Total groundwater abstraction is NOT limited by regional annual pumping capacity.') + self.groundwater_pumping_region_ids = None + self.regionalAnnualGroundwaterAbstractionLimit = None + + # updating fracVegCover of each landCover (landCover fraction) + # - if considering dynamic/historical irrigation areas (expansion/reduction of irrigated areas) + # - done at yearly basis, at the beginning of each year, also at the beginning of simulation + # + if self.dynamicIrrigationArea and self.includeIrrigation and \ + (currTimeStep.timeStepPCR == 1 or currTimeStep.doy == 1) and self.noLandCoverFractionCorrection == False: + + # scale land cover fraction (due to expansion/reduction of irrigated areas) + self.scaleDynamicIrrigation(currTimeStep.year) + + #################################################################################################################################################################### + # correcting land cover fractions + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].fracVegCover + + if 'grassland' in list(self.landCoverObj.keys()): + self.landCoverObj['grassland'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].fracVegCover, 1.0) + + if 'short_natural' in list(self.landCoverObj.keys()): + self.landCoverObj['short_natural'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].fracVegCover, 1.0) + + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].fracVegCover + + for coverType in self.coverTypes: + self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].fracVegCover / total_fractions + #################################################################################################################################################################### + + + # read land cover fractions from netcdf files + # - assumption: annual resolution + if self.noAnnualChangesInLandCoverParameter == False and self.dynamicIrrigationArea == False and \ + (currTimeStep.timeStepPCR == 1 or currTimeStep.doy == 1): + msg = 'Read land cover fractions based on the given netcdf file.' + logger.debug(msg) + for coverType in self.coverTypes: + self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].get_land_cover_parameters(date_in_string = str(currTimeStep.fulldate), \ + get_only_fracVegCover = True) + + #################################################################################################################################################################### + # correcting land cover fractions + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].fracVegCover + + if 'grassland' in list(self.landCoverObj.keys()): + self.landCoverObj['grassland'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].fracVegCover, 1.0) + + if 'short_natural' in list(self.landCoverObj.keys()): + self.landCoverObj['short_natural'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].fracVegCover, 1.0) + + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].fracVegCover + + for coverType in self.coverTypes: + self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].fracVegCover / total_fractions + #################################################################################################################################################################### + + + # transfer some states, due to changes/dynamics in land cover conditions + # - if considering dynamic/historical irrigation areas (expansion/reduction of irrigated areas) + # - done at yearly basis, at the beginning of each year + # - note that this must be done at the beginning of each year, including for the first time step (timeStepPCR == 1) + # + if ((self.dynamicIrrigationArea and self.includeIrrigation) or self.noAnnualChangesInLandCoverParameter == False) and currTimeStep.doy == 1: + # + # loop for all main states: + for var in self.mainStates: + + logger.info("Transfering states for the variable "+str(var)) + + moving_fraction = pcr.scalar(0.0) # total land cover fractions that will be transferred + moving_states = pcr.scalar(0.0) # total states that will be transferred + + for coverType in self.coverTypes: + + old_fraction = self.landCoverObj[coverType].previousFracVegCover + new_fraction = self.landCoverObj[coverType].fracVegCover + + moving_fraction += pcr.max(0.0, old_fraction-new_fraction) + moving_states += pcr.max(0.0, old_fraction-new_fraction) * vars(self.landCoverObj[coverType])[var] + + previous_state = pcr.scalar(0.0) + rescaled_state = pcr.scalar(0.0) + + # correcting states + for coverType in self.coverTypes: + + old_states = vars(self.landCoverObj[coverType])[var] + old_fraction = self.landCoverObj[coverType].previousFracVegCover + new_fraction = self.landCoverObj[coverType].fracVegCover + + correction = moving_states *\ + vos.getValDivZero( pcr.max(0.0, new_fraction - old_fraction),\ + moving_fraction, vos.smallNumber ) + + new_states = pcr.ifthenelse(new_fraction > old_fraction, + vos.getValDivZero( + old_states * old_fraction + correction, \ + new_fraction, vos.smallNumber), old_states) + + new_states = pcr.ifthenelse(new_fraction > 0.0, new_states, pcr.scalar(0.0)) + + vars(self.landCoverObj[coverType])[var] = new_states + + previous_state += old_fraction * old_states + rescaled_state += new_fraction * new_states + + # check and make sure that previous_state == rescaled_state + check_map = previous_state - rescaled_state + a,b,c = vos.getMinMaxMean(check_map) + threshold = 1e-5 + if abs(a) > threshold or abs(b) > threshold: + logger.warning("Error in transfering states (due to dynamic in land cover fractions) ... Min %f Max %f Mean %f" %(a,b,c)) + else: + logger.info("Successful in transfering states (after change in land cover fractions) ... Min %f Max %f Mean %f" %(a,b,c)) + + # for the last day of the year, we have to save the previous land cover fractions (to be considered in the next time step) + if self.dynamicIrrigationArea and self.includeIrrigation and currTimeStep.isLastDayOfYear: + # save the current state of fracVegCover + for coverType in self.coverTypes:\ + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].fracVegCover + + #- RvB: irrigation water efficiency + # added here are the lines required to read in the water efficiency + # irrigation water efficiency is updated at the start of the year and + if self.includeIrrigation and (currTimeStep.doy == 1 or currTimeStep.timeStepPCR == 1): + logger.info("Setting irrigation water efficiency") + for coverType in self.coverTypes: + self.landCoverObj[coverType].updateIrrigationWaterEfficiency(currTimeStep) + + # calculate cell fraction influenced by capillary rise: + self.capRiseFrac = self.calculateCapRiseFrac(groundwater,routing,currTimeStep) + + # get a dictionary containing livestock, domestic and industrial water demand, including their return flow fractions + self.nonIrrigationWaterDemandDict = self.obtainNonIrrWaterDemand(routing, currTimeStep) + + # get a dictionary containing the partitioning of withdrawal/abstraction sources: (from groundwater and surface water) + self.swAbstractionFractionDict = self.partitioningGroundSurfaceAbstraction(groundwater,routing) + + # get desalination water use (m/day); assume this one as potential supply + if self.includeDesalination: + logger.debug("Monthly desalination water use is included.") + if (currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1): + desalinationWaterUse = \ + pcr.ifthen(self.landmask,\ + pcr.cover(\ + vos.netcdf2PCRobjClone(self.desalinationWaterFile,'desalination_water_use',\ + currTimeStep.fulldate, useDoy = 'monthly', cloneMapFileName = self.cloneMap), 0.0)) + self.desalinationWaterUse = pcr.max(0.0, desalinationWaterUse) + else: + logger.debug("Monthly desalination water use is NOT included.") + self.desalinationWaterUse = pcr.scalar(0.0) + + + # update/calculate water use + # - non irrigation demand (no loop over land cover types needed) + + # - irrigation demand (loop over cover types, but irrigation only + for coverType in self.coverTypes: (but irrigation only): + self.irr_water_demand.calculateIrrigationDemand(meteo, groundwater, routing) + + + + + + # update/calculate hydro processes (loop per each land cover type): + for coverType in self.coverTypes: + + logger.info("Updating land cover: "+str(coverType)) + self.landCoverObj[coverType].updateLC(meteo,groundwater,routing,\ + self.capRiseFrac,\ + self.nonIrrigationWaterDemandDict,\ + self.swAbstractionFractionDict,\ + currTimeStep,\ + self.allocSegments,\ + self.desalinationWaterUse,\ + self.groundwater_pumping_region_ids,self.regionalAnnualGroundwaterAbstractionLimit) + + # first, we set all aggregated values/variables to zero: + for var in self.aggrVars: vars(self)[var] = pcr.scalar(0.0) + # + # get or calculate the values of all aggregated values/variables + for coverType in self.coverTypes: + # calculate the aggregrated or global landSurface values: + for var in self.aggrVars: + vars(self)[var] += \ + self.landCoverObj[coverType].fracVegCover * vars(self.landCoverObj[coverType])[var] + + # total storages (unit: m3) in the entire landSurface module + if self.numberOfSoilLayers == 2: self.totalSto = \ + self.snowCoverSWE + self.snowFreeWater + self.interceptStor +\ + self.topWaterLayer +\ + self.storUpp +\ + self.storLow + # + if self.numberOfSoilLayers == 3: self.totalSto = \ + self.snowCoverSWE + self.snowFreeWater + self.interceptStor +\ + self.topWaterLayer +\ + self.storUpp000005 + self.storUpp005030 +\ + self.storLow030150 + + # calculate irrigation water demand (for the next time step) + # self.calculate_irrigation_water_demand() + + # old-style reporting (this is useful for debugging) + self.old_style_land_surface_reporting(currTimeStep) + + + # .................................................................. + +# def calculate_irrigation_water_demand(self): +# # irrigation water demand (unit: m/day) for paddy and non-paddy +# self.irrGrossDemand = pcr.scalar(0.0) +# if (self.name == 'irrPaddy' or self.name == 'irr_paddy') and self.includeIrrigation: +# self.irrGrossDemand = \ +# pcr.ifthenelse(self.cropKC > 0.75, \ +# pcr.max(0.0,self.minTopWaterLayer - \ +# (self.topWaterLayer )), 0.) # a function of cropKC (evaporation and transpiration), +# # topWaterLayer (water available in the irrigation field) +# +# if (self.name == 'irrNonPaddy' or self.name == 'irr_non_paddy' or self.name == "irr_non_paddy_crops") and self.includeIrrigation: +# +# #~ adjDeplFactor = \ +# #~ pcr.max(0.1,\ +# #~ pcr.min(0.8,(self.cropDeplFactor + \ +# #~ 40.*(0.005-self.totalPotET)))) # from Wada et al. (2014) +# adjDeplFactor = \ +# pcr.max(0.1,\ +# pcr.min(0.8,(self.cropDeplFactor + \ +# 0.04*(5.-self.totalPotET*1000.)))) # original formula based on Allen et al. (1998) +# # see: http://www.fao.org/docrep/x0490e/x0490e0e.htm# +# # +# #~ # alternative 1: irrigation demand (to fill the entire totAvlWater, maintaining the field capacity) - NOT USED +# #~ self.irrGrossDemand = \ +# #~ pcr.ifthenelse( self.cropKC > 0.20, \ +# #~ pcr.ifthenelse( self.readAvlWater < \ +# #~ adjDeplFactor*self.totAvlWater, \ +# #~ pcr.max(0.0, self.totAvlWater-self.readAvlWater),0.),0.) # a function of cropKC and totalPotET (evaporation and transpiration), +# #~ # readAvlWater (available water in the root zone) +# +# # alternative 2: irrigation demand (to fill the entire totAvlWater, maintaining the field capacity, +# # but with the correction of totAvlWater based on the rooting depth) +# # - as the proxy of rooting depth, we use crop coefficient +# self.irrigation_factor = pcr.ifthenelse(self.cropKC > 0.0,\ +# pcr.min(1.0, self.cropKC / 1.0), 0.0) +# self.irrGrossDemand = \ +# pcr.ifthenelse( self.cropKC > 0.20, \ +# pcr.ifthenelse( self.readAvlWater < \ +# adjDeplFactor*self.irrigation_factor*self.totAvlWater, \ +# pcr.max(0.0, self.totAvlWater*self.irrigation_factor-self.readAvlWater),0.),0.) +# +# # irrigation demand is implemented only if there is deficit in transpiration and/or evaporation +# deficit_factor = 1.00 +# evaporationDeficit = pcr.max(0.0, (self.potBareSoilEvap + self.potTranspiration)*deficit_factor -\ +# self.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True)) +# transpirationDeficit = pcr.max(0.0, +# self.potTranspiration*deficit_factor -\ +# self.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True, returnTotalTranspirationOnly = True)) +# deficit = pcr.max(evaporationDeficit, transpirationDeficit) +# # +# # treshold to initiate irrigation +# deficit_treshold = 0.20 * self.totalPotET +# need_irrigation = pcr.ifthenelse(deficit > deficit_treshold, pcr.boolean(1),\ +# pcr.ifthenelse(self.soilWaterStorage == 0.000, pcr.boolean(1), pcr.boolean(0))) +# need_irrigation = pcr.cover(need_irrigation, pcr.boolean(0.0)) +# # +# self.irrGrossDemand = pcr.ifthenelse(need_irrigation, self.irrGrossDemand, 0.0) +# +# # demand is limited by potential evaporation for the next coming days +# # - objective: to avoid too high and unrealistic demand +# max_irrigation_interval = 15.0 +# min_irrigation_interval = 7.0 +# irrigation_interval = pcr.min(max_irrigation_interval, \ +# pcr.max(min_irrigation_interval, \ +# pcr.ifthenelse(self.totalPotET > 0.0, \ +# pcr.roundup((self.irrGrossDemand + pcr.max(self.readAvlWater, self.soilWaterStorage))/ self.totalPotET), 1.0))) +# # - irrigation demand - limited by potential evaporation for the next coming days +# self.irrGrossDemand = pcr.min(pcr.max(0.0,\ +# self.totalPotET * irrigation_interval - pcr.max(self.readAvlWater, self.soilWaterStorage)),\ +# self.irrGrossDemand) +# +# # assume that smart farmers do not irrigate higher than infiltration capacities +# if self.numberOfLayers == 2: self.irrGrossDemand = pcr.min(self.irrGrossDemand, self.parameters.kSatUpp) +# if self.numberOfLayers == 3: self.irrGrossDemand = pcr.min(self.irrGrossDemand, self.parameters.kSatUpp000005) +# +# # irrigation efficiency, minimum demand for start irrigating and maximum value to cap excessive demand +# if self.includeIrrigation: +# +# # irrigation efficiency # TODO: Improve the concept of irrigation efficiency +# self.irrigationEfficiencyUsed = pcr.min(1.0, pcr.max(0.10, self.irrigationEfficiency)) +# # demand, including its inefficiency +# self.irrGrossDemand = pcr.cover(self.irrGrossDemand / pcr.min(1.0, self.irrigationEfficiencyUsed), 0.0) +# +# # the following irrigation demand is not limited to available water +# self.irrGrossDemand = pcr.ifthen(self.landmask, self.irrGrossDemand) +# +# # reduce irrGrossDemand by netLqWaterToSoil +# self.irrGrossDemand = pcr.max(0.0, self.irrGrossDemand - self.netLqWaterToSoil) +# +# # minimum demand for start irrigating +# minimum_demand = 0.005 # unit: m/day # TODO: set the minimum demand in the ini/configuration file. +# if self.name == 'irrPaddy' or\ +# self.name == 'irr_paddy': minimum_demand = pcr.min(self.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. +# self.irrGrossDemand = pcr.ifthenelse(self.irrGrossDemand > minimum_demand, \ +# self.irrGrossDemand , 0.0) +# +# maximum_demand = 0.025 # unit: m/day # TODO: set the maximum demand in the ini/configuration file. +# if self.name == 'irrPaddy' or\ +# self.name == 'irr_paddy': maximum_demand = pcr.min(self.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. +# self.irrGrossDemand = pcr.min(maximum_demand, self.irrGrossDemand) +# +# # ignore small irrigation demand (less than 1 mm) +# self.irrGrossDemand = pcr.rounddown( self.irrGrossDemand *1000.)/1000. +# +# # irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? +# self.irrGrossDemand = pcr.ifthenelse(self.fracVegCover > 0.0, self.irrGrossDemand, 0.0) +# +# # total irrigation gross demand (m) per cover types (not limited by available water) +# self.totalPotentialMaximumIrrGrossDemandPaddy = 0.0 +# self.totalPotentialMaximumIrrGrossDemandNonPaddy = 0.0 +# if self.name == 'irrPaddy' or self.name == 'irr_paddy': self.totalPotentialMaximumIrrGrossDemandPaddy = self.irrGrossDemand +# if self.name == 'irrNonPaddy' or self.name == 'irr_non_paddy' or self.name == 'irr_non_paddy_crops': self.totalPotentialMaximumIrrGrossDemandNonPaddy = self.irrGrossDemand +# +# # .................................................................. + + def old_style_land_surface_reporting(self,currTimeStep): + + if self.report == True: + timeStamp = datetime.datetime(currTimeStep.year,\ + currTimeStep.month,\ + currTimeStep.day,\ + 0) + # writing daily output to netcdf files + timestepPCR = currTimeStep.timeStepPCR + if self.outDailyTotNC[0] != "None": + for var in self.outDailyTotNC: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_dailyTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,timestepPCR-1) + + # writing monthly output to netcdf files + # -cummulative + if self.outMonthTotNC[0] != "None": + for var in self.outMonthTotNC: + + # introduce variables at the beginning of simulation or + # reset variables at the beginning of the month + if currTimeStep.timeStepPCR == 1 or \ + currTimeStep.day == 1:\ + vars(self)[var+'MonthTot'] = pcr.scalar(0.0) + + # accumulating + vars(self)[var+'MonthTot'] += vars(self)[var] + + # reporting at the end of the month: + if currTimeStep.endMonth == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'MonthTot'),\ + vos.MV),timeStamp,currTimeStep.monthIdx-1) + # -average + if self.outMonthAvgNC[0] != "None": + for var in self.outMonthAvgNC: + # only if a accumulator variable has not been defined: + if var not in self.outMonthTotNC: + + # introduce accumulator at the beginning of simulation or + # reset accumulator at the beginning of the month + if currTimeStep.timeStepPCR == 1 or \ + currTimeStep.day == 1:\ + vars(self)[var+'MonthTot'] = pcr.scalar(0.0) + # accumulating + vars(self)[var+'MonthTot'] += vars(self)[var] + + # calculating average & reporting at the end of the month: + if currTimeStep.endMonth == True: + vars(self)[var+'MonthAvg'] = vars(self)[var+'MonthTot']/\ + currTimeStep.day + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthAvg.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'MonthAvg'),\ + vos.MV),timeStamp,currTimeStep.monthIdx-1) + # + # -last day of the month + if self.outMonthEndNC[0] != "None": + for var in self.outMonthEndNC: + # reporting at the end of the month: + if currTimeStep.endMonth == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthEnd.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,currTimeStep.monthIdx-1) + + # writing yearly output to netcdf files + # -cummulative + if self.outAnnuaTotNC[0] != "None": + for var in self.outAnnuaTotNC: + + # introduce variables at the beginning of simulation or + # reset variables at the beginning of the month + if currTimeStep.timeStepPCR == 1 or \ + currTimeStep.doy == 1:\ + vars(self)[var+'AnnuaTot'] = pcr.scalar(0.0) + + # accumulating + vars(self)[var+'AnnuaTot'] += vars(self)[var] + + # reporting at the end of the year: + if currTimeStep.endYear == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'AnnuaTot'),\ + vos.MV),timeStamp,currTimeStep.annuaIdx-1) + # -average + if self.outAnnuaAvgNC[0] != "None": + for var in self.outAnnuaAvgNC: + # only if a accumulator variable has not been defined: + if var not in self.outAnnuaTotNC: + # introduce accumulator at the beginning of simulation or + # reset accumulator at the beginning of the year + if currTimeStep.timeStepPCR == 1 or \ + currTimeStep.doy == 1:\ + vars(self)[var+'AnnuaTot'] = pcr.scalar(0.0) + # accumulating + vars(self)[var+'AnnuaTot'] += vars(self)[var] + # + # calculating average & reporting at the end of the year: + if currTimeStep.endYear == True: + vars(self)[var+'AnnuaAvg'] = vars(self)[var+'AnnuaTot']/\ + currTimeStep.doy + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaAvg.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'AnnuaAvg'),\ + vos.MV),timeStamp,currTimeStep.annuaIdx-1) + # + # -last day of the year + if self.outAnnuaEndNC[0] != "None": + for var in self.outAnnuaEndNC: + # reporting at the end of the year: + if currTimeStep.endYear == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaEnd.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,currTimeStep.annuaIdx-1) diff --git a/model/landSurfaceOrig.py b/model/landSurfaceOrig.py new file mode 100644 index 000000000..db20a2572 --- /dev/null +++ b/model/landSurfaceOrig.py @@ -0,0 +1,1711 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# PCR-GLOBWB (PCRaster Global Water Balance) Global Hydrological Model +# +# Copyright (C) 2016, Edwin H. Sutanudjaja, Rens van Beek, Niko Wanders, Yoshihide Wada, +# Joyce H. C. Bosmans, Niels Drost, Ruud J. van der Ent, Inge E. M. de Graaf, Jannis M. Hoch, +# Kor de Jong, Derek Karssenberg, Patricia López López, Stefanie Peßenteiner, Oliver Schmitz, +# Menno W. Straatsma, Ekkamol Vannametee, Dominik Wisser, and Marc F. P. Bierkens +# Faculty of Geosciences, Utrecht University, Utrecht, The Netherlands +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +import types +import pcraster as pcr +import virtualOS as vos + +import logging +logger = logging.getLogger(__name__) + +from ncConverter import * + +import landCover as lc +import parameterSoilAndTopo as parSoilAndTopo + +class LandSurface(object): + + def getState(self): + result = {} + + if self.numberOfSoilLayers == 2: + for coverType in self.coverTypes: + result[coverType] = {} + result[coverType]['interceptStor'] = \ + self.landCoverObj[coverType].interceptStor + result[coverType]['snowCoverSWE' ] = \ + self.landCoverObj[coverType].snowCoverSWE + result[coverType]['snowFreeWater'] = \ + self.landCoverObj[coverType].snowFreeWater + result[coverType]['topWaterLayer'] = \ + self.landCoverObj[coverType].topWaterLayer + result[coverType]['storUpp'] = \ + self.landCoverObj[coverType].storUpp + result[coverType]['storLow'] = \ + self.landCoverObj[coverType].storLow + result[coverType]['interflow' ] = \ + self.landCoverObj[coverType].interflow + + if self.numberOfSoilLayers == 3: + for coverType in self.coverTypes: + result[coverType] = {} + result[coverType]['interceptStor'] = \ + self.landCoverObj[coverType].interceptStor + result[coverType]['snowCoverSWE' ] = \ + self.landCoverObj[coverType].snowCoverSWE + result[coverType]['snowFreeWater'] = \ + self.landCoverObj[coverType].snowFreeWater + result[coverType]['topWaterLayer'] = \ + self.landCoverObj[coverType].topWaterLayer + result[coverType]['storUpp000005'] = \ + self.landCoverObj[coverType].storUpp000005 + result[coverType]['storUpp005030'] = \ + self.landCoverObj[coverType].storUpp005030 + result[coverType]['storLow030150'] = \ + self.landCoverObj[coverType].storLow030150 + result[coverType]['interflow' ] = \ + self.landCoverObj[coverType].interflow + + return result + + def getPseudoState(self): + result = {} + + if self.numberOfSoilLayers == 2: + result['interceptStor'] = self.interceptStor + result['snowCoverSWE'] = self.snowCoverSWE + result['snowFreeWater'] = self.snowFreeWater + result['topWaterLayer'] = self.topWaterLayer + result['storUpp'] = self.storUpp + result['storLow'] = self.storLow + + if self.numberOfSoilLayers == 3: + result['interceptStor'] = self.interceptStor + result['snowCoverSWE'] = self.snowCoverSWE + result['snowFreeWater'] = self.snowFreeWater + result['topWaterLayer'] = self.topWaterLayer + result['storUpp000005'] = self.storUpp000005 + result['storUpp005030'] = self.storUpp005030 + result['storLow030150'] = self.storLow030150 + + return result + + def __init__(self,iniItems,landmask,initialState=None): + object.__init__(self) + + # clone map, temporary directory, absolute path of input directory, and landmask + self.cloneMap = iniItems.cloneMap + self.tmpDir = iniItems.tmpDir + self.inputDir = iniItems.globalOptions['inputDir'] + self.landmask = landmask + + # make iniItems available for the other methods/functions: + self.iniItems = iniItems + + # cellArea (unit: m2) + self.cellArea = vos.readPCRmapClone(iniItems.routingOptions['cellAreaMap'], \ + self.cloneMap, self.tmpDir, self.inputDir) + self.cellArea = pcr.ifthen(self.landmask, self.cellArea) + + # number of soil layers: + self.numberOfSoilLayers = int(iniItems.landSurfaceOptions['numberOfUpperSoilLayers']) + + # list of aggregated variables that MUST be defined in the module: + # - aggregated from landCover modules + # - some are needed for water balance checking + # - some are needed in other modules (e.g. routing, groundwater) + # - some are needed for initialConditions + # + # main state variables (unit: m) + self.mainStates = ['interceptStor',\ + 'snowCoverSWE' ,\ + 'snowFreeWater',\ + 'topWaterLayer'] + # + # state variables (unit: m) + self.stateVars = ['storUppTotal', + 'storLowTotal', + 'satDegUppTotal', + 'satDegLowTotal', + 'satDegTotal'] + # + # flux variables (unit: m/day) + self.fluxVars = ['infiltration','gwRecharge','netLqWaterToSoil', + 'totalPotET', + 'actualET', + 'interceptEvap', + 'openWaterEvap', + 'actSnowFreeWaterEvap', + 'actBareSoilEvap', + 'actTranspiUppTotal', + 'actTranspiLowTotal', + 'actTranspiTotal', + 'directRunoff', + 'interflow', + 'interflowTotal', + 'irrGrossDemand', + 'nonIrrGrossDemand', + 'totalPotentialGrossDemand', + 'actSurfaceWaterAbstract', + 'allocSurfaceWaterAbstract', + 'desalinationAbstraction', + 'desalinationAllocation', + 'nonFossilGroundwaterAbs', + 'allocNonFossilGroundwater', + 'fossilGroundwaterAbstr', + 'fossilGroundwaterAlloc', + 'landSurfaceRunoff', + 'satExcess', + 'snowMelt', + 'totalGroundwaterAbstraction', + 'totalGroundwaterAllocation', + 'totalPotentialMaximumGrossDemand', + 'totalPotentialMaximumIrrGrossDemand', + 'totalPotentialMaximumIrrGrossDemandPaddy', + 'totalPotentialMaximumIrrGrossDemandNonPaddy', + 'totalPotentialMaximumNonIrrGrossDemand', + 'irrGrossDemandPaddy', + 'irrGrossDemandNonPaddy', + 'domesticWaterWithdrawal', + 'industryWaterWithdrawal', + 'livestockWaterWithdrawal', + 'nonIrrReturnFlow', + 'irrigationTranspirationDeficit'] + # + # specific variables for 2 and 3 layer soil models: + # + if self.numberOfSoilLayers == 2: + self.mainStates += ['storUpp','storLow'] + self.stateVars += self.mainStates + self.fluxVars += ['actTranspiUpp','actTranspiLow','netPercUpp'] + # + if self.numberOfSoilLayers == 3: + self.mainStates += ['storUpp000005', 'storUpp005030', 'storLow030150'] + self.stateVars += self.mainStates + self.fluxVars += ['actTranspiUpp000005','actTranspiUpp005030','actTranspiLow030150', + 'netPercUpp000005', 'netPercUpp005030', + 'interflowUpp005030'] + + # list of all variables that will be calculated/reported in landSurface.py + self.aggrVars = self.stateVars + self.fluxVars + if self.numberOfSoilLayers == 2: self.aggrVars += ['satDegUpp','satDegLow'] + if self.numberOfSoilLayers == 3: self.aggrVars += ['satDegUpp000005','satDegUpp005030','satDegLow030150'] + + self.debugWaterBalance = iniItems.landSurfaceOptions['debugWaterBalance'] + # TDOD: Perform water balance checks for aggregates values (from values of each land cover type). + + # limitAbstraction + self.limitAbstraction = False + if iniItems.landSurfaceOptions['limitAbstraction'] == "True": self.limitAbstraction = True + + # landCover types included in the simulation: + self.coverTypes = ["forest","grassland"] + # + self.includeIrrigation = False + if iniItems.landSurfaceOptions['includeIrrigation'] == "True": + self.includeIrrigation = True + self.coverTypes += ["irrPaddy","irrNonPaddy"] + logger.info("Irrigation is included/considered in this run.") + else: + logger.info("Irrigation is NOT included/considered in this run.") + + # if user define their land cover types: + if 'landCoverTypes' in list(iniItems.landSurfaceOptions.keys()): + self.coverTypes = iniItems.landSurfaceOptions['landCoverTypes'].split(",") + + # water demand options: irrigation efficiency, non irrigation water demand, and desalination supply + self.waterDemandOptions(iniItems) + + # TODO: Make an option so that users can easily perform natural runs (without water user, without reservoirs). + + # pre-defined surface water source fraction for satisfying irrigation and livestock water demand + self.swAbstractionFractionData = None + self.swAbstractionFractionDataQuality = None + if 'irrigationSurfaceWaterAbstractionFractionData' in list(iniItems.landSurfaceOptions.keys()) and\ + 'irrigationSurfaceWaterAbstractionFractionDataQuality' in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions['irrigationSurfaceWaterAbstractionFractionData'] not in ["None", "False"] or\ + iniItems.landSurfaceOptions['irrigationSurfaceWaterAbstractionFractionDataQuality'] not in ["None", "False"]: + + logger.info('Using/incorporating the predefined surface water source of Siebert et al. (2010) for satisfying irrigation and livestock demand.') + self.swAbstractionFractionData = pcr.cover(\ + vos.readPCRmapClone(iniItems.landSurfaceOptions['irrigationSurfaceWaterAbstractionFractionData'],\ + self.cloneMap,self.tmpDir,self.inputDir), 0.0) + self.swAbstractionFractionData = pcr.ifthen(self.swAbstractionFractionData >= 0.0, \ + self.swAbstractionFractionData ) + self.swAbstractionFractionDataQuality = \ + pcr.cover(\ + vos.readPCRmapClone(iniItems.landSurfaceOptions['irrigationSurfaceWaterAbstractionFractionDataQuality'],\ + self.cloneMap,self.tmpDir,self.inputDir), 0.0) + # ignore value with the quality above 5 (very bad) + # - Note: The resulting map has values only in cells with the data auality <= 5.0 + self.swAbstractionFractionData = pcr.ifthen(self.swAbstractionFractionDataQuality <= 5.0, \ + self.swAbstractionFractionData) + + # maximum pre-defined surface water source fraction for satisfying industrial and domestic water demand: + # - if not defined (default), set it to the maximum + self.maximumNonIrrigationSurfaceWaterAbstractionFractionData = pcr.scalar(1.0) + if 'maximumNonIrrigationSurfaceWaterAbstractionFractionData' in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions['maximumNonIrrigationSurfaceWaterAbstractionFractionData'] != "None" or\ + iniItems.landSurfaceOptions['maximumNonIrrigationSurfaceWaterAbstractionFractionData'] != "False": + + logger.info('Set the maximum fraction for predefined surface water source for satisfying domestic and industrial demand.') + self.maximumNonIrrigationSurfaceWaterAbstractionFractionData = pcr.min(1.0,\ + pcr.cover(\ + vos.readPCRmapClone(iniItems.landSurfaceOptions['maximumNonIrrigationSurfaceWaterAbstractionFractionData'],\ + self.cloneMap,self.tmpDir,self.inputDir), 1.0)) + + # pre-defined surface water source fraction for satisfying industrial and domestic water demand + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData = None + if 'predefinedNonIrrigationSurfaceWaterAbstractionFractionData' in list(iniItems.landSurfaceOptions.keys()) and \ + (iniItems.landSurfaceOptions['predefinedNonIrrigationSurfaceWaterAbstractionFractionData'] != "None" or \ + iniItems.landSurfaceOptions['predefinedNonIrrigationSurfaceWaterAbstractionFractionData'] != "False"): + + logger.info('Set the predefined fraction of surface water source for satisfying domestic and industrial demand.') + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData = pcr.min(1.0,\ + pcr.cover(\ + vos.readPCRmapClone(iniItems.landSurfaceOptions['predefinedNonIrrigationSurfaceWaterAbstractionFractionData'],\ + self.cloneMap,self.tmpDir,self.inputDir), 1.0)) + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData = pcr.max(0.0, \ + pcr.min(self.maximumNonIrrigationSurfaceWaterAbstractionFractionData, \ + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData)) + + # threshold values defining the preference for irrigation water source (unit: fraction/percentage) + self.treshold_to_maximize_irrigation_surface_water = \ + vos.readPCRmapClone(iniItems.landSurfaceOptions['treshold_to_maximize_irrigation_surface_water'],\ + self.cloneMap,self.tmpDir,self.inputDir) + self.treshold_to_minimize_fossil_groundwater_irrigation = \ + vos.readPCRmapClone(iniItems.landSurfaceOptions['treshold_to_minimize_fossil_groundwater_irrigation'],\ + self.cloneMap,self.tmpDir,self.inputDir) + + # assign the topography and soil parameters + self.soil_topo_parameters = {} + # - default values used for all land cover types + self.soil_topo_parameters['default'] = parSoilAndTopo.SoilAndTopoParameters(iniItems,self.landmask) + self.soil_topo_parameters['default'].read(iniItems) + # - specific soil and topography parameter (per land cover type) + for coverType in self.coverTypes: + name_of_section_given_in_ini_file = str(coverType)+'Options' + dictionary_of_land_cover_settings = iniItems.__getattribute__(name_of_section_given_in_ini_file) + + if 'usingSpecificSoilTopo' not in list(dictionary_of_land_cover_settings.keys()): dictionary_of_land_cover_settings['usingSpecificSoilTopo'] = "False" + if dictionary_of_land_cover_settings['usingSpecificSoilTopo'] == "True": + + msg = "Using a specific set of soil and topo parameters " + msg += "as defined in the "+name_of_section_given_in_ini_file+" of the ini/configuration file." + + self.soil_topo_parameters[coverType] = parSoilAndTopo.SoilAndTopoParameters(iniItems,self.landmask) + self.soil_topo_parameters[coverType].read(iniItems, dictionary_of_land_cover_settings) + else: + + msg = "Using the default set of soil and topo parameters " + msg += "as defined in the landSurfaceOptions of the ini/configuration file." + + self.soil_topo_parameters[coverType] = self.soil_topo_parameters['default'] + + logger.info(msg) + + + # instantiate self.landCoverObj[coverType] + self.landCoverObj = {} + for coverType in self.coverTypes: + self.landCoverObj[coverType] = lc.LandCover(iniItems,\ + str(coverType)+'Options',\ + self.soil_topo_parameters[coverType],self.landmask,\ + self.irrigationEfficiency,\ + self.usingAllocSegments) + + # rescale landCover Fractions + # - by default, the land cover fraction will always be corrected (to ensure the total of all fractions = 1.0) + self.noLandCoverFractionCorrection = False + if "noLandCoverFractionCorrection" in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions["noLandCoverFractionCorrection"] == "True": self.noLandCoverFractionCorrection = True + # - rescaling land cover fractions + if self.noLandCoverFractionCorrection == False: + self.scaleNaturalLandCoverFractions() + if self.includeIrrigation: self.scaleModifiedLandCoverFractions() + + # an option to introduce changes of land cover parameters (not only fracVegCover) + self.noAnnualChangesInLandCoverParameter = True + if 'annualChangesInLandCoverParameters' in list(iniItems.landSurfaceOptions.keys()): + if iniItems.landSurfaceOptions['annualChangesInLandCoverParameters'] == "True": self.noAnnualChangesInLandCoverParameter = False + + # Note that "dynamicIrrigationArea" CANNOT be combined with "noLandCoverFractionCorrection" + if self.noLandCoverFractionCorrection: self.dynamicIrrigationArea = False + + # Also note that "noAnnualChangesInLandCoverParameter = False" must be followed by "noLandCoverFractionCorrection" + if self.noAnnualChangesInLandCoverParameter == False and self.noLandCoverFractionCorrection == False: + self.noLandCoverFractionCorrection = True + msg = "WARNING! No land cover fraction correction will be performed. Please make sure that the 'total' of all fracVegCover adds to one." + logger.warning(msg) + logger.warning(msg) + logger.warning(msg) + logger.warning(msg) + logger.warning(msg) + + ######################################################################################################################################################################################### + # 29 July 2014: + # + # If using historical/dynamic irrigation file (changing every year), we have to get fraction over irrigation area + # (in order to calculate irrigation area for each irrigation type) + # + # Note that: totalIrrAreaFrac = fraction irrigated areas (e.g. paddy + nonPaddy) over the entire cell area (dimensionless) ; this value changes (if self.dynamicIrrigationArea = True) + # irrTypeFracOverIrr = fraction each land cover type (paddy or nonPaddy) over the irrigation area (dimensionless) ; this value is constant for the entire simulation + # + if self.dynamicIrrigationArea: + + logger.info('Determining fraction of total irrigated areas over each cell') + # Note that this is needed ONLY if historical irrigation areas are used (if self.dynamicIrrigationArea = True). + + # total irrigated area fraction (over the entire cell) + totalIrrAreaFrac = 0.0 + for coverType in self.coverTypes: + if coverType.startswith('irr'): + totalIrrAreaFrac += self.landCoverObj[coverType].fracVegCover + + # fraction over irrigation area + for coverType in self.coverTypes: + if coverType.startswith('irr'): + self.landCoverObj[coverType].irrTypeFracOverIrr = vos.getValDivZero(self.landCoverObj[coverType].fracVegCover,\ + totalIrrAreaFrac, vos.smallNumber) + + # get the initial conditions (for every land cover type) + self.getInitialConditions(iniItems, initialState) + + # initiate old style reporting (this is useful for debuging) + self.initiate_old_style_land_surface_reporting(iniItems) + + + def initiate_old_style_land_surface_reporting(self,iniItems): + + self.report = True + try: + self.outDailyTotNC = iniItems.landSurfaceOptions['outDailyTotNC'].split(",") + self.outMonthTotNC = iniItems.landSurfaceOptions['outMonthTotNC'].split(",") + self.outMonthAvgNC = iniItems.landSurfaceOptions['outMonthAvgNC'].split(",") + self.outMonthEndNC = iniItems.landSurfaceOptions['outMonthEndNC'].split(",") + self.outAnnuaTotNC = iniItems.landSurfaceOptions['outAnnuaTotNC'].split(",") + self.outAnnuaAvgNC = iniItems.landSurfaceOptions['outAnnuaAvgNC'].split(",") + self.outAnnuaEndNC = iniItems.landSurfaceOptions['outAnnuaEndNC'].split(",") + except: + self.report = False + if self.report == True: + self.outNCDir = iniItems.outNCDir + self.netcdfObj = PCR2netCDF(iniItems) + # + # daily output in netCDF files: + if self.outDailyTotNC[0] != "None": + for var in self.outDailyTotNC: + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_dailyTot.nc",\ + var,"undefined") + # MONTHly output in netCDF files: + # - cummulative + if self.outMonthTotNC[0] != "None": + for var in self.outMonthTotNC: + # initiating monthlyVarTot (accumulator variable): + vars(self)[var+'MonthTot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthTot.nc",\ + var,"undefined") + # - average + if self.outMonthAvgNC[0] != "None": + for var in self.outMonthAvgNC: + # initiating monthlyTotAvg (accumulator variable) + vars(self)[var+'MonthTot'] = None + # initiating monthlyVarAvg: + vars(self)[var+'MonthAvg'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthAvg.nc",\ + var,"undefined") + # - last day of the month + if self.outMonthEndNC[0] != "None": + for var in self.outMonthEndNC: + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthEnd.nc",\ + var,"undefined") + # YEARly output in netCDF files: + # - cummulative + if self.outAnnuaTotNC[0] != "None": + for var in self.outAnnuaTotNC: + # initiating yearly accumulator variable: + vars(self)[var+'AnnuaTot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaTot.nc",\ + var,"undefined") + # - average + if self.outAnnuaAvgNC[0] != "None": + for var in self.outAnnuaAvgNC: + # initiating annualyVarAvg: + vars(self)[var+'AnnuaAvg'] = None + # initiating annualyTotAvg (accumulator variable) + vars(self)[var+'AnnuaTot'] = None + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaAvg.nc",\ + var,"undefined") + # - last day of the year + if self.outAnnuaEndNC[0] != "None": + for var in self.outAnnuaEndNC: + # creating the netCDF files: + self.netcdfObj.createNetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaEnd.nc",\ + var,"undefined") + + def getInitialConditions(self, iniItems, iniConditions = None): + + # starting year in integer + starting_year = int(iniItems.globalOptions['startTime'][0:4]) + # + # check if the run start at the first day of the year: + start_on_1_Jan = False + if iniItems.globalOptions['startTime'][-5:] == "01-01": start_on_1_Jan = True + + # condition to consider previous year land cover fraction + consider_previous_year_land_cover_fraction = False + + + ####################################################################################################################################### + # obtaining initial land cover fractions for runs with dynamicIrrigationArea + # + # For non spin-up runs that start at the first day of the year (1 January), + # - we have to consider the previous year land cover fractions, specifically if we consider the dynamic/expansion of irrigation areas + # + if iniConditions == None and start_on_1_Jan == True and \ + self.dynamicIrrigationArea and self.noLandCoverFractionCorrection == False: + # obtain the previous year land cover fractions: + self.scaleDynamicIrrigation(starting_year - 1) # the previous year land cover fractions + consider_previous_year_land_cover_fraction = True + # + # For spin-up runs or for runs that start after 1 January, + # - we do not have to consider the previous year land cover fractions + # + if consider_previous_year_land_cover_fraction == False and \ + self.dynamicIrrigationArea and self.noLandCoverFractionCorrection == False: + # just using the current year land cover fractions: + self.scaleDynamicIrrigation(starting_year) # the current year land cover fractions + # + ################################################################################################################################# + + ####################################################################################################################################### + # obtaining initial land cover fractions for runs with noLandCoverFractionCorrection and annualChangesInLandCoverParameters + # + # For non spin-up runs that start at the first day of the year (1 January), + # - we have to consider the previous year land cover fractions + # + if iniConditions == None and start_on_1_Jan == True and \ + self.noLandCoverFractionCorrection and self.noAnnualChangesInLandCoverParameter == False: + # obtain the previous year land cover fractions: + previous_year = starting_year - 1 + one_january_prev_year = str(previous_year)+"-01-01" + for coverType in self.coverTypes: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].get_land_cover_parameters(date_in_string = one_january_prev_year, \ + get_only_fracVegCover = True) + + #################################################################################################################################################################### + # correcting land cover fractions + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].previousFracVegCover + + if 'grassland' in list(self.landCoverObj.keys()): + self.landCoverObj['grassland'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].previousFracVegCover, 1.0) + + if 'short_natural' in list(self.landCoverObj.keys()): + self.landCoverObj['short_natural'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].previousFracVegCover, 1.0) + + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].previousFracVegCover + + for coverType in self.coverTypes: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].previousFracVegCover / total_fractions + #################################################################################################################################################################### + + consider_previous_year_land_cover_fraction = True + + # For spin-up runs or for runs that start after 1 January, + # - we do not have to consider the previous year land cover fractions + # + if consider_previous_year_land_cover_fraction == False and \ + self.noLandCoverFractionCorrection and self.noAnnualChangesInLandCoverParameter == False: + # just using the current year land cover fractions: + one_january_this_year = str(starting_year)+"-01-01" + for coverType in self.coverTypes: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].get_land_cover_parameters(date_in_string = one_january_this_year, \ + get_only_fracVegCover = True) + + #################################################################################################################################################################### + # correcting land cover fractions + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].previousFracVegCover + + if 'grassland' in list(self.landCoverObj.keys()): + self.landCoverObj['grassland'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].previousFracVegCover, 1.0) + + if 'short_natural' in list(self.landCoverObj.keys()): + self.landCoverObj['short_natural'].previousFracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].previousFracVegCover, 1.0) + + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].previousFracVegCover + + for coverType in self.coverTypes: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].previousFracVegCover / total_fractions + #################################################################################################################################################################### + + + # get initial conditions + # - first, we set all aggregated states to zero (only the ones in mainStates): + for var in self.mainStates: vars(self)[var] = pcr.scalar(0.0) + # - then we initiate them in the following loop of land cover types: + for coverType in self.coverTypes: + if iniConditions != None: + self.landCoverObj[coverType].getICsLC(iniItems,iniConditions['landSurface'][coverType]) + else: + self.landCoverObj[coverType].getICsLC(iniItems) + # summarize/aggregate the initial states/storages (using the initial land cover fractions: previousFracVegCover) + for var in self.mainStates: + # - initial land cover fractions (dimensionless) + if self.landCoverObj[coverType].previousFracVegCover is None: + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].fracVegCover + land_cover_fraction = self.landCoverObj[coverType].previousFracVegCover + # - initial land cover states (unit: m) + land_cover_states = vars(self.landCoverObj[coverType])[var] + vars(self)[var] += land_cover_states * land_cover_fraction + + def waterDemandOptions(self,iniItems): + + # domestic water demand (unit: m/day) + # + self.domesticWaterDemandOption = False + if iniItems.landSurfaceOptions['includeDomesticWaterDemand'] == "True": + logger.info("Domestic water demand is included in the calculation.") + self.domesticWaterDemandOption = True + else: + logger.info("Domestic water demand is NOT included in the calculation.") + # + if self.domesticWaterDemandOption: + self.domesticWaterDemandFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['domesticWaterDemandFile'],self.inputDir,False) + + # industry water demand (unit: m/day) + # + self.industryWaterDemandOption = False + if iniItems.landSurfaceOptions['includeIndustryWaterDemand'] == "True": + logger.info("Industry water demand is included in the calculation.") + self.industryWaterDemandOption = True + else: + logger.info("Industry water demand is NOT included in the calculation.") + # + if self.industryWaterDemandOption: + self.industryWaterDemandFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['industryWaterDemandFile'],self.inputDir,False) + + # livestock water demand (unit: m/day) + self.livestockWaterDemandOption = False + if iniItems.landSurfaceOptions['includeLivestockWaterDemand'] == "True": + logger.info("Livestock water demand is included in the calculation.") + self.livestockWaterDemandOption = True + else: + logger.info("Livestock water demand is NOT included in the calculation.") + # + if self.livestockWaterDemandOption: + self.livestockWaterDemandFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['livestockWaterDemandFile'],self.inputDir,False) + + # historical irrigation area (unit: hectar) + self.dynamicIrrigationArea = False + if iniItems.landSurfaceOptions['historicalIrrigationArea'] != "None": + logger.info("Using the dynamicIrrigationArea option. Extent of irrigation areas is based on the file provided in the 'historicalIrrigationArea'.") + self.dynamicIrrigationArea = True + # + if self.dynamicIrrigationArea: + self.dynamicIrrigationAreaFile = vos.getFullPath(\ + iniItems.landSurfaceOptions['historicalIrrigationArea'],self.inputDir,False) + + # irrigation efficiency map (in percentage) # TODO: Using the time series of efficiency (considering historical technological development). + self.irrigationEfficiency = vos.readPCRmapClone(\ + iniItems.landSurfaceOptions['irrigationEfficiency'], + self.cloneMap,self.tmpDir,self.inputDir) + + extrapolate = True + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False + + if extrapolate: + + # extrapolate efficiency map: # TODO: Make a better extrapolation algorithm (considering cell size, etc.). + + window_size = 1.25 * pcr.clone().cellSize() + window_size = min(window_size, min(pcr.clone().nrRows(), pcr.clone().nrCols())*pcr.clone().cellSize()) + try: + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, window_size)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 0.75)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.00)) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, pcr.windowaverage(self.irrigationEfficiency, 1.50)) + except: + pass + + #~ self.irrigationEfficiency = pcr.ifthen(self.landmask, self.irrigationEfficiency) + self.irrigationEfficiency = pcr.cover(self.irrigationEfficiency, 1.0) + self.irrigationEfficiency = pcr.max(0.1, self.irrigationEfficiency) + self.irrigationEfficiency = pcr.ifthen(self.landmask, self.irrigationEfficiency) + + # desalination water supply option + self.includeDesalination = False + if iniItems.landSurfaceOptions['desalinationWater'] not in ["None", "False"]: + logger.info("Monthly desalination water is included.") + self.includeDesalination = True + self.desalinationWaterFile = vos.getFullPath(iniItems.landSurfaceOptions['desalinationWater'], self.inputDir) + else: + logger.info("Monthly desalination water is NOT included.") + + # zones at which water allocation (surface and groundwater allocation) is determined + self.usingAllocSegments = False + self.allocSegments = None + if iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'] != "None": + self.usingAllocSegments = True + + self.allocSegments = vos.readPCRmapClone(\ + iniItems.landSurfaceOptions['allocationSegmentsForGroundSurfaceWater'], + self.cloneMap,self.tmpDir,self.inputDir,isLddMap=False,cover=None,isNomMap=True) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + self.allocSegments = pcr.clump(self.allocSegments) + + extrapolate = True + if "noParameterExtrapolation" in iniItems.landSurfaceOptions.keys() and iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False + + if extrapolate: + # extrapolate it + self.allocSegments = pcr.cover(self.allocSegments, \ + pcr.windowmajority(self.allocSegments, 0.5)) + + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # clump it and cover the rests with cell ids + self.allocSegments = pcr.clump(self.allocSegments) + cell_ids = pcr.mapmaximum(pcr.scalar(self.allocSegments)) + pcr.scalar(100.0) + pcr.uniqueid(pcr.boolean(1.0)) + self.allocSegments = pcr.cover(self.allocSegments, pcr.nominal(cell_ids)) + self.allocSegments = pcr.clump(self.allocSegments) + self.allocSegments = pcr.ifthen(self.landmask, self.allocSegments) + + # cell area (unit: m2) + cellArea = vos.readPCRmapClone(\ + iniItems.routingOptions['cellAreaMap'], + self.cloneMap,self.tmpDir,self.inputDir) + cellArea = pcr.ifthen(self.landmask, cellArea) + + # zonal/segment area (unit: m2) + self.segmentArea = pcr.areatotal(pcr.cover(cellArea, 0.0), self.allocSegments) + self.segmentArea = pcr.ifthen(self.landmask, self.segmentArea) + + else: + + logger.info("If there is any, water demand is satisfied by local source only.") + + + def scaleNaturalLandCoverFractions(self): + ''' rescales natural land cover fractions (make sure the total = 1)''' + + # total land cover fractions + pristineAreaFrac = 0.0 + numb_of_lc_types = 0.0 + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + pristineAreaFrac += pcr.cover(self.landCoverObj[coverType].fracVegCover, 0.0) + numb_of_lc_types += 1.0 + + # Fill cells with pristineAreaFrac < 0.0 - with window average value within 0.5 and 1.5 degree + for coverType in self.coverTypes: + + if not coverType.startswith('irr'): + + extrapolate = True + if "noParameterExtrapolation" in self.iniItems.landSurfaceOptions.keys() and self.iniItems.landSurfaceOptions["noParameterExtrapolation"] == "True": extrapolate = False + + if extrapolate: + filled_fractions = pcr.windowaverage(self.landCoverObj[coverType].fracVegCover,0.5) + filled_fractions = pcr.cover(filled_fractions,\ + pcr.windowaverage(self.landCoverObj[coverType].fracVegCover,1.5)) + filled_fractions = pcr.max(0.0, filled_fractions) + filled_fractions = pcr.min(1.0, filled_fractions) + + self.landCoverObj[coverType].fracVegCover = pcr.ifthen(pristineAreaFrac >= 0.0, self.landCoverObj[coverType].fracVegCover) + self.landCoverObj[coverType].fracVegCover = pcr.cover(\ + self.landCoverObj[coverType].fracVegCover,filled_fractions) + self.landCoverObj[coverType].fracVegCover = pcr.ifthen(self.landmask,\ + self.landCoverObj[coverType].fracVegCover) + + # re-check total land cover fractions + pristineAreaFrac = 0.0 + numb_of_lc_types = 0.0 + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + pristineAreaFrac += pcr.cover(self.landCoverObj[coverType].fracVegCover, 0.0) + numb_of_lc_types += 1.0 + + # Fill cells with pristineAreaFrac = 0.0: + # - NOTE this only works for certain land cover names. TODO: FIX THIS + try: + self.landCoverObj['forest'].fracVegCover = pcr.ifthenelse(pristineAreaFrac > 0.0, self.landCoverObj['forest'].fracVegCover, 0.0) + self.landCoverObj['forest'].fracVegCover = pcr.min(1.0, self.landCoverObj['forest'].fracVegCover) + self.landCoverObj['grassland'].fracVegCover = 1.0 - self.landCoverObj['forest'].fracVegCover + except: + pass + + # recalculate total land cover fractions + pristineAreaFrac = 0.0 + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + pristineAreaFrac += pcr.cover(self.landCoverObj[coverType].fracVegCover, 0.0) + + # correcting + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + self.landCoverObj[coverType].fracVegCover = \ + self.landCoverObj[coverType].fracVegCover / pristineAreaFrac + + pristineAreaFrac = 0.0 # reset + # + # checking pristineAreaFrac (must be equal to 1) + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + pristineAreaFrac += self.landCoverObj[coverType].fracVegCover + self.landCoverObj[coverType].naturalFracVegCover = \ + self.landCoverObj[coverType].fracVegCover + # + # check and make sure that totalArea = 1.0 for all cells + totalArea = pristineAreaFrac + totalArea = pcr.ifthen(self.landmask,totalArea) + totalArea = pcr.cover(totalArea, 1.0) + check_map = totalArea - pcr.scalar(1.0) + a,b,c = vos.getMinMaxMean(check_map) + threshold = 1e-4 + if abs(a) > threshold or abs(b) > threshold: + logger.error("total of 'Natural Area' fractions is not equal to 1.0 ... Min %f Max %f Mean %f" %(a,b,c)) + + def scaleModifiedLandCoverFractions(self): + ''' rescales the land cover fractions with irrigation areas''' + + # calculate irrigatedAreaFrac (fraction of irrigation areas) + irrigatedAreaFrac = pcr.spatial(pcr.scalar(0.0)) + for coverType in self.coverTypes: + if coverType.startswith('irr'): + irrigatedAreaFrac = irrigatedAreaFrac + self.landCoverObj[coverType].fracVegCover + + # correcting/scaling fracVegCover of irrigation if irrigatedAreaFrac > 1 + for coverType in self.coverTypes: + if coverType.startswith('irr'): + self.landCoverObj[coverType].fracVegCover = pcr.ifthenelse(irrigatedAreaFrac > 1.0,\ + self.landCoverObj[coverType].fracVegCover/irrigatedAreaFrac,\ + self.landCoverObj[coverType].fracVegCover) + + # the corrected irrigated area fraction + irrigatedAreaFrac = pcr.spatial(pcr.scalar(0.0)) + for coverType in self.coverTypes: + if coverType.startswith('irr'): + irrigatedAreaFrac += self.landCoverObj[coverType].fracVegCover + + totalArea = pcr.spatial(pcr.scalar(0.0)) + totalArea += irrigatedAreaFrac + + # correction factor for forest and grassland (pristine Areas) + lcFrac = pcr.max(0.0, 1.0 - totalArea) + pristineAreaFrac = pcr.spatial(pcr.scalar(0.0)) + + for coverType in self.coverTypes: + if not coverType.startswith('irr'): + self.landCoverObj[coverType].fracVegCover = 0.0 + self.landCoverObj[coverType].fracVegCover = \ + self.landCoverObj[coverType].naturalFracVegCover * lcFrac + pristineAreaFrac += pcr.cover(\ + self.landCoverObj[coverType].fracVegCover, 0.0) + + # check and make sure that totalArea = 1.0 for all cells + totalArea += pristineAreaFrac + totalArea = pcr.ifthen(self.landmask,totalArea) + totalArea = pcr.cover(totalArea, 1.0) + totalArea = pcr.ifthen(self.landmask,totalArea) + a,b,c = vos.getMinMaxMean(totalArea - pcr.scalar(1.0)) + threshold = 1e-4 + if abs(a) > threshold or abs(b) > threshold: + logger.error("fraction total (from all land cover types) is not equal to 1.0 ... Min %f Max %f Mean %f" %(a,b,c)) + + def obtainNonIrrWaterDemand(self,routing,currTimeStep): + # get NON-Irrigation GROSS water demand and its return flow fraction + + # domestic water demand + if currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1: + if self.domesticWaterDemandOption: + # + if self.domesticWaterDemandFile.endswith(vos.netcdf_suffixes): + # + self.domesticGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.domesticWaterDemandFile,\ + 'domesticGrossDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + # + self.domesticNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.domesticWaterDemandFile,\ + 'domesticNettoDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + else: + string_month = str(currTimeStep.month) + if currTimeStep.month < 10: string_month = "0"+str(currTimeStep.month) + grossFileName = self.domesticWaterDemandFile+"w"+str(currTimeStep.year)+".0"+string_month + self.domesticGrossDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(grossFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + nettoFileName = self.domesticWaterDemandFile+"n"+str(currTimeStep.year)+".0"+string_month + self.domesticNettoDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(nettoFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + else: + self.domesticGrossDemand = pcr.spatial(pcr.scalar(0.0)) + self.domesticNettoDemand = pcr.spatial(pcr.scalar(0.0)) + logger.debug("Domestic water demand is NOT included.") + + # gross and netto domestic water demand in m/day + self.domesticGrossDemand = pcr.cover(self.domesticGrossDemand,0.0) + self.domesticNettoDemand = pcr.cover(self.domesticNettoDemand,0.0) + self.domesticNettoDemand = pcr.min(self.domesticGrossDemand, self.domesticNettoDemand) + + # industry water demand + if currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1: + if self.industryWaterDemandOption: + # + if self.industryWaterDemandFile.endswith(vos.netcdf_suffixes): + # + self.industryGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.industryWaterDemandFile,\ + 'industryGrossDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + # + self.industryNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.industryWaterDemandFile,\ + 'industryNettoDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + else: + grossFileName = self.industryWaterDemandFile+"w"+str(currTimeStep.year)+".map" + self.industryGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.readPCRmapClone(grossFileName,self.cloneMap,self.tmpDir), 0.0)) + nettoFileName = self.industryWaterDemandFile+"n"+str(currTimeStep.year)+".map" + self.industryNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.readPCRmapClone(nettoFileName,self.cloneMap,self.tmpDir), 0.0)) + else: + self.industryGrossDemand = pcr.spatial(pcr.scalar(0.0)) + self.industryNettoDemand = pcr.spatial(pcr.scalar(0.0)) + logger.debug("Industry water demand is NOT included.") + + # gross and netto industrial water demand in m/day + self.industryGrossDemand = pcr.cover(self.industryGrossDemand,0.0) + self.industryNettoDemand = pcr.cover(self.industryNettoDemand,0.0) + self.industryNettoDemand = pcr.min(self.industryGrossDemand, self.industryNettoDemand) + + # livestock water demand + if currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1: + if self.livestockWaterDemandOption: + # + if self.livestockWaterDemandFile.endswith(vos.netcdf_suffixes): + # + self.livestockGrossDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.livestockWaterDemandFile,\ + 'livestockGrossDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + # + self.livestockNettoDemand = pcr.max(0.0, pcr.cover(\ + vos.netcdf2PCRobjClone(self.livestockWaterDemandFile,\ + 'livestockNettoDemand',\ + currTimeStep.fulldate, useDoy = 'monthly',\ + cloneMapFileName = self.cloneMap), 0.0)) + else: + string_month = str(currTimeStep.month) + if currTimeStep.month < 10: string_month = "0"+str(currTimeStep.month) + grossFileName = self.livestockWaterDemandFile+"w"+str(currTimeStep.year)+".0"+string_month + self.livestockGrossDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(grossFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + nettoFileName = self.livestockWaterDemandFile+"n"+str(currTimeStep.year)+".0"+string_month + self.livestockNettoDemand = pcr.max(pcr.cover(\ + vos.readPCRmapClone(nettoFileName,self.cloneMap,self.tmpDir), 0.0), 0.0) + else: + self.livestockGrossDemand = pcr.spatial(pcr.scalar(0.0)) + self.livestockNettoDemand = pcr.spatial(pcr.scalar(0.0)) + logger.debug("Livestock water demand is NOT included.") + + # gross and netto livestock water demand in m/day + self.livestockGrossDemand = pcr.cover(self.livestockGrossDemand,0.0) + self.livestockNettoDemand = pcr.cover(self.livestockNettoDemand,0.0) + self.livestockNettoDemand = pcr.min(self.livestockGrossDemand, self.livestockNettoDemand) + + # GROSS domestic, industrial and livestock water demands (unit: m/day) + self.domesticGrossDemand = pcr.ifthen(self.landmask, self.domesticGrossDemand ) + self.domesticNettoDemand = pcr.ifthen(self.landmask, self.domesticNettoDemand ) + self.industryGrossDemand = pcr.ifthen(self.landmask, self.industryGrossDemand ) + self.industryNettoDemand = pcr.ifthen(self.landmask, self.industryNettoDemand ) + self.livestockGrossDemand = pcr.ifthen(self.landmask, self.livestockGrossDemand) + self.livestockNettoDemand = pcr.ifthen(self.landmask, self.livestockNettoDemand) + + # RETURN FLOW fractions for domestic, industrial and livestock water demands (unit: fraction/percentage) + self.domesticReturnFlowFraction = pcr.min(1.0, pcr.max(0.0, 1.0 - vos.getValDivZero(self.domesticNettoDemand, self.domesticGrossDemand))) + self.industryReturnFlowFraction = pcr.min(1.0, pcr.max(0.0, 1.0 - vos.getValDivZero(self.industryNettoDemand, self.industryGrossDemand))) + self.livestockReturnFlowFraction = pcr.min(1.0, pcr.max(0.0, 1.0 - vos.getValDivZero(self.livestockNettoDemand, self.livestockGrossDemand))) + + # make a dictionary summarizing potential demand (potential withdrawal) and its return flow fraction + nonIrrigationWaterDemandDict = {} + nonIrrigationWaterDemandDict['potential_demand'] = {} + nonIrrigationWaterDemandDict['potential_demand']['domestic'] = self.domesticGrossDemand + nonIrrigationWaterDemandDict['potential_demand']['industry'] = self.industryGrossDemand + nonIrrigationWaterDemandDict['potential_demand']['livestock'] = self.livestockGrossDemand + nonIrrigationWaterDemandDict['return_flow_fraction'] = {} + nonIrrigationWaterDemandDict['return_flow_fraction']['domestic'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.domesticReturnFlowFraction *1000.)/1000.), 1.0) + nonIrrigationWaterDemandDict['return_flow_fraction']['industry'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.industryReturnFlowFraction *1000.)/1000.), 1.0) + nonIrrigationWaterDemandDict['return_flow_fraction']['livestock'] = pcr.cover(pcr.min(1.0, pcr.roundup(self.livestockReturnFlowFraction*1000.)/1000.), 1.0) + + return nonIrrigationWaterDemandDict + + def calculateCapRiseFrac(self,groundwater,routing,currTimeStep): + # calculate cell fraction influenced by capillary rise: + + # relative groundwater head (m) above the minimum elevation within a grid cell + if groundwater.useMODFLOW == True: + + dzGroundwater = groundwater.relativeGroundwaterHead + + # update dzGroundwater from file, from modflow calculation, using the previous time step + # - assumption that it will be updated once every month + + if currTimeStep.day == 1 and currTimeStep.timeStepPCR > 1: + + # for online coupling, we will read files from pcraster maps + directory = self.iniItems.main_output_directory + "/modflow/transient/maps/" + + # - relative groundwater head from MODFLOW + yesterday = str(currTimeStep.yesterday()) + filename = directory + "relativeGroundwaterHead_" + str(yesterday) + ".map" + dzGroundwater = pcr.ifthen(self.landmask, pcr.cover(vos.readPCRmapClone(filename, self.cloneMap, self.tmpDir), 0.0)) + + else: + dzGroundwater = groundwater.storGroundwater/groundwater.specificYield + + # add some tolerance/influence level (unit: m) + dzGroundwater += self.soil_topo_parameters['default'].maxGWCapRise; + + # set minimum value to zero (zero relativeGroundwaterHead indicate no capRiseFrac) + dzGroundwater = pcr.max(0.0, dzGroundwater) + + # approximate cell fraction under influence of capillary rise + + FRACWAT = pcr.spatial(pcr.scalar(0.0)); + if currTimeStep.timeStepPCR > 1: + FRACWAT = pcr.cover(routing.WaterBodies.fracWat, 0.0); + else: + if routing.includeWaterBodies: + if routing.WaterBodies.useNetCDF: + routing.WaterBodies.fracWat = vos.netcdf2PCRobjClone(\ + routing.WaterBodies.ncFileInp,'fracWaterInp', \ + currTimeStep.fulldate, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap) + else: + if routing.WaterBodies.fracWaterInp != "None": + routing.WaterBodies.fracWat = vos.readPCRmapClone(\ + routing.WaterBodies.fracWaterInp+str(currTimeStep.year)+".map", + self.cloneMap,self.tmpDir,self.inputDir) + else: + routing.WaterBodies.fracWat = pcr.spatial(pcr.scalar(0.0)) + else: + if routing.WaterBodies.useNetCDF: + routing.WaterBodies.fracWat = vos.netcdf2PCRobjClone(\ + routing.WaterBodies.ncFileInp,'fracWaterInp', \ + currTimeStep.fulldate, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap) + else: + if routing.WaterBodies.fracWaterInp != "None": + routing.WaterBodies.fracWat = vos.readPCRmapClone(\ + routing.WaterBodies.fracWaterInp, + self.cloneMap,self.tmpDir,self.inputDir) + else: + routing.WaterBodies.fracWat = pcr.spatial(pcr.scalar(0.0)) + # Note that the variable used in the following line is FRACWAT (this may be a 'small' bug fixing to the GMD paper version) + FRACWAT = pcr.cover(routing.WaterBodies.fracWat, 0.0); + FRACWAT = pcr.cover(FRACWAT, 0.0) + + # zero fracwat assumption used for debugging against version 1.0 + if routing.zeroFracWatAllAndAlways: FRACWAT = pcr.scalar(0.0) + + + CRFRAC = pcr.min( 1.0,1.0 -(self.soil_topo_parameters['default'].dzRel0100-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0100-self.soil_topo_parameters['default'].dzRel0090) ); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0090,0.9 -(self.soil_topo_parameters['default'].dzRel0090-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0090-self.soil_topo_parameters['default'].dzRel0080),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0080,0.8 -(self.soil_topo_parameters['default'].dzRel0080-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0080-self.soil_topo_parameters['default'].dzRel0070),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0070,0.7 -(self.soil_topo_parameters['default'].dzRel0070-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0070-self.soil_topo_parameters['default'].dzRel0060),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0060,0.6 -(self.soil_topo_parameters['default'].dzRel0060-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0060-self.soil_topo_parameters['default'].dzRel0050),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0050,0.5 -(self.soil_topo_parameters['default'].dzRel0050-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0050-self.soil_topo_parameters['default'].dzRel0040),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0040,0.4 -(self.soil_topo_parameters['default'].dzRel0040-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0040-self.soil_topo_parameters['default'].dzRel0030),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0030,0.3 -(self.soil_topo_parameters['default'].dzRel0030-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0030-self.soil_topo_parameters['default'].dzRel0020),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0020,0.2 -(self.soil_topo_parameters['default'].dzRel0020-dzGroundwater)*0.1 /pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0020-self.soil_topo_parameters['default'].dzRel0010),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0010,0.1 -(self.soil_topo_parameters['default'].dzRel0010-dzGroundwater)*0.05/pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0010-self.soil_topo_parameters['default'].dzRel0005),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0005,0.05-(self.soil_topo_parameters['default'].dzRel0005-dzGroundwater)*0.04/pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0005-self.soil_topo_parameters['default'].dzRel0001),CRFRAC); + CRFRAC = pcr.ifthenelse(dzGroundwater < self.soil_topo_parameters['default'].dzRel0001,0.01-(self.soil_topo_parameters['default'].dzRel0001-dzGroundwater)*0.01/pcr.max(0.001,self.soil_topo_parameters['default'].dzRel0001 ),CRFRAC); + + #~ print(FRACWAT) + + CRFRAC = pcr.ifthenelse(FRACWAT < 1.0,pcr.max(0.0,CRFRAC-FRACWAT)/(1.0-FRACWAT),0.0); + + capRiseFrac = pcr.max(0.0,pcr.min(1.0,CRFRAC)) + + #~ capRiseFrac = 0.0 + + return capRiseFrac + + def partitioningGroundSurfaceAbstraction(self,groundwater,routing): + + # partitioning abstraction sources: groundwater and surface water + # de Graaf et al., 2014 principle: partitioning based on local average baseflow (m3/s) and upstream average discharge (m3/s) + # - estimates of fractions of groundwater and surface water abstractions + averageBaseflowInput = routing.avgBaseflow + averageUpstreamInput = pcr.max(routing.avgDischarge, pcr.cover(pcr.upstream(routing.lddMap, routing.avgDischarge), 0.0)) + + if self.usingAllocSegments: + + averageBaseflowInput = pcr.max(0.0, pcr.ifthen(self.landmask, averageBaseflowInput)) + averageUpstreamInput = pcr.max(0.0, pcr.ifthen(self.landmask, averageUpstreamInput)) + + averageBaseflowInput = pcr.cover(pcr.areaaverage(averageBaseflowInput, self.allocSegments), 0.0) + averageUpstreamInput = pcr.cover(pcr.areamaximum(averageUpstreamInput, self.allocSegments), 0.0) + + else: + logger.debug("Water demand can only be satisfied by local source.") + + swAbstractionFraction = vos.getValDivZero(\ + averageUpstreamInput, + averageUpstreamInput+averageBaseflowInput, vos.smallNumber) + swAbstractionFraction = pcr.roundup(swAbstractionFraction*100.)/100. + swAbstractionFraction = pcr.max(0.0, swAbstractionFraction) + swAbstractionFraction = pcr.min(1.0, swAbstractionFraction) + + if self.usingAllocSegments: + swAbstractionFraction = pcr.areamaximum(swAbstractionFraction, self.allocSegments) + + swAbstractionFraction = pcr.cover(swAbstractionFraction, 1.0) + swAbstractionFraction = pcr.ifthen(self.landmask, swAbstractionFraction) + + # making a dictionary containing the surface water fraction for various purpose + swAbstractionFractionDict = {} + # - the default estimate (based on de Graaf et al., 2014) + swAbstractionFractionDict['estimate'] = swAbstractionFraction + # - for irrigation and livestock purpose + swAbstractionFractionDict['irrigation'] = swAbstractionFraction + # - for industrial and domestic purpose + swAbstractionFractionDict['max_for_non_irrigation'] = swAbstractionFraction + # + # - a treshold fraction value to optimize/maximize surface water withdrawal for irrigation + # Principle: Areas with swAbstractionFractionDict['irrigation'] above this treshold will prioritize surface water use for irrigation purpose. + # A zero treshold value will ignore this principle. + swAbstractionFractionDict['treshold_to_maximize_irrigation_surface_water'] = self.treshold_to_maximize_irrigation_surface_water + # + # - a treshold fraction value to minimize fossil groundwater withdrawal, particularly to remove the unrealistic areas of fossil groundwater abstraction + # Principle: Areas with swAbstractionFractionDict['irrigation'] above this treshold will not extract fossil groundwater. + swAbstractionFractionDict['treshold_to_minimize_fossil_groundwater_irrigation'] = self.treshold_to_minimize_fossil_groundwater_irrigation + + + # the default value of surface water source fraction is None or not defined (in this case, this value will be the 'estimate' and limited with 'max_for_non_irrigation') + swAbstractionFractionDict['non_irrigation'] = None + + # incorporating the pre-defined fraction of surface water sources (e.g. based on Siebert et al., 2014 and McDonald et al., 2014) + if self.swAbstractionFractionData is not None: + + logger.debug('Using/incorporating the predefined fractions of surface water source.') + swAbstractionFractionDict['estimate'] = swAbstractionFraction + swAbstractionFractionDict['irrigation'] = self.partitioningGroundSurfaceAbstractionForIrrigation(swAbstractionFraction,\ + self.swAbstractionFractionData,\ + self.swAbstractionFractionDataQuality) + swAbstractionFractionDict['max_for_non_irrigation'] = self.maximumNonIrrigationSurfaceWaterAbstractionFractionData + + if self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData is not None: + swAbstractionFractionDict['non_irrigation'] = pcr.cover( + self.predefinedNonIrrigationSurfaceWaterAbstractionFractionData, \ + swAbstractionFractionDict['estimate']) + swAbstractionFractionDict['non_irrigation'] = pcr.min(\ + swAbstractionFractionDict['non_irrigation'], \ + swAbstractionFractionDict['max_for_non_irrigation']) + + else: + + logger.debug('NOT using/incorporating the predefined fractions of surface water source.') + + return swAbstractionFractionDict + + def partitioningGroundSurfaceAbstractionForIrrigation(self,\ + swAbstractionFractionEstimate,\ + swAbstractionFractionData,\ + swAbstractionFractionDataQuality): + + # surface water source fraction based on Stefan Siebert's map: + factor = 0.5 # using this factor, the minimum value for the following 'data_weight_value' is 0.75 (for swAbstractionFractionDataQuality == 5) + data_weight_value = pcr.scalar(1.0) - \ + (pcr.min(5., pcr.max(0.0, swAbstractionFractionDataQuality))/10.0)*factor + + swAbstractionFractionForIrrigation = data_weight_value * swAbstractionFractionData +\ + (1.0 - data_weight_value) * swAbstractionFractionEstimate + + swAbstractionFractionForIrrigation = pcr.cover(swAbstractionFractionForIrrigation, swAbstractionFractionEstimate) + swAbstractionFractionForIrrigation = pcr.cover(swAbstractionFractionForIrrigation, 1.0) + swAbstractionFractionForIrrigation = pcr.ifthen(self.landmask, swAbstractionFractionForIrrigation) + + return swAbstractionFractionForIrrigation + + def scaleDynamicIrrigation(self,yearInInteger): + # This method is to update fracVegCover of landCover for historical irrigation areas (done at yearly basis). + + + #~ # Available datasets are only from 1960 to 2010 (status on 24 September 2010) + #~ yearInInteger = int(yearInInteger) + #~ if float(yearInInteger) < 1960. or float(yearInInteger) > 2010.: + #~ msg = 'Dataset for the year '+str(yearInInteger)+" is not available. Dataset of historical irrigation areas is only available from 1960 to 2010." + #~ logger.warning(msg) + #~ yearInInteger = min(2010, max(1960, yearInInteger)) + # + # TODO: Generally, I do not need the aforementioned lines as I have defined the functions "findLastYearInNCTime" and "findFirstYearInNCTime" in the module virtualOS.py + # However, Niko still need them for his DA scheme as we somehow his DA scheme cannot handle the netcdf file of historical irrigation areas (and therefore we have to use pcraster map files). + + + yearInString = str(yearInInteger) + + # read historical irrigation areas + if self.dynamicIrrigationAreaFile.endswith(('.nc4','.nc')): + fulldateInString = yearInString+"-01"+"-01" + self.irrigationArea = 10000. * pcr.cover(\ + vos.netcdf2PCRobjClone(self.dynamicIrrigationAreaFile,\ + 'irrigationArea',\ + fulldateInString, useDoy = 'yearly',\ + cloneMapFileName = self.cloneMap), 0.0) # unit: m2 (input file is in hectare) + else: + irrigation_pcraster_file = self.dynamicIrrigationAreaFile + yearInString + ".map" + logger.debug('reading irrigation area map from : '+irrigation_pcraster_file) + self.irrigationArea = 10000. * pcr.cover(\ + vos.readPCRmapClone(irrigation_pcraster_file,\ + self.cloneMap,self.tmpDir), 0.0) # unit: m2 (input file is in hectare) + + # TODO: Convert the input file, from hectare to percentage. + # This is to avoid errors if somebody uses 30 min input to run his 5 min model. + + # area of irrigation is limited by cellArea + self.irrigationArea = pcr.max(self.irrigationArea, 0.0) + self.irrigationArea = pcr.min(self.irrigationArea, self.cellArea) # limited by cellArea + + # calculate fracVegCover (for irrigation only) + for coverType in self.coverTypes: + if coverType.startswith('irr'): + + self.landCoverObj[coverType].fractionArea = 0.0 # reset + self.landCoverObj[coverType].fractionArea = self.landCoverObj[coverType].irrTypeFracOverIrr * self.irrigationArea # unit: m2 + self.landCoverObj[coverType].fracVegCover = pcr.min(1.0, self.landCoverObj[coverType].fractionArea/ self.cellArea) + + # avoid small values + self.landCoverObj[coverType].fracVegCover = pcr.rounddown(self.landCoverObj[coverType].fracVegCover * 1000.)/1000. + + # rescale land cover fractions (for all land cover types): + self.scaleModifiedLandCoverFractions() + + def update(self,meteo,groundwater,routing,currTimeStep): + + # updating regional groundwater abstraction limit (at the begining of the year or at the beginning of simulation) + if groundwater.limitRegionalAnnualGroundwaterAbstraction: + + logger.debug('Total groundwater abstraction is limited by regional annual pumping capacity.') + if currTimeStep.doy == 1 or currTimeStep.timeStepPCR == 1: + + self.groundwater_pumping_region_ids = \ + vos.netcdf2PCRobjClone(groundwater.pumpingCapacityNC,'region_ids',\ + currTimeStep.fulldate, useDoy = 'yearly', cloneMapFileName = self.cloneMap) + other_ids = pcr.mapmaximum(self.groundwater_pumping_region_ids) + pcr.scalar(1000.) + pcr.uniqueid(self.landmask) + self.groundwater_pumping_region_ids = pcr.cover(self.groundwater_pumping_region_ids, other_ids) + self.groundwater_pumping_region_ids = pcr.ifthen(self.landmask, pcr.nominal(self.groundwater_pumping_region_ids)) + + self.regionalAnnualGroundwaterAbstractionLimit = \ + pcr.ifthen(self.landmask,\ + pcr.cover(\ + vos.netcdf2PCRobjClone(groundwater.pumpingCapacityNC,'regional_pumping_limit',\ + currTimeStep.fulldate, useDoy = 'yearly', cloneMapFileName = self.cloneMap), 0.0)) + + self.regionalAnnualGroundwaterAbstractionLimit = pcr.areamaximum(self.regionalAnnualGroundwaterAbstractionLimit, self.groundwater_pumping_region_ids) + + self.regionalAnnualGroundwaterAbstractionLimit *= 1000. * 1000. * 1000. # unit: m3/year + self.regionalAnnualGroundwaterAbstractionLimit = pcr.ifthen(self.landmask,\ + self.regionalAnnualGroundwaterAbstractionLimit) + # minimum value (unit: m3/year at the regional scale) + minimum_value = 1000. + self.regionalAnnualGroundwaterAbstractionLimit = pcr.max(minimum_value,\ + self.regionalAnnualGroundwaterAbstractionLimit) + else: + + logger.debug('Total groundwater abstraction is NOT limited by regional annual pumping capacity.') + self.groundwater_pumping_region_ids = None + self.regionalAnnualGroundwaterAbstractionLimit = None + + # updating fracVegCover of each landCover (landCover fraction) + # - if considering dynamic/historical irrigation areas (expansion/reduction of irrigated areas) + # - done at yearly basis, at the beginning of each year, also at the beginning of simulation + # + if self.dynamicIrrigationArea and self.includeIrrigation and \ + (currTimeStep.timeStepPCR == 1 or currTimeStep.doy == 1) and self.noLandCoverFractionCorrection == False: + + # scale land cover fraction (due to expansion/reduction of irrigated areas) + self.scaleDynamicIrrigation(currTimeStep.year) + + #################################################################################################################################################################### + # correcting land cover fractions + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].fracVegCover + + if 'grassland' in list(self.landCoverObj.keys()): + self.landCoverObj['grassland'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].fracVegCover, 1.0) + + if 'short_natural' in list(self.landCoverObj.keys()): + self.landCoverObj['short_natural'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].fracVegCover, 1.0) + + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].fracVegCover + + for coverType in self.coverTypes: + self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].fracVegCover / total_fractions + #################################################################################################################################################################### + + + # read land cover fractions from netcdf files + # - assumption: annual resolution + if self.noAnnualChangesInLandCoverParameter == False and self.dynamicIrrigationArea == False and \ + (currTimeStep.timeStepPCR == 1 or currTimeStep.doy == 1): + msg = 'Read land cover fractions based on the given netcdf file.' + logger.debug(msg) + for coverType in self.coverTypes: + self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].get_land_cover_parameters(date_in_string = str(currTimeStep.fulldate), \ + get_only_fracVegCover = True) + + #################################################################################################################################################################### + # correcting land cover fractions + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].fracVegCover + + if 'grassland' in list(self.landCoverObj.keys()): + self.landCoverObj['grassland'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['grassland'].fracVegCover, 1.0) + + if 'short_natural' in list(self.landCoverObj.keys()): + self.landCoverObj['short_natural'].fracVegCover = pcr.ifthenelse(total_fractions > 0.1, self.landCoverObj['short_natural'].fracVegCover, 1.0) + + total_fractions = pcr.scalar(0.0) + for coverType in self.coverTypes: + total_fractions += self.landCoverObj[coverType].fracVegCover + + for coverType in self.coverTypes: + self.landCoverObj[coverType].fracVegCover = self.landCoverObj[coverType].fracVegCover / total_fractions + #################################################################################################################################################################### + + + # transfer some states, due to changes/dynamics in land cover conditions + # - if considering dynamic/historical irrigation areas (expansion/reduction of irrigated areas) + # - done at yearly basis, at the beginning of each year + # - note that this must be done at the beginning of each year, including for the first time step (timeStepPCR == 1) + # + if ((self.dynamicIrrigationArea and self.includeIrrigation) or self.noAnnualChangesInLandCoverParameter == False) and currTimeStep.doy == 1: + # + # loop for all main states: + for var in self.mainStates: + + logger.info("Transfering states for the variable "+str(var)) + + moving_fraction = pcr.scalar(0.0) # total land cover fractions that will be transferred + moving_states = pcr.scalar(0.0) # total states that will be transferred + + for coverType in self.coverTypes: + + old_fraction = self.landCoverObj[coverType].previousFracVegCover + new_fraction = self.landCoverObj[coverType].fracVegCover + + moving_fraction += pcr.max(0.0, old_fraction-new_fraction) + moving_states += pcr.max(0.0, old_fraction-new_fraction) * vars(self.landCoverObj[coverType])[var] + + previous_state = pcr.scalar(0.0) + rescaled_state = pcr.scalar(0.0) + + # correcting states + for coverType in self.coverTypes: + + old_states = vars(self.landCoverObj[coverType])[var] + old_fraction = self.landCoverObj[coverType].previousFracVegCover + new_fraction = self.landCoverObj[coverType].fracVegCover + + correction = moving_states *\ + vos.getValDivZero( pcr.max(0.0, new_fraction - old_fraction),\ + moving_fraction, vos.smallNumber ) + + new_states = pcr.ifthenelse(new_fraction > old_fraction, + vos.getValDivZero( + old_states * old_fraction + correction, \ + new_fraction, vos.smallNumber), old_states) + + new_states = pcr.ifthenelse(new_fraction > 0.0, new_states, pcr.scalar(0.0)) + + vars(self.landCoverObj[coverType])[var] = new_states + + previous_state += old_fraction * old_states + rescaled_state += new_fraction * new_states + + # check and make sure that previous_state == rescaled_state + check_map = previous_state - rescaled_state + a,b,c = vos.getMinMaxMean(check_map) + threshold = 1e-5 + if abs(a) > threshold or abs(b) > threshold: + logger.warning("Error in transfering states (due to dynamic in land cover fractions) ... Min %f Max %f Mean %f" %(a,b,c)) + else: + logger.info("Successful in transfering states (after change in land cover fractions) ... Min %f Max %f Mean %f" %(a,b,c)) + + # for the last day of the year, we have to save the previous land cover fractions (to be considered in the next time step) + if self.dynamicIrrigationArea and self.includeIrrigation and currTimeStep.isLastDayOfYear: + # save the current state of fracVegCover + for coverType in self.coverTypes:\ + self.landCoverObj[coverType].previousFracVegCover = self.landCoverObj[coverType].fracVegCover + + #- RvB: irrigation water efficiency + # added here are the lines required to read in the water efficiency + # irrigation water efficiency is updated at the start of the year and + if self.includeIrrigation and (currTimeStep.doy == 1 or currTimeStep.timeStepPCR == 1): + logger.info("Setting irrigation water efficiency") + for coverType in self.coverTypes: + self.landCoverObj[coverType].updateIrrigationWaterEfficiency(currTimeStep) + + # calculate cell fraction influenced by capillary rise: + self.capRiseFrac = self.calculateCapRiseFrac(groundwater,routing,currTimeStep) + + # get a dictionary containing livestock, domestic and industrial water demand, including their return flow fractions + self.nonIrrigationWaterDemandDict = self.obtainNonIrrWaterDemand(routing, currTimeStep) + + # get a dictionary containing the partitioning of withdrawal/abstraction sources: (from groundwater and surface water) + self.swAbstractionFractionDict = self.partitioningGroundSurfaceAbstraction(groundwater,routing) + + # get desalination water use (m/day); assume this one as potential supply + if self.includeDesalination: + logger.debug("Monthly desalination water use is included.") + if (currTimeStep.timeStepPCR == 1 or currTimeStep.day == 1): + desalinationWaterUse = \ + pcr.ifthen(self.landmask,\ + pcr.cover(\ + vos.netcdf2PCRobjClone(self.desalinationWaterFile,'desalination_water_use',\ + currTimeStep.fulldate, useDoy = 'monthly', cloneMapFileName = self.cloneMap), 0.0)) + self.desalinationWaterUse = pcr.max(0.0, desalinationWaterUse) + else: + logger.debug("Monthly desalination water use is NOT included.") + self.desalinationWaterUse = pcr.scalar(0.0) + + # update (loop per each land cover type): + for coverType in self.coverTypes: + + logger.info("Updating land cover: "+str(coverType)) + self.landCoverObj[coverType].updateLC(meteo,groundwater,routing,\ + self.capRiseFrac,\ + self.nonIrrigationWaterDemandDict,\ + self.swAbstractionFractionDict,\ + currTimeStep,\ + self.allocSegments,\ + self.desalinationWaterUse,\ + self.groundwater_pumping_region_ids,self.regionalAnnualGroundwaterAbstractionLimit) + + # first, we set all aggregated values/variables to zero: + for var in self.aggrVars: vars(self)[var] = pcr.scalar(0.0) + # + # get or calculate the values of all aggregated values/variables + for coverType in self.coverTypes: + # calculate the aggregrated or global landSurface values: + for var in self.aggrVars: + vars(self)[var] += \ + self.landCoverObj[coverType].fracVegCover * vars(self.landCoverObj[coverType])[var] + + # total storages (unit: m3) in the entire landSurface module + if self.numberOfSoilLayers == 2: self.totalSto = \ + self.snowCoverSWE + self.snowFreeWater + self.interceptStor +\ + self.topWaterLayer +\ + self.storUpp +\ + self.storLow + # + if self.numberOfSoilLayers == 3: self.totalSto = \ + self.snowCoverSWE + self.snowFreeWater + self.interceptStor +\ + self.topWaterLayer +\ + self.storUpp000005 + self.storUpp005030 +\ + self.storLow030150 + + # calculate irrigation water demand (for the next time step) + # self.calculate_irrigation_water_demand() + + # old-style reporting (this is useful for debugging) + self.old_style_land_surface_reporting(currTimeStep) + + + # .................................................................. + +# def calculate_irrigation_water_demand(self): +# # irrigation water demand (unit: m/day) for paddy and non-paddy +# self.irrGrossDemand = pcr.scalar(0.0) +# if (self.name == 'irrPaddy' or self.name == 'irr_paddy') and self.includeIrrigation: +# self.irrGrossDemand = \ +# pcr.ifthenelse(self.cropKC > 0.75, \ +# pcr.max(0.0,self.minTopWaterLayer - \ +# (self.topWaterLayer )), 0.) # a function of cropKC (evaporation and transpiration), +# # topWaterLayer (water available in the irrigation field) +# +# if (self.name == 'irrNonPaddy' or self.name == 'irr_non_paddy' or self.name == "irr_non_paddy_crops") and self.includeIrrigation: +# +# #~ adjDeplFactor = \ +# #~ pcr.max(0.1,\ +# #~ pcr.min(0.8,(self.cropDeplFactor + \ +# #~ 40.*(0.005-self.totalPotET)))) # from Wada et al. (2014) +# adjDeplFactor = \ +# pcr.max(0.1,\ +# pcr.min(0.8,(self.cropDeplFactor + \ +# 0.04*(5.-self.totalPotET*1000.)))) # original formula based on Allen et al. (1998) +# # see: http://www.fao.org/docrep/x0490e/x0490e0e.htm# +# # +# #~ # alternative 1: irrigation demand (to fill the entire totAvlWater, maintaining the field capacity) - NOT USED +# #~ self.irrGrossDemand = \ +# #~ pcr.ifthenelse( self.cropKC > 0.20, \ +# #~ pcr.ifthenelse( self.readAvlWater < \ +# #~ adjDeplFactor*self.totAvlWater, \ +# #~ pcr.max(0.0, self.totAvlWater-self.readAvlWater),0.),0.) # a function of cropKC and totalPotET (evaporation and transpiration), +# #~ # readAvlWater (available water in the root zone) +# +# # alternative 2: irrigation demand (to fill the entire totAvlWater, maintaining the field capacity, +# # but with the correction of totAvlWater based on the rooting depth) +# # - as the proxy of rooting depth, we use crop coefficient +# self.irrigation_factor = pcr.ifthenelse(self.cropKC > 0.0,\ +# pcr.min(1.0, self.cropKC / 1.0), 0.0) +# self.irrGrossDemand = \ +# pcr.ifthenelse( self.cropKC > 0.20, \ +# pcr.ifthenelse( self.readAvlWater < \ +# adjDeplFactor*self.irrigation_factor*self.totAvlWater, \ +# pcr.max(0.0, self.totAvlWater*self.irrigation_factor-self.readAvlWater),0.),0.) +# +# # irrigation demand is implemented only if there is deficit in transpiration and/or evaporation +# deficit_factor = 1.00 +# evaporationDeficit = pcr.max(0.0, (self.potBareSoilEvap + self.potTranspiration)*deficit_factor -\ +# self.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True)) +# transpirationDeficit = pcr.max(0.0, +# self.potTranspiration*deficit_factor -\ +# self.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True, returnTotalTranspirationOnly = True)) +# deficit = pcr.max(evaporationDeficit, transpirationDeficit) +# # +# # treshold to initiate irrigation +# deficit_treshold = 0.20 * self.totalPotET +# need_irrigation = pcr.ifthenelse(deficit > deficit_treshold, pcr.boolean(1),\ +# pcr.ifthenelse(self.soilWaterStorage == 0.000, pcr.boolean(1), pcr.boolean(0))) +# need_irrigation = pcr.cover(need_irrigation, pcr.boolean(0.0)) +# # +# self.irrGrossDemand = pcr.ifthenelse(need_irrigation, self.irrGrossDemand, 0.0) +# +# # demand is limited by potential evaporation for the next coming days +# # - objective: to avoid too high and unrealistic demand +# max_irrigation_interval = 15.0 +# min_irrigation_interval = 7.0 +# irrigation_interval = pcr.min(max_irrigation_interval, \ +# pcr.max(min_irrigation_interval, \ +# pcr.ifthenelse(self.totalPotET > 0.0, \ +# pcr.roundup((self.irrGrossDemand + pcr.max(self.readAvlWater, self.soilWaterStorage))/ self.totalPotET), 1.0))) +# # - irrigation demand - limited by potential evaporation for the next coming days +# self.irrGrossDemand = pcr.min(pcr.max(0.0,\ +# self.totalPotET * irrigation_interval - pcr.max(self.readAvlWater, self.soilWaterStorage)),\ +# self.irrGrossDemand) +# +# # assume that smart farmers do not irrigate higher than infiltration capacities +# if self.numberOfLayers == 2: self.irrGrossDemand = pcr.min(self.irrGrossDemand, self.parameters.kSatUpp) +# if self.numberOfLayers == 3: self.irrGrossDemand = pcr.min(self.irrGrossDemand, self.parameters.kSatUpp000005) +# +# # irrigation efficiency, minimum demand for start irrigating and maximum value to cap excessive demand +# if self.includeIrrigation: +# +# # irrigation efficiency # TODO: Improve the concept of irrigation efficiency +# self.irrigationEfficiencyUsed = pcr.min(1.0, pcr.max(0.10, self.irrigationEfficiency)) +# # demand, including its inefficiency +# self.irrGrossDemand = pcr.cover(self.irrGrossDemand / pcr.min(1.0, self.irrigationEfficiencyUsed), 0.0) +# +# # the following irrigation demand is not limited to available water +# self.irrGrossDemand = pcr.ifthen(self.landmask, self.irrGrossDemand) +# +# # reduce irrGrossDemand by netLqWaterToSoil +# self.irrGrossDemand = pcr.max(0.0, self.irrGrossDemand - self.netLqWaterToSoil) +# +# # minimum demand for start irrigating +# minimum_demand = 0.005 # unit: m/day # TODO: set the minimum demand in the ini/configuration file. +# if self.name == 'irrPaddy' or\ +# self.name == 'irr_paddy': minimum_demand = pcr.min(self.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. +# self.irrGrossDemand = pcr.ifthenelse(self.irrGrossDemand > minimum_demand, \ +# self.irrGrossDemand , 0.0) +# +# maximum_demand = 0.025 # unit: m/day # TODO: set the maximum demand in the ini/configuration file. +# if self.name == 'irrPaddy' or\ +# self.name == 'irr_paddy': maximum_demand = pcr.min(self.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. +# self.irrGrossDemand = pcr.min(maximum_demand, self.irrGrossDemand) +# +# # ignore small irrigation demand (less than 1 mm) +# self.irrGrossDemand = pcr.rounddown( self.irrGrossDemand *1000.)/1000. +# +# # irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? +# self.irrGrossDemand = pcr.ifthenelse(self.fracVegCover > 0.0, self.irrGrossDemand, 0.0) +# +# # total irrigation gross demand (m) per cover types (not limited by available water) +# self.totalPotentialMaximumIrrGrossDemandPaddy = 0.0 +# self.totalPotentialMaximumIrrGrossDemandNonPaddy = 0.0 +# if self.name == 'irrPaddy' or self.name == 'irr_paddy': self.totalPotentialMaximumIrrGrossDemandPaddy = self.irrGrossDemand +# if self.name == 'irrNonPaddy' or self.name == 'irr_non_paddy' or self.name == 'irr_non_paddy_crops': self.totalPotentialMaximumIrrGrossDemandNonPaddy = self.irrGrossDemand +# +# # .................................................................. + + def old_style_land_surface_reporting(self,currTimeStep): + + if self.report == True: + timeStamp = datetime.datetime(currTimeStep.year,\ + currTimeStep.month,\ + currTimeStep.day,\ + 0) + # writing daily output to netcdf files + timestepPCR = currTimeStep.timeStepPCR + if self.outDailyTotNC[0] != "None": + for var in self.outDailyTotNC: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_dailyTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,timestepPCR-1) + + # writing monthly output to netcdf files + # -cummulative + if self.outMonthTotNC[0] != "None": + for var in self.outMonthTotNC: + + # introduce variables at the beginning of simulation or + # reset variables at the beginning of the month + if currTimeStep.timeStepPCR == 1 or \ + currTimeStep.day == 1:\ + vars(self)[var+'MonthTot'] = pcr.scalar(0.0) + + # accumulating + vars(self)[var+'MonthTot'] += vars(self)[var] + + # reporting at the end of the month: + if currTimeStep.endMonth == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'MonthTot'),\ + vos.MV),timeStamp,currTimeStep.monthIdx-1) + # -average + if self.outMonthAvgNC[0] != "None": + for var in self.outMonthAvgNC: + # only if a accumulator variable has not been defined: + if var not in self.outMonthTotNC: + + # introduce accumulator at the beginning of simulation or + # reset accumulator at the beginning of the month + if currTimeStep.timeStepPCR == 1 or \ + currTimeStep.day == 1:\ + vars(self)[var+'MonthTot'] = pcr.scalar(0.0) + # accumulating + vars(self)[var+'MonthTot'] += vars(self)[var] + + # calculating average & reporting at the end of the month: + if currTimeStep.endMonth == True: + vars(self)[var+'MonthAvg'] = vars(self)[var+'MonthTot']/\ + currTimeStep.day + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthAvg.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'MonthAvg'),\ + vos.MV),timeStamp,currTimeStep.monthIdx-1) + # + # -last day of the month + if self.outMonthEndNC[0] != "None": + for var in self.outMonthEndNC: + # reporting at the end of the month: + if currTimeStep.endMonth == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_monthEnd.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,currTimeStep.monthIdx-1) + + # writing yearly output to netcdf files + # -cummulative + if self.outAnnuaTotNC[0] != "None": + for var in self.outAnnuaTotNC: + + # introduce variables at the beginning of simulation or + # reset variables at the beginning of the month + if currTimeStep.timeStepPCR == 1 or \ + currTimeStep.doy == 1:\ + vars(self)[var+'AnnuaTot'] = pcr.scalar(0.0) + + # accumulating + vars(self)[var+'AnnuaTot'] += vars(self)[var] + + # reporting at the end of the year: + if currTimeStep.endYear == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaTot.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'AnnuaTot'),\ + vos.MV),timeStamp,currTimeStep.annuaIdx-1) + # -average + if self.outAnnuaAvgNC[0] != "None": + for var in self.outAnnuaAvgNC: + # only if a accumulator variable has not been defined: + if var not in self.outAnnuaTotNC: + # introduce accumulator at the beginning of simulation or + # reset accumulator at the beginning of the year + if currTimeStep.timeStepPCR == 1 or \ + currTimeStep.doy == 1:\ + vars(self)[var+'AnnuaTot'] = pcr.scalar(0.0) + # accumulating + vars(self)[var+'AnnuaTot'] += vars(self)[var] + # + # calculating average & reporting at the end of the year: + if currTimeStep.endYear == True: + vars(self)[var+'AnnuaAvg'] = vars(self)[var+'AnnuaTot']/\ + currTimeStep.doy + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaAvg.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var+'AnnuaAvg'),\ + vos.MV),timeStamp,currTimeStep.annuaIdx-1) + # + # -last day of the year + if self.outAnnuaEndNC[0] != "None": + for var in self.outAnnuaEndNC: + # reporting at the end of the year: + if currTimeStep.endYear == True: + self.netcdfObj.data2NetCDF(str(self.outNCDir)+"/"+ \ + str(var)+"_annuaEnd.nc",\ + var,\ + pcr.pcr2numpy(self.__getattribute__(var),vos.MV),\ + timeStamp,currTimeStep.annuaIdx-1) diff --git a/model/waterUseLandCover.py b/model/waterUseLandCover.py new file mode 100644 index 000000000..71dfd6072 --- /dev/null +++ b/model/waterUseLandCover.py @@ -0,0 +1,980 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pcraster as pcr +import virtualOS as vos +from ncConverter import * + +import logging +logger = logging.getLogger(__name__) + +#................................................................................................................................................. + +def waterAbstractionAndAllocation(water_demand_volume, + available_water_volume, + allocation_zones, + zone_area = None, + high_volume_treshold = None, + debug_water_balance = True, + extra_info_for_water_balance_reporting = "", + landmask = None, + ignore_small_values = False, + prioritizing_local_source = True): + + logger.debug("Allocation of abstraction.") + + if landmask is not None: + water_demand_volume = pcr.ifthen(landmask, pcr.cover(water_demand_volume, 0.0)) + available_water_volume = pcr.ifthen(landmask, pcr.cover(available_water_volume, 0.0)) + allocation_zones = pcr.ifthen(landmask, allocation_zones) + + # satistify demand with local sources: + localAllocation = pcr.scalar(0.0) + localAbstraction = pcr.scalar(0.0) + cellVolDemand = pcr.max(0.0, water_demand_volume) + cellAvlWater = pcr.max(0.0, available_water_volume) + + if prioritizing_local_source: + logger.debug("Allocation of abstraction - first, satisfy demand with local source.") + + # demand volume in each cell (unit: m3) + if landmask is not None: + cellVolDemand = pcr.ifthen(landmask, pcr.cover(cellVolDemand, 0.0)) + + # total available water volume in each cell + if landmask is not None: + cellAvlWater = pcr.ifthen(landmask, pcr.cover(cellAvlWater, 0.0)) + + # first, satisfy demand with local source + localAllocation = pcr.max(0.0, pcr.min(cellVolDemand, cellAvlWater)) + localAbstraction = localAllocation * 1.0 + + logger.debug("Allocation of abstraction - satisfy demand with neighbour sources.") + + # the remaining demand and available water + cellVolDemand = pcr.max(0.0, cellVolDemand - localAllocation ) + cellAvlWater = pcr.max(0.0, cellAvlWater - localAbstraction) + + # ignoring small values of water availability + if ignore_small_values: available_water_volume = pcr.max(0.0, pcr.rounddown(available_water_volume)) + + # demand volume in each cell (unit: m3) + cellVolDemand = pcr.max(0.0, cellVolDemand) + if landmask is not None: + cellVolDemand = pcr.ifthen(landmask, pcr.cover(cellVolDemand, 0.0)) + + # total demand volume in each zone/segment (unit: m3) + zoneVolDemand = pcr.areatotal(cellVolDemand, allocation_zones) + + # avoid very high values of available water + cellAvlWater = pcr.min(cellAvlWater, zoneVolDemand) + + # total available water volume in each cell + cellAvlWater = pcr.max(0.0, cellAvlWater) + if landmask is not None: + cellAvlWater = pcr.ifthen(landmask, pcr.cover(cellAvlWater, 0.0)) + + # total available water volume in each zone/segment (unit: m3) + zoneAvlWater = pcr.areatotal(cellAvlWater, allocation_zones) + + # total actual water abstraction volume in each zone/segment (unit: m3) + # - limited to available water + zoneAbstraction = pcr.min(zoneAvlWater, zoneVolDemand) + + # actual water abstraction volume in each cell (unit: m3) + cellAbstraction = vos.getValDivZero(\ + cellAvlWater, zoneAvlWater, vos.smallNumber) * zoneAbstraction + cellAbstraction = pcr.min(cellAbstraction, cellAvlWater) + + # to minimize numerical errors + if high_volume_treshold is not None: + # mask: 0 for small volumes ; 1 for large volumes (e.g. lakes and reservoirs) + mask = pcr.cover(\ + pcr.ifthen(cellAbstraction > high_volume_treshold, pcr.boolean(1)), pcr.boolean(0)) + zoneAbstraction = pcr.areatotal( + pcr.ifthenelse(mask, 0.0, cellAbstraction), allocation_zones) + zoneAbstraction += pcr.areatotal( + pcr.ifthenelse(mask, cellAbstraction, 0.0), allocation_zones) + + # allocation water to meet water demand (unit: m3) + cellAllocation = vos.getValDivZero(\ + cellVolDemand, zoneVolDemand, vos.smallNumber) * zoneAbstraction + cellAllocation = pcr.min(cellAllocation, cellVolDemand) + + # adding local abstraction and local allocation + cellAbstraction = cellAbstraction + localAbstraction + cellAllocation = cellAllocation + localAllocation + + if debug_water_balance and zone_area is not None: + + vos.waterBalanceCheck([pcr.cover(pcr.areatotal(cellAbstraction, allocation_zones)/zone_area, 0.0)],\ + [pcr.cover(pcr.areatotal(cellAllocation , allocation_zones)/zone_area, 0.0)],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)],\ + 'abstraction - allocation per zone/segment (PS: Error here may be caused by rounding error.)' ,\ + True,\ + extra_info_for_water_balance_reporting,threshold=1e-4) + + return cellAbstraction, cellAllocation + + +#..................................................................................................................................................... + + +class WaterUse(object): + + + #................................................................................................................................................. + + + def __init__(self, lc, nonIrrGrossDemandDict, + swAbstractionFractionDict, + groundwater, + routing, + allocSegments, + currTimeStep, + desalinationWaterUse, + groundwater_pumping_region_ids, + regionalAnnualGroundwaterAbstractionLimit): + + object.__init__(self) + + # self.nonIrrGrossDemandDict = nonIrrGrossDemandDict + # self.swAbstractionFractionDict = swAbstractionFractionDict + # self.groundwater = groundwater + # self.routing = routing + # self.allocSegments = allocSegments + # self.currTimeStep = currTimeStep + # self.desalinationWaterUse = desalinationWaterUse + # self.groundwater_pumping_region_ids = groundwater_pumping_region_ids + # self.regionalAnnualGroundwaterAbstractionLimit = regionalAnnualGroundwaterAbstractionLimit + + if lc.naturalisedConditions == True: + self.naturalisedConditions() + else: + self.calculateWaterDemand(lc, + nonIrrGrossDemandDict, + swAbstractionFractionDict, + groundwater, + routing, + allocSegments, + currTimeStep, + desalinationWaterUse, + groundwater_pumping_region_ids, + regionalAnnualGroundwaterAbstractionLimit) + + + #................................................................................................................................................. + + + def naturalisedConditions(self): + # naturalised conditions: no water abstracctions from the water system + self.totalPotentialGrossDemand = pcr.scalar(0.0) + self.nonIrrGrossDemand = pcr.scalar(0.0) + self.irrGrossDemand = pcr.scalar(0.0) + self.irrGrossDemandPaddy = pcr.scalar(0.0) + self.irrGrossDemandNonPaddy = pcr.scalar(0.0) + self.desalinationAbstraction = pcr.scalar(0.0) + self.desalinationAllocation = pcr.scalar(0.0) + self.actSurfaceWaterAbstract = pcr.scalar(0.0) + self.allocSurfaceWaterAbstract = pcr.scalar(0.0) + self.nonFossilGroundwaterAbs = pcr.scalar(0.0) + self.allocNonFossilGroundwater = pcr.scalar(0.0) + self.fossilGroundwaterAbstr = pcr.scalar(0.0) + self.fossilGroundwaterAlloc = pcr.scalar(0.0) + self.totalGroundwaterAbstraction = pcr.scalar(0.0) + self.totalGroundwaterAllocation = pcr.scalar(0.0) + self.reducedCapRise = pcr.scalar(0.0) + + self.totalPotentialMaximumGrossDemand = pcr.scalar(0.0) + self.totalPotentialMaximumNonIrrGrossDemand = pcr.scalar(0.0) + self.totalPotentialMaximumIrrGrossDemand = pcr.scalar(0.0) + self.totalPotentialMaximumIrrGrossDemandPaddy = pcr.scalar(0.0) + self.totalPotentialMaximumIrrGrossDemandNonPaddy = pcr.scalar(0.0) + + self.domesticWaterWithdrawal = pcr.scalar(0.0) + self.industryWaterWithdrawal = pcr.scalar(0.0) + self.livestockWaterWithdrawal = pcr.scalar(0.0) + + self.nonIrrReturnFlow = pcr.scalar(0.0) + self.irrigationEfficiencyUsed = pcr.scalar(1.0) + + + #................................................................................................................................................. + + + def calculateIrrigationDemand(self, lc): + ''' + irrigation water demand (unit: m/day) for paddy and non-paddy + ''' + self.irrGrossDemand = pcr.scalar(0.) + if (lc.name == 'irrPaddy' or lc.name == 'irr_paddy') and lc.includeIrrigation: + self.irrGrossDemand = \ + pcr.ifthenelse(lc.cropKC > 0.75, + pcr.max(0.0, + lc.minTopWaterLayer - (lc.topWaterLayer )), + 0.0) + # a function of: - cropKC (evaporation and transpiration), + # - topWaterLayer (water available in the irrigation field) + + if (lc.name == 'irrNonPaddy' or lc.name == 'irr_non_paddy' or lc.name == "irr_non_paddy_crops") and lc.includeIrrigation: + adjDeplFactor = \ + pcr.max(0.1, + pcr.min(0.8, + (lc.cropDeplFactor + 0.04*(5.-lc.totalPotET*1000.)))) + # original formula based on Allen et al. (1998) + # see: http://www.fao.org/docrep/x0490e/x0490e0e.htm# + + # irrigation demand (to fill the entire totAvlWater, maintaining the field capacity, + # but with the correction of totAvlWater based on the rooting depth) + # - as the proxy of rooting depth, we use crop coefficient + self.irrigation_factor = pcr.ifthenelse(lc.cropKC > 0.0,\ + pcr.min(1.0, lc.cropKC / 1.0), 0.0) + self.irrGrossDemand = \ + pcr.ifthenelse( lc.cropKC > 0.20, \ + pcr.ifthenelse( lc.readAvlWater < \ + adjDeplFactor*self.irrigation_factor*lc.totAvlWater, \ + pcr.max(0.0, lc.totAvlWater*self.irrigation_factor-lc.readAvlWater),0.),0.) + + # irrigation demand is implemented only if there is deficit in transpiration and/or evaporation + deficit_factor = 1.00 + evaporationDeficit = pcr.max(0.0, (lc.potBareSoilEvap + lc.potTranspiration)*deficit_factor -\ + lc.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True)) + transpirationDeficit = pcr.max(0.0, + lc.potTranspiration*deficit_factor -\ + lc.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True, returnTotalTranspirationOnly = True)) + deficit = pcr.max(evaporationDeficit, transpirationDeficit) + + # treshold to initiate irrigation + deficit_treshold = 0.20 * lc.totalPotET + need_irrigation = pcr.ifthenelse(deficit > deficit_treshold, pcr.boolean(1),\ + pcr.ifthenelse(lc.soilWaterStorage == 0.000, pcr.boolean(1), pcr.boolean(0))) + need_irrigation = pcr.cover(need_irrigation, pcr.boolean(0.0)) + + self.irrGrossDemand = pcr.ifthenelse(need_irrigation, self.irrGrossDemand, 0.0) + + # demand is limited by potential evaporation for the next coming days + # - objective: to avoid too high and unrealistic demand + max_irrigation_interval = 15.0 + min_irrigation_interval = 7.0 + irrigation_interval = pcr.min(max_irrigation_interval, \ + pcr.max(min_irrigation_interval, \ + pcr.ifthenelse(lc.totalPotET > 0.0, \ + pcr.roundup((self.irrGrossDemand + pcr.max(lc.readAvlWater, lc.soilWaterStorage))/ lc.totalPotET), 1.0))) + # - irrigation demand - limited by potential evaporation for the next coming days + self.irrGrossDemand = pcr.min(pcr.max(0.0,\ + lc.totalPotET * irrigation_interval - pcr.max(lc.readAvlWater, lc.soilWaterStorage)),\ + self.irrGrossDemand) + + # assume that smart farmers do not irrigate higher than infiltration capacities + if lc.numberOfLayers == 2: self.irrGrossDemand = pcr.min(self.irrGrossDemand, lc.parameters.kSatUpp) + if lc.numberOfLayers == 3: self.irrGrossDemand = pcr.min(self.irrGrossDemand, lc.parameters.kSatUpp000005) + + # irrigation efficiency, minimum demand for start irrigating and maximum value to cap excessive demand + if lc.includeIrrigation: + + # irrigation efficiency # TODO: Improve the concept of irrigation efficiency + self.irrigationEfficiencyUsed = pcr.min(1.0, pcr.max(0.10, lc.irrigationEfficiency)) + # demand, including its inefficiency + self.irrGrossDemand = pcr.cover(self.irrGrossDemand / pcr.min(1.0, self.irrigationEfficiencyUsed), 0.0) + + # the following irrigation demand is not limited to available water + self.irrGrossDemand = pcr.ifthen(lc.landmask, self.irrGrossDemand) + + # reduce irrGrossDemand by netLqWaterToSoil + self.irrGrossDemand = pcr.max(0.0, self.irrGrossDemand - lc.netLqWaterToSoil) + + # minimum demand for start irrigating + minimum_demand = 0.005 # unit: m/day # TODO: set the minimum demand in the ini/configuration file. + if lc.name == 'irrPaddy' or lc.name == 'irr_paddy': + minimum_demand = pcr.min(lc.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. + self.irrGrossDemand = pcr.ifthenelse(self.irrGrossDemand > minimum_demand, \ + self.irrGrossDemand , 0.0) + + maximum_demand = 0.025 # unit: m/day # TODO: set the maximum demand in the ini/configuration file. + if lc.name == 'irrPaddy' or lc.name == 'irr_paddy': + maximum_demand = pcr.min(lc.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. + self.irrGrossDemand = pcr.min(maximum_demand, self.irrGrossDemand) + + # ignore small irrigation demand (less than 1 mm) + self.irrGrossDemand = pcr.rounddown( self.irrGrossDemand *1000.)/1000. + + # irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? + self.irrGrossDemand = pcr.ifthenelse(lc.fracVegCover > 0.0, self.irrGrossDemand, 0.0) + + # total irrigation gross demand (m) per cover types (not limited by available water) + self.totalPotentialMaximumIrrGrossDemandPaddy = 0.0 + self.totalPotentialMaximumIrrGrossDemandNonPaddy = 0.0 + if lc.name == 'irrPaddy' or lc.name == 'irr_paddy': + self.totalPotentialMaximumIrrGrossDemandPaddy = self.irrGrossDemand + if lc.name == 'irrNonPaddy' or lc.name == 'irr_non_paddy' or lc.name == 'irr_non_paddy_crops': + self.totalPotentialMaximumIrrGrossDemandNonPaddy = self.irrGrossDemand + + + #................................................................................................................................................. + + + def calculateWaterDemand(self, lc, + nonIrrGrossDemandDict, + swAbstractionFractionDict, + groundwater, + routing, + allocSegments, + currTimeStep, + desalinationWaterUse, + groundwater_pumping_region_ids, + regionalAnnualGroundwaterAbstractionLimit): + + # irrigation demand + self.calculateIrrigationDemand(lc) + + # non irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? + nonIrrGrossDemandDict['potential_demand']['domestic'] = pcr.ifthenelse(lc.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] , 0.0) + nonIrrGrossDemandDict['potential_demand']['industry'] = pcr.ifthenelse(lc.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['industry'] , 0.0) + nonIrrGrossDemandDict['potential_demand']['livestock'] = pcr.ifthenelse(lc.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['livestock'], 0.0) + + # non irrigation water demand, including the livestock (not limited by available water) + self.nonIrrGrossDemand = nonIrrGrossDemandDict['potential_demand']['domestic'] +\ + nonIrrGrossDemandDict['potential_demand']['industry'] +\ + nonIrrGrossDemandDict['potential_demand']['livestock'] + + # total irrigation and livestock demand (not limited by available water) + totalIrrigationLivestockDemand = self.irrGrossDemand + nonIrrGrossDemandDict['potential_demand']['livestock'] + + # totalGrossDemand (m): irrigation and non irrigation (not limited by available water) - these values will not be reduced + self.totalPotentialMaximumGrossDemand = self.irrGrossDemand + self.nonIrrGrossDemand + # - irrigation (excluding livestock) + self.totalPotentialMaximumIrrGrossDemand = self.irrGrossDemand + # - non irrigation (including livestock) + self.totalPotentialMaximumNonIrrGrossDemand = self.nonIrrGrossDemand + + # the following value will be reduced by available/accesible water + self.totalPotentialGrossDemand = self.totalPotentialMaximumGrossDemand + + #................................................................................................................... + # Abstraction and Allocation of DESALINATED WATER + # - desalination water to satisfy water demand + if lc.usingAllocSegments: # using zone/segments at which networks are defined (as defined in the landSurface options) + logger.debug("Allocation of supply from desalination water.") + + volDesalinationAbstraction, volDesalinationAllocation = \ + waterAbstractionAndAllocation(\ + water_demand_volume = self.totalPotentialGrossDemand*routing.cellArea,\ + available_water_volume = pcr.max(0.00, desalinationWaterUse*routing.cellArea),\ + allocation_zones = allocSegments,\ + zone_area = lc.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = lc.landmask, + ignore_small_values = False, + prioritizing_local_source = lc.prioritizeLocalSourceToMeetWaterDemand) + + self.desalinationAbstraction = volDesalinationAbstraction / routing.cellArea + self.desalinationAllocation = volDesalinationAllocation / routing.cellArea + + else: + logger.debug("Supply from desalination water is only for satisfying local demand (no network).") + self.desalinationAbstraction = pcr.min(desalinationWaterUse, self.totalPotentialGrossDemand) + self.desalinationAllocation = self.desalinationAbstraction + + self.desalinationAbstraction = pcr.ifthen(lc.landmask, self.desalinationAbstraction) + self.desalinationAllocation = pcr.ifthen(lc.landmask, self.desalinationAllocation) + + # water demand that have been satisfied (unit: m/day) - after desalination + # - for irrigation (excluding livestock) + satisfiedIrrigationDemand = vos.getValDivZero(self.irrGrossDemand, self.totalPotentialGrossDemand) * self.desalinationAllocation + # - for domestic, industry and livestock + satisfiedNonIrrDemand = pcr.max(0.00, self.desalinationAllocation - satisfiedIrrigationDemand) + # - for domestic + satisfiedDomesticDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['domestic'], + self.totalPotentialMaximumNonIrrGrossDemand) + # - for industry + satisfiedIndustryDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['industry'], + self.totalPotentialMaximumNonIrrGrossDemand) + # - for livestock + satisfiedLivestockDemand = pcr.max(0.0, satisfiedNonIrrDemand - satisfiedDomesticDemand - satisfiedIndustryDemand) + + # total remaining gross demand (m/day) after desalination + ################################################################################################################################ + self.totalGrossDemandAfterDesalination = pcr.max(0.0, self.totalPotentialGrossDemand - self.desalinationAllocation) + # the remaining water demand per sector + # - for domestic + remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # - for industry + remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # - for livestock + remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - total for livestock and irrigation + remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # - total for industrial and domestic (excluding livestock) + remainingIndustrialDomestic = pcr.max(0.0, self.totalGrossDemandAfterDesalination - remainingIrrigationLivestock) + + # Abstraction and Allocation of SURFACE WATER + ############################################################################################################################## + # calculate the estimate of surface water demand (considering by swAbstractionFractionDict) + # - for industrial and domestic + swAbstractionFraction_industrial_domestic = pcr.min(swAbstractionFractionDict['max_for_non_irrigation'],\ + swAbstractionFractionDict['estimate']) + if swAbstractionFractionDict['non_irrigation'] is not None: + swAbstractionFraction_industrial_domestic = swAbstractionFractionDict['non_irrigation'] + + surface_water_demand_estimate = swAbstractionFraction_industrial_domestic * remainingIndustrialDomestic + # - for irrigation and livestock + surface_water_irrigation_demand_estimate = swAbstractionFractionDict['irrigation'] * remainingIrrigationLivestock + # - surface water source as priority if groundwater irrigation fraction is relatively low + surface_water_irrigation_demand_estimate = \ + pcr.ifthenelse(swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_maximize_irrigation_surface_water'],\ + remainingIrrigationLivestock, surface_water_irrigation_demand_estimate) + # - update estimate of surface water demand withdrawal (unit: m/day) + surface_water_demand_estimate += surface_water_irrigation_demand_estimate + # - prioritize surface water use in non productive aquifers that have limited groundwater supply + surface_water_demand_estimate = pcr.ifthenelse(groundwater.productive_aquifer, surface_water_demand_estimate,\ + pcr.max(0.0, remainingIrrigationLivestock - \ + pcr.min(groundwater.avgAllocationShort, groundwater.avgAllocation))) + # - maximize/optimize surface water use in areas with the overestimation of groundwater supply + surface_water_demand_estimate += pcr.max(0.0, pcr.max(groundwater.avgAllocationShort, groundwater.avgAllocation) -\ + (1.0 - swAbstractionFractionDict['irrigation']) * totalIrrigationLivestockDemand -\ + (1.0 - swAbstractionFraction_industrial_domestic) * (self.totalPotentialMaximumGrossDemand - totalIrrigationLivestockDemand)) + # + # total demand (unit: m/day) that should be allocated from surface water + # (corrected/limited by swAbstractionFractionDict and limited by the remaining demand) + surface_water_demand_estimate = pcr.min(self.totalGrossDemandAfterDesalination, surface_water_demand_estimate) + correctedRemainingIrrigationLivestock = pcr.min(surface_water_demand_estimate, remainingIrrigationLivestock) + correctedRemainingIndustrialDomestic = pcr.min(remainingIndustrialDomestic,\ + pcr.max(0.0, surface_water_demand_estimate - remainingIrrigationLivestock)) + correctedSurfaceWaterDemandEstimate = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic + surface_water_demand = correctedSurfaceWaterDemandEstimate + # + # if surface water abstraction as the first priority + if lc.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination + # + if lc.usingAllocSegments: # using zone/segment at which supply network is defined + # + logger.debug("Allocation of surface water abstraction.") + # + volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = surface_water_demand*routing.cellArea,\ + available_water_volume = pcr.max(0.00, routing.readAvlChannelStorage),\ + allocation_zones = allocSegments,\ + zone_area = lc.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = lc.landmask, + ignore_small_values = False, + prioritizing_local_source = lc.prioritizeLocalSourceToMeetWaterDemand) + + self.actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea + self.allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea + # + else: + logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") + self.actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ + surface_water_demand) # unit: m + self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m + # + self.actSurfaceWaterAbstract = pcr.ifthen(lc.landmask, self.actSurfaceWaterAbstract) + self.allocSurfaceWaterAbstract = pcr.ifthen(lc.landmask, self.allocSurfaceWaterAbstract) + ################################################################################################################################ + # - end of Abstraction and Allocation of SURFACE WATER + + + # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply + ################################################################################################################################ + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromSurfaceWater = self.allocSurfaceWaterAbstract * \ + vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedSurfaceWaterDemandEstimate) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromSurfaceWater = satisfiedIrrigationLivestockDemandFromSurfaceWater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract - satisfiedIrrigationDemandFromSurfaceWater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromSurfaceWater - \ + satisfiedIrrigationDemandFromSurfaceWater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract -\ + satisfiedIrrigationLivestockDemandFromSurfaceWater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + + ###################################################################################################################### + # water demand (unit: m) that must be satisfied by groundwater abstraction (not limited to available water) + self.potGroundwaterAbstract = pcr.max(0.0, self.totalGrossDemandAfterDesalination - self.allocSurfaceWaterAbstract) + ###################################################################################################################### + # water demand per sector + # - for domestic + remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # - for industry + remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # - for livestock + remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - total for livestock and irrigation + remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # - total for industrial and domestic (excluding livestock) + remainingIndustrialDomestic = remainingIndustry + remainingDomestic + + + # Abstraction and Allocation of GROUNDWATER (fossil and non fossil) + ######################################################################################################################### + # estimating groundwater water demand: + # - demand for industrial and domestic sectors + # (all remaining demand for these sectors should be satisfied) + groundwater_demand_estimate = remainingIndustrialDomestic + # - demand for irrigation and livestock sectors + # (only part of them will be satisfied, as they may be too high due to the uncertainty in the irrigation scheme) + irrigationLivestockGroundwaterDemand = pcr.min(remainingIrrigationLivestock, \ + pcr.max(0.0, \ + (1.0 - swAbstractionFractionDict['irrigation'])*totalIrrigationLivestockDemand)) + groundwater_demand_estimate += irrigationLivestockGroundwaterDemand + + ##################################################################################################### + # water demand that must be satisfied by groundwater abstraction (not limited to available water) + self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, groundwater_demand_estimate) + ##################################################################################################### + + # constraining groundwater abstraction with the regional annual pumping capacity + if groundwater.limitRegionalAnnualGroundwaterAbstraction: + + logger.debug('Total groundwater abstraction is limited by regional annual pumping capacity.') + + # estimate of total groundwater abstraction (m3) from the last 365 days: + tolerating_days = 0. + annualGroundwaterAbstraction = groundwater.avgAbstraction * routing.cellArea *\ + pcr.min(pcr.max(0.0, 365.0 - tolerating_days), routing.timestepsToAvgDischarge) + # total groundwater abstraction (m3) from the last 365 days at the regional scale + regionalAnnualGroundwaterAbstraction = pcr.areatotal(pcr.cover(annualGroundwaterAbstraction, 0.0), groundwater_pumping_region_ids) + + #~ # reduction factor to reduce groundwater abstraction/demand + #~ reductionFactorForPotGroundwaterAbstract = pcr.cover(\ + #~ pcr.ifthenelse(regionalAnnualGroundwaterAbstractionLimit > 0.0, + #~ pcr.max(0.000, regionalAnnualGroundwaterAbstractionLimit -\ + #~ regionalAnnualGroundwaterAbstraction) / + #~ regionalAnnualGroundwaterAbstractionLimit , 0.0), 0.0) + + #~ # reduced potential groundwater abstraction (after pumping capacity) + #~ self.potGroundwaterAbstract = pcr.min(1.00, reductionFactorForPotGroundwaterAbstract) * self.potGroundwaterAbstract + + #~ # alternative: reduced potential groundwater abstraction (after pumping capacity) and considering the average recharge (baseflow) + #~ potGroundwaterAbstract = pcr.min(1.00, reductionFactorForPotGroundwaterAbstract) * self.potGroundwaterAbstract + #~ self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, + #~ potGroundwaterAbstract + pcr.max(0.0, routing.avgBaseflow / routing.cellArea)) + + ################## NEW METHOD ################################################################################################################# + # the remaining pumping capacity (unit: m3) at the regional scale + remainingRegionalAnnualGroundwaterAbstractionLimit = pcr.max(0.0, regionalAnnualGroundwaterAbstractionLimit - \ + regionalAnnualGroundwaterAbstraction) + # considering safety factor (residence time in day-1) + remainingRegionalAnnualGroundwaterAbstractionLimit *= 0.33 + + # the remaining pumping capacity (unit: m3) limited by self.potGroundwaterAbstract (at the regional scale) + remainingRegionalAnnualGroundwaterAbstractionLimit = pcr.min(remainingRegionalAnnualGroundwaterAbstractionLimit,\ + pcr.areatotal(self.potGroundwaterAbstract * routing.cellArea, groundwater_pumping_region_ids)) + + # the remaining pumping capacity (unit: m3) at the pixel scale - downscaled using self.potGroundwaterAbstract + remainingPixelAnnualGroundwaterAbstractionLimit = remainingRegionalAnnualGroundwaterAbstractionLimit * \ + vos.getValDivZero(self.potGroundwaterAbstract * routing.cellArea, pcr.areatotal(self.potGroundwaterAbstract * routing.cellArea, groundwater_pumping_region_ids)) + + # reduced (after pumping capacity) potential groundwater abstraction/demand (unit: m) and considering the average recharge (baseflow) + self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, \ + remainingPixelAnnualGroundwaterAbstractionLimit/routing.cellArea + pcr.max(0.0, routing.avgBaseflow / routing.cellArea)) + ################## end of NEW METHOD (but still under development) ########################################################################################################## + + #~ # Shall we will always try to fulfil the industrial and domestic demand? + #~ self.potGroundwaterAbstract = pcr.max(remainingIndustrialDomestic, self.potGroundwaterAbstract) + + else: + logger.debug('NO LIMIT for regional groundwater (annual) pumping. It may result too high groundwater abstraction.') + + # Abstraction and Allocation of NON-FOSSIL GROUNDWATER + # ############################################################################################################################# + # available storGroundwater (non fossil groundwater) that can be accessed (unit: m) + readAvlStorGroundwater = pcr.cover(pcr.max(0.00, groundwater.storGroundwater), 0.0) + # - considering maximum daily groundwater abstraction + readAvlStorGroundwater = pcr.min(readAvlStorGroundwater, groundwater.maximumDailyGroundwaterAbstraction) + # - ignore groundwater storage in non-productive aquifer + readAvlStorGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, readAvlStorGroundwater, 0.0) + + # for non-productive aquifer, reduce readAvlStorGroundwater to the current recharge/baseflow rate + readAvlStorGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, \ + readAvlStorGroundwater, pcr.min(readAvlStorGroundwater, pcr.max(routing.avgBaseflow, 0.0))) + + # avoid the condition that the entire groundwater volume abstracted instantaneously + readAvlStorGroundwater *= 0.75 + + if groundwater.usingAllocSegments: + + logger.debug('Allocation of non fossil groundwater abstraction.') + + # TODO: considering aquifer productivity while doing the allocation (e.g. using aquifer transmissivity/conductivity) + + # non fossil groundwater abstraction and allocation in volume (unit: m3) + volActGroundwaterAbstract, volAllocGroundwaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = self.potGroundwaterAbstract*routing.cellArea,\ + available_water_volume = pcr.max(0.00, readAvlStorGroundwater*routing.cellArea),\ + allocation_zones = groundwater.allocSegments,\ + zone_area = groundwater.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = lc.landmask, + ignore_small_values = False, + prioritizing_local_source = lc.prioritizeLocalSourceToMeetWaterDemand) + + # non fossil groundwater abstraction and allocation in meter + self.nonFossilGroundwaterAbs = volActGroundwaterAbstract / routing.cellArea + self.allocNonFossilGroundwater = volAllocGroundwaterAbstract/ routing.cellArea + + else: + + logger.debug('Non fossil groundwater abstraction is only for satisfying local demand.') + self.nonFossilGroundwaterAbs = pcr.min(readAvlStorGroundwater, self.potGroundwaterAbstract) + self.allocNonFossilGroundwater = self.nonFossilGroundwaterAbs + ################################################################################################################################ + # - end of Abstraction and Allocation of NON FOSSIL GROUNDWATER + + ################################################################################################################################ + # variable to reduce capillary rise in order to ensure there is always enough water to supply non fossil groundwater abstraction + self.reducedCapRise = self.nonFossilGroundwaterAbs + # TODO: Check do we need this for runs with MODFLOW ??? + ################################################################################################################################ + + + # water demand that have been satisfied (unit: m/day) - after desalination, surface water and non-fossil groundwater supply + ################################################################################################################################ + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromNonFossilGroundwater = self.allocNonFossilGroundwater * \ + vos.getValDivZero(irrigationLivestockGroundwaterDemand, groundwater_demand_estimate) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromNonFossilGroundwater = satisfiedIrrigationLivestockDemandFromNonFossilGroundwater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromNonFossilGroundwater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromNonFossilGroundwater = pcr.max(0.0, self.allocNonFossilGroundwater - satisfiedIrrigationLivestockDemandFromNonFossilGroundwater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromNonFossilGroundwater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromNonFossilGroundwater - \ + satisfiedIrrigationDemandFromNonFossilGroundwater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromNonFossilGroundwater = pcr.max(0.0, self.allocNonFossilGroundwater -\ + satisfiedIrrigationLivestockDemandFromNonFossilGroundwater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromNonFossilGroundwater * vos.getValDivZero(remainingDomestic, remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromNonFossilGroundwater * vos.getValDivZero(remainingIndustry, remainingIndustrialDomestic) + + ###################################################################################################################### + ###################################################################################################################### + # water demand that must be satisfied by fossil groundwater abstraction (unit: m, not limited to available water) + self.potFossilGroundwaterAbstract = pcr.max(0.0, self.potGroundwaterAbstract - \ + self.allocNonFossilGroundwater) + ###################################################################################################################### + ###################################################################################################################### + + # For a run using MODFLOW, the concept of fossil groundwater abstraction is abandoned (lc.limitAbstraction == True): + if groundwater.useMODFLOW or lc.limitAbstraction: + logger.debug('Fossil groundwater abstractions are NOT allowed') + self.fossilGroundwaterAbstr = pcr.scalar(0.0) + self.fossilGroundwaterAlloc = pcr.scalar(0.0) + + # Abstraction and Allocation of FOSSIL GROUNDWATER + # ##################################################################################################################################### + + if lc.limitAbstraction == False: # TODO: For runs without any water use, we can exclude this. + + logger.debug('Fossil groundwater abstractions are allowed.') + + # the remaining water demand (m/day) for all sectors - NOT limited to self.potFossilGroundwaterAbstract + ##################################################################################################################### + # - for domestic + remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # - for industry + remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # - for livestock + remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - total for livestock and irrigation + remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # - total for industrial and domestic (excluding livestock) + remainingIndustrialDomestic = remainingIndustry + remainingDomestic + # - remaining total demand + remainingTotalDemand = remainingIrrigationLivestock + remainingIndustrialDomestic + + # constraining fossil groundwater abstraction with regional pumping capacity + if groundwater.limitRegionalAnnualGroundwaterAbstraction and lc.limitAbstraction == False: + + logger.debug('Fossil groundwater abstraction is allowed, BUT limited by the regional annual pumping capacity.') + + # estimate of total groundwater abstraction (m3) from the last 365 days: + # - considering abstraction from non fossil groundwater + annualGroundwaterAbstraction += self.nonFossilGroundwaterAbs*routing.cellArea + # at the regional scale + regionalAnnualGroundwaterAbstraction = pcr.areatotal(pcr.cover(annualGroundwaterAbstraction, 0.0), groundwater_pumping_region_ids) + + # fossil groundwater demand/asbtraction reduced by pumping capacity (unit: m/day) + # - safety factor to avoid the remaining limit abstracted at once (due to overestimation of groundwater demand) + safety_factor_for_fossil_abstraction = 1.00 + self.potFossilGroundwaterAbstract *= pcr.min(1.00,\ + pcr.cover(\ + pcr.ifthenelse(regionalAnnualGroundwaterAbstractionLimit > 0.0, + pcr.max(0.000, regionalAnnualGroundwaterAbstractionLimit * safety_factor_for_fossil_abstraction-\ + regionalAnnualGroundwaterAbstraction) / + regionalAnnualGroundwaterAbstractionLimit , 0.0), 0.0)) + + #~ # Shall we will always try to fulfil the remaining industrial and domestic demand? + #~ self.potFossilGroundwaterAbstract = pcr.max(remainingIndustrialDomestic, self.potFossilGroundwaterAbstract) + + if lc.limitAbstraction == False: # TODO: For runs without any water use, we can exclude this. + + ############################################################################################################################### + # estimate the remaining total demand (unit: m/day) LIMITED to self.potFossilGroundwaterAbstract + ############################################################################################################################### + + correctedRemainingTotalDemand = pcr.min(self.potFossilGroundwaterAbstract, remainingTotalDemand) + + # the remaining industrial and domestic demand and livestock (unit: m/day) limited to self.potFossilGroundwaterAbstract + # - no correction, we will always try to fulfil these demands + correctedRemainingIndustrialDomesticLivestock = pcr.min(remainingIndustrialDomestic + remainingLivestock, correctedRemainingTotalDemand) + + # the remaining irrigation demand limited to self.potFossilGroundwaterAbstract + correctedRemainingIrrigation = pcr.min(remainingIrrigation, \ + pcr.max(0.0, correctedRemainingTotalDemand - correctedRemainingIndustrialDomesticLivestock)) + # - ignore small irrigation demand (less than 1 mm) + correctedRemainingIrrigation = pcr.rounddown(correctedRemainingIrrigation*1000.)/1000. + + # the (corrected) remaining total demand (limited to self.potFossilGroundwaterAbstract) + correctedRemainingTotalDemand = correctedRemainingIndustrialDomesticLivestock + correctedRemainingIrrigation + + # the (corrected) remaining industrial and domestic demand (excluding livestock) + correctedRemainingIndustrialDomestic = pcr.min(remainingIndustrialDomestic, correctedRemainingTotalDemand) + + # the remaining irrigation and livestock water demand limited to self.potFossilGroundwaterAbstract + correctedRemainingIrrigationLivestock = pcr.min(remainingIrrigationLivestock, \ + pcr.max(0.0, correctedRemainingTotalDemand - correctedRemainingIndustrialDomestic)) + + # the (corrected) remaining total demand (unit: m/day) limited to self.potFossilGroundwaterAbstract + correctedRemainingTotalDemand = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic + + # TODO: Do the water balance check: correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic <= self.potFossilGroundwaterAbstract + + # constrain the irrigation groundwater demand with groundwater source fraction + correctedRemainingIrrigationLivestock = pcr.min((1.0 - swAbstractionFractionDict['irrigation']) * remainingIrrigationLivestock,\ + correctedRemainingIrrigationLivestock) + correctedRemainingIrrigationLivestock = pcr.max(0.0,\ + pcr.min(correctedRemainingIrrigationLivestock,\ + pcr.max(0.0, totalIrrigationLivestockDemand) * (1.0 - swAbstractionFractionDict['irrigation']) - satisfiedIrrigationDemandFromNonFossilGroundwater)) + + # ignore fossil groundwater abstraction in irrigation areas dominated by swAbstractionFractionDict['irrigation'] + correctedRemainingIrrigationLivestock = pcr.ifthenelse(\ + swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_minimize_fossil_groundwater_irrigation'], 0.0,\ + correctedRemainingIrrigationLivestock) + + # reduce the fossil irrigation and livestock demands with enough supply of non fossil groundwater (in order to minimize unrealistic areas of fossil groundwater abstraction) + # - supply from the average recharge (baseflow) and non fossil groundwater allocation + nonFossilGroundwaterSupply = pcr.max(pcr.max(0.0, routing.avgBaseflow) / routing.cellArea, \ + groundwater.avgNonFossilAllocationShort, groundwater.avgNonFossilAllocation) + # - irrigation supply from the non fossil groundwater + nonFossilIrrigationGroundwaterSupply = nonFossilGroundwaterSupply * vos.getValDivZero(remainingIrrigationLivestock, remainingTotalDemand) + # - the corrected/reduced irrigation and livestock demand + correctedRemainingIrrigationLivestock = pcr.max(0.0, correctedRemainingIrrigationLivestock - nonFossilIrrigationGroundwaterSupply) + + # the corrected remaining total demand (unit: m/day) + correctedRemainingTotalDemand = correctedRemainingIndustrialDomestic + correctedRemainingIrrigationLivestock + + ############################################################################################################################### + + # water demand that must be satisfied by fossil groundwater abstraction + self.potFossilGroundwaterAbstract = pcr.min(self.potFossilGroundwaterAbstract, correctedRemainingTotalDemand) + + if groundwater.limitFossilGroundwaterAbstraction == False and lc.limitAbstraction == False: + + # Note: If limitFossilGroundwaterAbstraction == False, + # allocation of fossil groundwater abstraction is not needed. + msg = 'Fossil groundwater abstractions are without limit for satisfying local demand. ' + msg = 'Allocation for fossil groundwater abstraction is NOT needed/implemented. ' + msg += 'However, the fossil groundwater abstraction rate still consider the maximumDailyGroundwaterAbstraction.' + logger.debug(msg) + + # fossil groundwater abstraction (unit: m/day) + self.fossilGroundwaterAbstr = self.potFossilGroundwaterAbstract + self.fossilGroundwaterAbstr = \ + pcr.min(\ + pcr.max(0.0, groundwater.maximumDailyGroundwaterAbstraction - self.nonFossilGroundwaterAbs), self.fossilGroundwaterAbstr) + + # fossil groundwater allocation (unit: m/day) + self.fossilGroundwaterAlloc = self.fossilGroundwaterAbstr + + if groundwater.limitFossilGroundwaterAbstraction and lc.limitAbstraction == False: + + logger.debug('Fossil groundwater abstractions are allowed, but with limit.') + + # accesible fossil groundwater (unit: m/day) + readAvlFossilGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, groundwater.storGroundwaterFossil, 0.0) + # - residence time (day-1) or safety factor (to avoid 'unrealistic' zero fossil groundwater) + readAvlFossilGroundwater *= 0.10 + # - considering maximum daily groundwater abstraction + readAvlFossilGroundwater = pcr.min(readAvlFossilGroundwater, groundwater.maximumDailyFossilGroundwaterAbstraction, \ + pcr.max(0.0, groundwater.maximumDailyGroundwaterAbstraction - self.nonFossilGroundwaterAbs)) + readAvlFossilGroundwater = pcr.max(pcr.cover(readAvlFossilGroundwater, 0.0), 0.0) + + if groundwater.usingAllocSegments: + + logger.debug('Allocation of fossil groundwater abstraction.') + + # TODO: considering aquifer productivity while doing the allocation. + + # fossil groundwater abstraction and allocation in volume (unit: m3) + volActGroundwaterAbstract, volAllocGroundwaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = self.potFossilGroundwaterAbstract*routing.cellArea,\ + available_water_volume = pcr.max(0.00, readAvlFossilGroundwater*routing.cellArea),\ + allocation_zones = groundwater.allocSegments,\ + zone_area = groundwater.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = lc.landmask, + ignore_small_values = False, + prioritizing_local_source = lc.prioritizeLocalSourceToMeetWaterDemand) + + # fossil groundwater abstraction and allocation in meter + self.fossilGroundwaterAbstr = volActGroundwaterAbstract /routing.cellArea + self.fossilGroundwaterAlloc = volAllocGroundwaterAbstract/routing.cellArea + + else: + + logger.debug('Fossil groundwater abstraction is only for satisfying local demand. NO Allocation for fossil groundwater abstraction.') + + self.fossilGroundwaterAbstr = pcr.min(pcr.max(0.0, readAvlFossilGroundwater), self.potFossilGroundwaterAbstract) + self.fossilGroundwaterAlloc = self.fossilGroundwaterAbstr + + + # water demand that have been satisfied (m/day) - after desalination, surface water, non fossil groundwater & fossil groundwater + ################################################################################################################################ + + # from fossil groundwater, we should prioritize domestic and industrial water demand + prioritizeFossilGroundwaterForDomesticIndutrial = False # TODO: Define this in the configuration file. + + if prioritizeFossilGroundwaterForDomesticIndutrial: + + # - first priority: for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromFossilGroundwater = pcr.min(self.fossilGroundwaterAlloc, \ + remainingIndustrialDomestic) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + # - for irrigation and livestock demand + satisfiedIrrigationLivestockDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - \ + satisfiedIndustrialDomesticDemandFromFossilGroundwater) + # - for irrigation + satisfiedIrrigationDemand += satisfiedIrrigationLivestockDemandFromFossilGroundwater * vos.getValDivZero(remainingIrrigation, \ + remainingIrrigationLivestock) + # - for livestock + satisfiedLivestockDemand += satisfiedIrrigationLivestockDemandFromFossilGroundwater * vos.getValDivZero(remainingLivestock, \ + remainingIrrigationLivestock) + + else: + + # Distribute fossil water proportionaly based on the amount of each sector + + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromFossilGroundwater = self.fossilGroundwaterAlloc * \ + vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedRemainingTotalDemand) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromFossilGroundwater = satisfiedIrrigationLivestockDemandFromFossilGroundwater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromFossilGroundwater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - satisfiedIrrigationDemandFromFossilGroundwater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromFossilGroundwater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromFossilGroundwater - \ + satisfiedIrrigationDemandFromFossilGroundwater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - \ + satisfiedIrrigationLivestockDemandFromFossilGroundwater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + + # water demand limited to available/allocated water + self.totalPotentialGrossDemand = self.fossilGroundwaterAlloc +\ + self.allocNonFossilGroundwater +\ + self.allocSurfaceWaterAbstract +\ + self.desalinationAllocation + + # total groundwater abstraction and allocation (unit: m/day) + self.totalGroundwaterAllocation = self.allocNonFossilGroundwater + self.fossilGroundwaterAlloc + self.totalGroundwaterAbstraction = self.fossilGroundwaterAbstr + self.nonFossilGroundwaterAbs + + # irrigation water demand (excluding livestock) limited to available/allocated water (unit: m/day) + self.irrGrossDemand = satisfiedIrrigationDemand # not including livestock + + # irrigation gross demand (m) per cover type (limited by available water) + self.irrGrossDemandPaddy = 0.0 + self.irrGrossDemandNonPaddy = 0.0 + if lc.name == 'irrPaddy' or lc.name == "irr_paddy": self.irrGrossDemandPaddy = self.irrGrossDemand + if lc.name == 'irrNonPaddy' or lc.name == "irr_non_paddy" or lc.name == "irr_non_paddy_crops": self.irrGrossDemandNonPaddy = self.irrGrossDemand + + # non irrigation water demand (including livestock) limited to available/allocated water (unit: m/day) + self.nonIrrGrossDemand = pcr.max(0.0, \ + self.totalPotentialGrossDemand - self.irrGrossDemand) # livestock, domestic and industry + self.domesticWaterWithdrawal = satisfiedDomesticDemand + self.industryWaterWithdrawal = satisfiedIndustryDemand + self.livestockWaterWithdrawal = satisfiedLivestockDemand + + # return flow (unit: m/day) from non irrigation withdrawal (from domestic, industry and livestock) + self.nonIrrReturnFlow = nonIrrGrossDemandDict['return_flow_fraction']['domestic'] * self.domesticWaterWithdrawal +\ + nonIrrGrossDemandDict['return_flow_fraction']['industry'] * self.industryWaterWithdrawal +\ + nonIrrGrossDemandDict['return_flow_fraction']['livestock']* self.livestockWaterWithdrawal + # - ignore very small return flow (less than 0.1 mm) + self.nonIrrReturnFlow = pcr.rounddown(self.nonIrrReturnFlow * 10000.)/10000. + self.nonIrrReturnFlow = pcr.min(self.nonIrrReturnFlow, self.nonIrrGrossDemand) + + if lc.debugWaterBalance: + vos.waterBalanceCheck([self.irrGrossDemand,\ + self.nonIrrGrossDemand],\ + [self.totalPotentialGrossDemand],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)] ,\ + 'waterAllocationForAllSectors',True,\ + currTimeStep.fulldate,threshold=1e-4) + vos.waterBalanceCheck([self.domesticWaterWithdrawal,\ + self.industryWaterWithdrawal,\ + self.livestockWaterWithdrawal],\ + [self.nonIrrGrossDemand],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)] ,\ + 'waterAllocationForNonIrrigationSectors',True,\ + currTimeStep.fulldate,threshold=1e-4) + vos.waterBalanceCheck([self.irrGrossDemand,\ + self.domesticWaterWithdrawal,\ + self.industryWaterWithdrawal,\ + self.livestockWaterWithdrawal],\ + [self.totalPotentialGrossDemand],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)] ,\ + 'waterAllocationPerSector',True,\ + currTimeStep.fulldate,threshold=1e-4) + + # TODO: Perform water balance checks for all sources: desalination, surface water, non-fossil groundwater and fossil groundwater diff --git a/model/waterUse_v1.py b/model/waterUse_v1.py new file mode 100644 index 000000000..46950539b --- /dev/null +++ b/model/waterUse_v1.py @@ -0,0 +1,888 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pcraster as pcr +import virtualOS as vos +from ncConverter import * + +import logging +logger = logging.getLogger(__name__) + +class WaterUse(object): + + #................................................................................................................................................ + + def __init__(self, nonIrrGrossDemandDict, + swAbstractionFractionDict, + groundwater, + routing, + allocSegments, + currTimeStep, + desalinationWaterUse, + groundwater_pumping_region_ids, + regionalAnnualGroundwaterAbstractionLimit, + + name, + landmask, + numberOfLayers, + includeIrrigation, + cropKC, + minTopWaterLayer, + topWaterLayer, + cropDeplFactor, + totalPotET, + readAvlWater, + totAvlWater, + potBareSoilEvap, + potTranspiration, + estimateTranspirationAndBareSoilEvap, + returnTotalEstimation, + returnTotalTranspirationOnly, + soilWaterStorage, + kSatUpp, + kSatUpp000005, + irrigationEfficiency, + netLqWaterToSoil, + fracVegCover, + usingAllocSegments, + segmentArea, + prioritizeLocalSourceToMeetWaterDemand, + surfaceWaterPiority, + limitAbstraction, + debugWaterBalance): + + object.__init__(self) + + self.nonIrrGrossDemandDict = nonIrrGrossDemandDict + self.swAbstractionFractionDict = swAbstractionFractionDict + self.groundwater = groundwater + self.routing = routing + self.allocSegments = allocSegments + self.currTimeStep = currTimeStep + self.desalinationWaterUse = desalinationWaterUse + self.groundwater_pumping_region_ids = groundwater_pumping_region_ids + self.regionalAnnualGroundwaterAbstractionLimit = regionalAnnualGroundwaterAbstractionLimit + + self.name = name + self.landmask = landmask + self.numberOfLayers = numberOfLayers + self.includeIrrigation = includeIrrigation + self.cropKC = cropKC + self.minTopWaterLayer = minTopWaterLayer + self.topWaterLayer = topWaterLayer + self.cropDeplFactor = cropDeplFactor + self.totalPotET = totalPotET + self.readAvlWater = readAvlWater + self.totAvlWater = totAvlWater + self.potBareSoilEvap = potBareSoilEvap + self.potTranspiration = potTranspiration + self.estimateTranspirationAndBareSoilEvap = estimateTranspirationAndBareSoilEvap + self.returnTotalEstimation = returnTotalEstimation + self.returnTotalTranspirationOnly = returnTotalTranspirationOnly + self.soilWaterStorage = soilWaterStorage + self.kSatUpp = kSatUpp + self.kSatUpp000005 = kSatUpp000005 + self.irrigationEfficiency = irrigationEfficiency + self.netLqWaterToSoil = netLqWaterToSoil + self.fracVegCover = fracVegCover + self.usingAllocSegments = usingAllocSegments + self.segmentArea = segmentArea + self.prioritizeLocalSourceToMeetWaterDemand = prioritizeLocalSourceToMeetWaterDemand + self.surfaceWaterPiority = surfaceWaterPiority + self.limitAbstraction = limitAbstraction + self.debugWaterBalance = debugWaterBalance + + self.calculateWaterDemand(nonIrrGrossDemandDict, + swAbstractionFractionDict, + groundwater, + routing, + allocSegments, + currTimeStep, + desalinationWaterUse, + groundwater_pumping_region_ids, + regionalAnnualGroundwaterAbstractionLimit) + + #................................................................................................................................................ + + def calculateIrrigationDemand(self): + ''' + irrigation water demand (unit: m/day) for paddy and non-paddy + ''' + self.irrGrossDemand = pcr.scalar(0.) + if (self.name == 'irrPaddy' or self.name == 'irr_paddy') and self.includeIrrigation: + self.irrGrossDemand = \ + pcr.ifthenelse(self.cropKC > 0.75, + pcr.max(0.0, + self.minTopWaterLayer - (self.topWaterLayer )), + 0.0) + # a function of: - cropKC (evaporation and transpiration), + # - topWaterLayer (water available in the irrigation field) + + if (self.name == 'irrNonPaddy' or self.name == 'irr_non_paddy' or self.name == "irr_non_paddy_crops") and self.includeIrrigation: + adjDeplFactor = \ + pcr.max(0.1, + pcr.min(0.8, + (self.cropDeplFactor + 0.04*(5.-self.totalPotET*1000.)))) + # original formula based on Allen et al. (1998) + # see: http://www.fao.org/docrep/x0490e/x0490e0e.htm# + + # alternative 1: irrigation demand (to fill the entire totAvlWater, maintaining the field capacity) - NOT USED + #~ self.irrGrossDemand = \ + #~ pcr.ifthenelse( self.cropKC > 0.20, \ + #~ pcr.ifthenelse( self.readAvlWater < \ + #~ adjDeplFactor*self.totAvlWater, \ + #~ pcr.max(0.0, self.totAvlWater-self.readAvlWater),0.),0.) # a function of cropKC and totalPotET (evaporation and transpiration), + # readAvlWater (available water in the root zone) + + # alternative 2: irrigation demand (to fill the entire totAvlWater, maintaining the field capacity, + # but with the correction of totAvlWater based on the rooting depth) + # - as the proxy of rooting depth, we use crop coefficient + self.irrigation_factor = pcr.ifthenelse(self.cropKC > 0.0,\ + pcr.min(1.0, self.cropKC / 1.0), 0.0) + self.irrGrossDemand = \ + pcr.ifthenelse( self.cropKC > 0.20, \ + pcr.ifthenelse( self.readAvlWater < \ + adjDeplFactor*self.irrigation_factor*self.totAvlWater, \ + pcr.max(0.0, self.totAvlWater*self.irrigation_factor-self.readAvlWater),0.),0.) + + # irrigation demand is implemented only if there is deficit in transpiration and/or evaporation + deficit_factor = 1.00 + evaporationDeficit = pcr.max(0.0, (self.potBareSoilEvap + self.potTranspiration)*deficit_factor -\ + self.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True)) + transpirationDeficit = pcr.max(0.0, + self.potTranspiration*deficit_factor -\ + self.estimateTranspirationAndBareSoilEvap(returnTotalEstimation = True, returnTotalTranspirationOnly = True)) + deficit = pcr.max(evaporationDeficit, transpirationDeficit) + # + # treshold to initiate irrigation + deficit_treshold = 0.20 * self.totalPotET + need_irrigation = pcr.ifthenelse(deficit > deficit_treshold, pcr.boolean(1),\ + pcr.ifthenelse(self.soilWaterStorage == 0.000, pcr.boolean(1), pcr.boolean(0))) + need_irrigation = pcr.cover(need_irrigation, pcr.boolean(0.0)) + # + self.irrGrossDemand = pcr.ifthenelse(need_irrigation, self.irrGrossDemand, 0.0) + + # demand is limited by potential evaporation for the next coming days + # - objective: to avoid too high and unrealistic demand + max_irrigation_interval = 15.0 + min_irrigation_interval = 7.0 + irrigation_interval = pcr.min(max_irrigation_interval, \ + pcr.max(min_irrigation_interval, \ + pcr.ifthenelse(self.totalPotET > 0.0, \ + pcr.roundup((self.irrGrossDemand + pcr.max(self.readAvlWater, self.soilWaterStorage))/ self.totalPotET), 1.0))) + # - irrigation demand - limited by potential evaporation for the next coming days + self.irrGrossDemand = pcr.min(pcr.max(0.0,\ + self.totalPotET * irrigation_interval - pcr.max(self.readAvlWater, self.soilWaterStorage)),\ + self.irrGrossDemand) + + # assume that smart farmers do not irrigate higher than infiltration capacities + if self.numberOfLayers == 2: self.irrGrossDemand = pcr.min(self.irrGrossDemand, self.kSatUpp) + if self.numberOfLayers == 3: self.irrGrossDemand = pcr.min(self.irrGrossDemand, self.kSatUpp000005) + + # irrigation efficiency, minimum demand for start irrigating and maximum value to cap excessive demand + if self.includeIrrigation: + + # irrigation efficiency # TODO: Improve the concept of irrigation efficiency + self.irrigationEfficiencyUsed = pcr.min(1.0, pcr.max(0.10, self.irrigationEfficiency)) + # demand, including its inefficiency + self.irrGrossDemand = pcr.cover(self.irrGrossDemand / pcr.min(1.0, self.irrigationEfficiencyUsed), 0.0) + + # the following irrigation demand is not limited to available water + self.irrGrossDemand = pcr.ifthen(self.landmask, self.irrGrossDemand) + + # reduce irrGrossDemand by netLqWaterToSoil + self.irrGrossDemand = pcr.max(0.0, self.irrGrossDemand - self.netLqWaterToSoil) + + # minimum demand for start irrigating + minimum_demand = 0.005 # unit: m/day # TODO: set the minimum demand in the ini/configuration file. + if self.name == 'irrPaddy' or self.name == 'irr_paddy': + minimum_demand = pcr.min(self.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. + self.irrGrossDemand = pcr.ifthenelse(self.irrGrossDemand > minimum_demand, \ + self.irrGrossDemand , 0.0) + + maximum_demand = 0.025 # unit: m/day # TODO: set the maximum demand in the ini/configuration file. + if self.name == 'irrPaddy' or\ + self.name == 'irr_paddy': maximum_demand = pcr.min(self.minTopWaterLayer, 0.025) # TODO: set the minimum demand in the ini/configuration file. + self.irrGrossDemand = pcr.min(maximum_demand, self.irrGrossDemand) + + # ignore small irrigation demand (less than 1 mm) + self.irrGrossDemand = pcr.rounddown( self.irrGrossDemand *1000.)/1000. + + # irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? + self.irrGrossDemand = pcr.ifthenelse(self.fracVegCover > 0.0, self.irrGrossDemand, 0.0) + + # total irrigation gross demand (m) per cover types (not limited by available water) + self.totalPotentialMaximumIrrGrossDemandPaddy = 0.0 + self.totalPotentialMaximumIrrGrossDemandNonPaddy = 0.0 + if self.name == 'irrPaddy' or self.name == 'irr_paddy': + self.totalPotentialMaximumIrrGrossDemandPaddy = self.irrGrossDemand + if self.name == 'irrNonPaddy' or self.name == 'irr_non_paddy' or self.name == 'irr_non_paddy_crops': + self.totalPotentialMaximumIrrGrossDemandNonPaddy = self.irrGrossDemand + +#.............................................................................................................................................................. + + def calculateWaterDemand(self, nonIrrGrossDemandDict, + swAbstractionFractionDict, + groundwater, + routing, + allocSegments, + currTimeStep, + desalinationWaterUse, + groundwater_pumping_region_ids, + regionalAnnualGroundwaterAbstractionLimit): + + # irrigation demand + self.calculateIrrigationDemand() + + # non irrigation demand is only calculated for areas with fracVegCover > 0 # DO WE NEED THIS ? + nonIrrGrossDemandDict['potential_demand']['domestic'] = pcr.ifthenelse(self.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] , 0.0) + nonIrrGrossDemandDict['potential_demand']['industry'] = pcr.ifthenelse(self.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['industry'] , 0.0) + nonIrrGrossDemandDict['potential_demand']['livestock'] = pcr.ifthenelse(self.fracVegCover > 0.0, nonIrrGrossDemandDict['potential_demand']['livestock'], 0.0) + + # non irrigation water demand, including the livestock (not limited by available water) + self.nonIrrGrossDemand = nonIrrGrossDemandDict['potential_demand']['domestic'] +\ + nonIrrGrossDemandDict['potential_demand']['industry'] +\ + nonIrrGrossDemandDict['potential_demand']['livestock'] + + # total irrigation and livestock demand (not limited by available water) + totalIrrigationLivestockDemand = self.irrGrossDemand + nonIrrGrossDemandDict['potential_demand']['livestock'] + + # totalGrossDemand (m): irrigation and non irrigation (not limited by available water) - these values will not be reduced + self.totalPotentialMaximumGrossDemand = self.irrGrossDemand + self.nonIrrGrossDemand + # - irrigation (excluding livestock) + self.totalPotentialMaximumIrrGrossDemand = self.irrGrossDemand + # - non irrigation (including livestock) + self.totalPotentialMaximumNonIrrGrossDemand = self.nonIrrGrossDemand + + # the following value will be reduced by available/accesible water + self.totalPotentialGrossDemand = self.totalPotentialMaximumGrossDemand + + #................................................................................................................... + # Abstraction and Allocation of DESALINATED WATER + # - desalination water to satisfy water demand + if self.usingAllocSegments: # using zone/segments at which networks are defined (as defined in the landSurface options) + # + logger.debug("Allocation of supply from desalination water.") + # + volDesalinationAbstraction, volDesalinationAllocation = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = self.totalPotentialGrossDemand*routing.cellArea,\ + available_water_volume = pcr.max(0.00, desalinationWaterUse*routing.cellArea),\ + allocation_zones = allocSegments,\ + zone_area = self.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + # + self.desalinationAbstraction = volDesalinationAbstraction / routing.cellArea + self.desalinationAllocation = volDesalinationAllocation / routing.cellArea + # + else: + # + logger.debug("Supply from desalination water is only for satisfying local demand (no network).") + self.desalinationAbstraction = pcr.min(desalinationWaterUse, self.totalPotentialGrossDemand) + self.desalinationAllocation = self.desalinationAbstraction + # + self.desalinationAbstraction = pcr.ifthen(self.landmask, self.desalinationAbstraction) + self.desalinationAllocation = pcr.ifthen(self.landmask, self.desalinationAllocation) + + # water demand that have been satisfied (unit: m/day) - after desalination + # - for irrigation (excluding livestock) + satisfiedIrrigationDemand = vos.getValDivZero(self.irrGrossDemand, self.totalPotentialGrossDemand) * self.desalinationAllocation + # - for domestic, industry and livestock + satisfiedNonIrrDemand = pcr.max(0.00, self.desalinationAllocation - satisfiedIrrigationDemand) + # - for domestic + satisfiedDomesticDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['domestic'], + self.totalPotentialMaximumNonIrrGrossDemand) + # - for industry + satisfiedIndustryDemand = satisfiedNonIrrDemand * vos.getValDivZero(nonIrrGrossDemandDict['potential_demand']['industry'], + self.totalPotentialMaximumNonIrrGrossDemand) + # - for livestock + satisfiedLivestockDemand = pcr.max(0.0, satisfiedNonIrrDemand - satisfiedDomesticDemand - satisfiedIndustryDemand) + + # total remaining gross demand (m/day) after desalination + ################################################################################################################################ + self.totalGrossDemandAfterDesalination = pcr.max(0.0, self.totalPotentialGrossDemand - self.desalinationAllocation) + # the remaining water demand per sector + # - for domestic + remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # - for industry + remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # - for livestock + remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - total for livestock and irrigation + remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # - total for industrial and domestic (excluding livestock) + remainingIndustrialDomestic = pcr.max(0.0, self.totalGrossDemandAfterDesalination - remainingIrrigationLivestock) + + # Abstraction and Allocation of SURFACE WATER + ############################################################################################################################## + # calculate the estimate of surface water demand (considering by swAbstractionFractionDict) + # - for industrial and domestic + swAbstractionFraction_industrial_domestic = pcr.min(swAbstractionFractionDict['max_for_non_irrigation'],\ + swAbstractionFractionDict['estimate']) + if swAbstractionFractionDict['non_irrigation'] is not None: + swAbstractionFraction_industrial_domestic = swAbstractionFractionDict['non_irrigation'] + + surface_water_demand_estimate = swAbstractionFraction_industrial_domestic * remainingIndustrialDomestic + # - for irrigation and livestock + surface_water_irrigation_demand_estimate = swAbstractionFractionDict['irrigation'] * remainingIrrigationLivestock + # - surface water source as priority if groundwater irrigation fraction is relatively low + surface_water_irrigation_demand_estimate = \ + pcr.ifthenelse(swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_maximize_irrigation_surface_water'],\ + remainingIrrigationLivestock, surface_water_irrigation_demand_estimate) + # - update estimate of surface water demand withdrawal (unit: m/day) + surface_water_demand_estimate += surface_water_irrigation_demand_estimate + # - prioritize surface water use in non productive aquifers that have limited groundwater supply + surface_water_demand_estimate = pcr.ifthenelse(groundwater.productive_aquifer, surface_water_demand_estimate,\ + pcr.max(0.0, remainingIrrigationLivestock - \ + pcr.min(groundwater.avgAllocationShort, groundwater.avgAllocation))) + # - maximize/optimize surface water use in areas with the overestimation of groundwater supply + surface_water_demand_estimate += pcr.max(0.0, pcr.max(groundwater.avgAllocationShort, groundwater.avgAllocation) -\ + (1.0 - swAbstractionFractionDict['irrigation']) * totalIrrigationLivestockDemand -\ + (1.0 - swAbstractionFraction_industrial_domestic) * (self.totalPotentialMaximumGrossDemand - totalIrrigationLivestockDemand)) + # + # total demand (unit: m/day) that should be allocated from surface water + # (corrected/limited by swAbstractionFractionDict and limited by the remaining demand) + surface_water_demand_estimate = pcr.min(self.totalGrossDemandAfterDesalination, surface_water_demand_estimate) + correctedRemainingIrrigationLivestock = pcr.min(surface_water_demand_estimate, remainingIrrigationLivestock) + correctedRemainingIndustrialDomestic = pcr.min(remainingIndustrialDomestic,\ + pcr.max(0.0, surface_water_demand_estimate - remainingIrrigationLivestock)) + correctedSurfaceWaterDemandEstimate = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic + surface_water_demand = correctedSurfaceWaterDemandEstimate + # + # if surface water abstraction as the first priority + if self.surfaceWaterPiority: surface_water_demand = self.totalGrossDemandAfterDesalination + # + if self.usingAllocSegments: # using zone/segment at which supply network is defined + # + logger.debug("Allocation of surface water abstraction.") + # + volActSurfaceWaterAbstract, volAllocSurfaceWaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = surface_water_demand*routing.cellArea,\ + available_water_volume = pcr.max(0.00, routing.readAvlChannelStorage),\ + allocation_zones = allocSegments,\ + zone_area = self.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + + self.actSurfaceWaterAbstract = volActSurfaceWaterAbstract / routing.cellArea + self.allocSurfaceWaterAbstract = volAllocSurfaceWaterAbstract / routing.cellArea + # + else: + logger.debug("Surface water abstraction is only to satisfy local demand (no surface water network).") + self.actSurfaceWaterAbstract = pcr.min(routing.readAvlChannelStorage/routing.cellArea,\ + surface_water_demand) # unit: m + self.allocSurfaceWaterAbstract = self.actSurfaceWaterAbstract # unit: m + # + self.actSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.actSurfaceWaterAbstract) + self.allocSurfaceWaterAbstract = pcr.ifthen(self.landmask, self.allocSurfaceWaterAbstract) + ################################################################################################################################ + # - end of Abstraction and Allocation of SURFACE WATER + + + # water demand that have been satisfied (unit: m/day) - after desalination and surface water supply + ################################################################################################################################ + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromSurfaceWater = self.allocSurfaceWaterAbstract * \ + vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedSurfaceWaterDemandEstimate) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromSurfaceWater = satisfiedIrrigationLivestockDemandFromSurfaceWater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromSurfaceWater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract - satisfiedIrrigationDemandFromSurfaceWater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromSurfaceWater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromSurfaceWater - \ + satisfiedIrrigationDemandFromSurfaceWater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromSurfaceWater = pcr.max(0.0, self.allocSurfaceWaterAbstract -\ + satisfiedIrrigationLivestockDemandFromSurfaceWater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromSurfaceWater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + + ###################################################################################################################### + # water demand (unit: m) that must be satisfied by groundwater abstraction (not limited to available water) + self.potGroundwaterAbstract = pcr.max(0.0, self.totalGrossDemandAfterDesalination - self.allocSurfaceWaterAbstract) + ###################################################################################################################### + # water demand per sector + # - for domestic + remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # - for industry + remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # - for livestock + remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - total for livestock and irrigation + remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # - total for industrial and domestic (excluding livestock) + remainingIndustrialDomestic = remainingIndustry + remainingDomestic + + + # Abstraction and Allocation of GROUNDWATER (fossil and non fossil) + ######################################################################################################################### + # estimating groundwater water demand: + # - demand for industrial and domestic sectors + # (all remaining demand for these sectors should be satisfied) + groundwater_demand_estimate = remainingIndustrialDomestic + # - demand for irrigation and livestock sectors + # (only part of them will be satisfied, as they may be too high due to the uncertainty in the irrigation scheme) + irrigationLivestockGroundwaterDemand = pcr.min(remainingIrrigationLivestock, \ + pcr.max(0.0, \ + (1.0 - swAbstractionFractionDict['irrigation'])*totalIrrigationLivestockDemand)) + groundwater_demand_estimate += irrigationLivestockGroundwaterDemand + + ##################################################################################################### + # water demand that must be satisfied by groundwater abstraction (not limited to available water) + self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, groundwater_demand_estimate) + ##################################################################################################### + + # constraining groundwater abstraction with the regional annual pumping capacity + if groundwater.limitRegionalAnnualGroundwaterAbstraction: + + logger.debug('Total groundwater abstraction is limited by regional annual pumping capacity.') + + # estimate of total groundwater abstraction (m3) from the last 365 days: + tolerating_days = 0. + annualGroundwaterAbstraction = groundwater.avgAbstraction * routing.cellArea *\ + pcr.min(pcr.max(0.0, 365.0 - tolerating_days), routing.timestepsToAvgDischarge) + # total groundwater abstraction (m3) from the last 365 days at the regional scale + regionalAnnualGroundwaterAbstraction = pcr.areatotal(pcr.cover(annualGroundwaterAbstraction, 0.0), groundwater_pumping_region_ids) + + #~ # reduction factor to reduce groundwater abstraction/demand + #~ reductionFactorForPotGroundwaterAbstract = pcr.cover(\ + #~ pcr.ifthenelse(regionalAnnualGroundwaterAbstractionLimit > 0.0, + #~ pcr.max(0.000, regionalAnnualGroundwaterAbstractionLimit -\ + #~ regionalAnnualGroundwaterAbstraction) / + #~ regionalAnnualGroundwaterAbstractionLimit , 0.0), 0.0) + + #~ # reduced potential groundwater abstraction (after pumping capacity) + #~ self.potGroundwaterAbstract = pcr.min(1.00, reductionFactorForPotGroundwaterAbstract) * self.potGroundwaterAbstract + + #~ # alternative: reduced potential groundwater abstraction (after pumping capacity) and considering the average recharge (baseflow) + #~ potGroundwaterAbstract = pcr.min(1.00, reductionFactorForPotGroundwaterAbstract) * self.potGroundwaterAbstract + #~ self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, + #~ potGroundwaterAbstract + pcr.max(0.0, routing.avgBaseflow / routing.cellArea)) + + ################## NEW METHOD ################################################################################################################# + # the remaining pumping capacity (unit: m3) at the regional scale + remainingRegionalAnnualGroundwaterAbstractionLimit = pcr.max(0.0, regionalAnnualGroundwaterAbstractionLimit - \ + regionalAnnualGroundwaterAbstraction) + # considering safety factor (residence time in day-1) + remainingRegionalAnnualGroundwaterAbstractionLimit *= 0.33 + + # the remaining pumping capacity (unit: m3) limited by self.potGroundwaterAbstract (at the regional scale) + remainingRegionalAnnualGroundwaterAbstractionLimit = pcr.min(remainingRegionalAnnualGroundwaterAbstractionLimit,\ + pcr.areatotal(self.potGroundwaterAbstract * routing.cellArea, groundwater_pumping_region_ids)) + + # the remaining pumping capacity (unit: m3) at the pixel scale - downscaled using self.potGroundwaterAbstract + remainingPixelAnnualGroundwaterAbstractionLimit = remainingRegionalAnnualGroundwaterAbstractionLimit * \ + vos.getValDivZero(self.potGroundwaterAbstract * routing.cellArea, pcr.areatotal(self.potGroundwaterAbstract * routing.cellArea, groundwater_pumping_region_ids)) + + # reduced (after pumping capacity) potential groundwater abstraction/demand (unit: m) and considering the average recharge (baseflow) + self.potGroundwaterAbstract = pcr.min(self.potGroundwaterAbstract, \ + remainingPixelAnnualGroundwaterAbstractionLimit/routing.cellArea + pcr.max(0.0, routing.avgBaseflow / routing.cellArea)) + ################## end of NEW METHOD (but still under development) ########################################################################################################## + + #~ # Shall we will always try to fulfil the industrial and domestic demand? + #~ self.potGroundwaterAbstract = pcr.max(remainingIndustrialDomestic, self.potGroundwaterAbstract) + + else: + logger.debug('NO LIMIT for regional groundwater (annual) pumping. It may result too high groundwater abstraction.') + + # Abstraction and Allocation of NON-FOSSIL GROUNDWATER + # ############################################################################################################################# + # available storGroundwater (non fossil groundwater) that can be accessed (unit: m) + readAvlStorGroundwater = pcr.cover(pcr.max(0.00, groundwater.storGroundwater), 0.0) + # - considering maximum daily groundwater abstraction + readAvlStorGroundwater = pcr.min(readAvlStorGroundwater, groundwater.maximumDailyGroundwaterAbstraction) + # - ignore groundwater storage in non-productive aquifer + readAvlStorGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, readAvlStorGroundwater, 0.0) + + # for non-productive aquifer, reduce readAvlStorGroundwater to the current recharge/baseflow rate + readAvlStorGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, \ + readAvlStorGroundwater, pcr.min(readAvlStorGroundwater, pcr.max(routing.avgBaseflow, 0.0))) + + # avoid the condition that the entire groundwater volume abstracted instantaneously + readAvlStorGroundwater *= 0.75 + + if groundwater.usingAllocSegments: + + logger.debug('Allocation of non fossil groundwater abstraction.') + + # TODO: considering aquifer productivity while doing the allocation (e.g. using aquifer transmissivity/conductivity) + + # non fossil groundwater abstraction and allocation in volume (unit: m3) + volActGroundwaterAbstract, volAllocGroundwaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = self.potGroundwaterAbstract*routing.cellArea,\ + available_water_volume = pcr.max(0.00, readAvlStorGroundwater*routing.cellArea),\ + allocation_zones = groundwater.allocSegments,\ + zone_area = groundwater.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + + # non fossil groundwater abstraction and allocation in meter + self.nonFossilGroundwaterAbs = volActGroundwaterAbstract / routing.cellArea + self.allocNonFossilGroundwater = volAllocGroundwaterAbstract/ routing.cellArea + + else: + + logger.debug('Non fossil groundwater abstraction is only for satisfying local demand.') + self.nonFossilGroundwaterAbs = pcr.min(readAvlStorGroundwater, self.potGroundwaterAbstract) + self.allocNonFossilGroundwater = self.nonFossilGroundwaterAbs + ################################################################################################################################ + # - end of Abstraction and Allocation of NON FOSSIL GROUNDWATER + + ################################################################################################################################ + # variable to reduce capillary rise in order to ensure there is always enough water to supply non fossil groundwater abstraction + self.reducedCapRise = self.nonFossilGroundwaterAbs + # TODO: Check do we need this for runs with MODFLOW ??? + ################################################################################################################################ + + + # water demand that have been satisfied (unit: m/day) - after desalination, surface water and non-fossil groundwater supply + ################################################################################################################################ + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromNonFossilGroundwater = self.allocNonFossilGroundwater * \ + vos.getValDivZero(irrigationLivestockGroundwaterDemand, groundwater_demand_estimate) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromNonFossilGroundwater = satisfiedIrrigationLivestockDemandFromNonFossilGroundwater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromNonFossilGroundwater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromNonFossilGroundwater = pcr.max(0.0, self.allocNonFossilGroundwater - satisfiedIrrigationLivestockDemandFromNonFossilGroundwater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromNonFossilGroundwater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromNonFossilGroundwater - \ + satisfiedIrrigationDemandFromNonFossilGroundwater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromNonFossilGroundwater = pcr.max(0.0, self.allocNonFossilGroundwater -\ + satisfiedIrrigationLivestockDemandFromNonFossilGroundwater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromNonFossilGroundwater * vos.getValDivZero(remainingDomestic, remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromNonFossilGroundwater * vos.getValDivZero(remainingIndustry, remainingIndustrialDomestic) + + ###################################################################################################################### + ###################################################################################################################### + # water demand that must be satisfied by fossil groundwater abstraction (unit: m, not limited to available water) + self.potFossilGroundwaterAbstract = pcr.max(0.0, self.potGroundwaterAbstract - \ + self.allocNonFossilGroundwater) + ###################################################################################################################### + ###################################################################################################################### + + # For a run using MODFLOW, the concept of fossil groundwater abstraction is abandoned (self.limitAbstraction == True): + if groundwater.useMODFLOW or self.limitAbstraction: + logger.debug('Fossil groundwater abstractions are NOT allowed') + self.fossilGroundwaterAbstr = pcr.scalar(0.0) + self.fossilGroundwaterAlloc = pcr.scalar(0.0) + + # Abstraction and Allocation of FOSSIL GROUNDWATER + # ##################################################################################################################################### + + if self.limitAbstraction == False: # TODO: For runs without any water use, we can exclude this. + + logger.debug('Fossil groundwater abstractions are allowed.') + + # the remaining water demand (m/day) for all sectors - NOT limited to self.potFossilGroundwaterAbstract + ##################################################################################################################### + # - for domestic + remainingDomestic = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['domestic'] - satisfiedDomesticDemand) + # - for industry + remainingIndustry = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['industry'] - satisfiedIndustryDemand) + # - for livestock + remainingLivestock = pcr.max(0.0, nonIrrGrossDemandDict['potential_demand']['livestock'] - satisfiedLivestockDemand) + # - for irrigation (excluding livestock) + remainingIrrigation = pcr.max(0.0, self.irrGrossDemand - satisfiedIrrigationDemand) + # - total for livestock and irrigation + remainingIrrigationLivestock = remainingIrrigation + remainingLivestock + # - total for industrial and domestic (excluding livestock) + remainingIndustrialDomestic = remainingIndustry + remainingDomestic + # - remaining total demand + remainingTotalDemand = remainingIrrigationLivestock + remainingIndustrialDomestic + + # constraining fossil groundwater abstraction with regional pumping capacity + if groundwater.limitRegionalAnnualGroundwaterAbstraction and self.limitAbstraction == False: + + logger.debug('Fossil groundwater abstraction is allowed, BUT limited by the regional annual pumping capacity.') + + # estimate of total groundwater abstraction (m3) from the last 365 days: + # - considering abstraction from non fossil groundwater + annualGroundwaterAbstraction += self.nonFossilGroundwaterAbs*routing.cellArea + # at the regional scale + regionalAnnualGroundwaterAbstraction = pcr.areatotal(pcr.cover(annualGroundwaterAbstraction, 0.0), groundwater_pumping_region_ids) + + # fossil groundwater demand/asbtraction reduced by pumping capacity (unit: m/day) + # - safety factor to avoid the remaining limit abstracted at once (due to overestimation of groundwater demand) + safety_factor_for_fossil_abstraction = 1.00 + self.potFossilGroundwaterAbstract *= pcr.min(1.00,\ + pcr.cover(\ + pcr.ifthenelse(regionalAnnualGroundwaterAbstractionLimit > 0.0, + pcr.max(0.000, regionalAnnualGroundwaterAbstractionLimit * safety_factor_for_fossil_abstraction-\ + regionalAnnualGroundwaterAbstraction) / + regionalAnnualGroundwaterAbstractionLimit , 0.0), 0.0)) + + #~ # Shall we will always try to fulfil the remaining industrial and domestic demand? + #~ self.potFossilGroundwaterAbstract = pcr.max(remainingIndustrialDomestic, self.potFossilGroundwaterAbstract) + + if self.limitAbstraction == False: # TODO: For runs without any water use, we can exclude this. + + ############################################################################################################################### + # estimate the remaining total demand (unit: m/day) LIMITED to self.potFossilGroundwaterAbstract + ############################################################################################################################### + + correctedRemainingTotalDemand = pcr.min(self.potFossilGroundwaterAbstract, remainingTotalDemand) + + # the remaining industrial and domestic demand and livestock (unit: m/day) limited to self.potFossilGroundwaterAbstract + # - no correction, we will always try to fulfil these demands + correctedRemainingIndustrialDomesticLivestock = pcr.min(remainingIndustrialDomestic + remainingLivestock, correctedRemainingTotalDemand) + + # the remaining irrigation demand limited to self.potFossilGroundwaterAbstract + correctedRemainingIrrigation = pcr.min(remainingIrrigation, \ + pcr.max(0.0, correctedRemainingTotalDemand - correctedRemainingIndustrialDomesticLivestock)) + # - ignore small irrigation demand (less than 1 mm) + correctedRemainingIrrigation = pcr.rounddown(correctedRemainingIrrigation*1000.)/1000. + + # the (corrected) remaining total demand (limited to self.potFossilGroundwaterAbstract) + correctedRemainingTotalDemand = correctedRemainingIndustrialDomesticLivestock + correctedRemainingIrrigation + + # the (corrected) remaining industrial and domestic demand (excluding livestock) + correctedRemainingIndustrialDomestic = pcr.min(remainingIndustrialDomestic, correctedRemainingTotalDemand) + + # the remaining irrigation and livestock water demand limited to self.potFossilGroundwaterAbstract + correctedRemainingIrrigationLivestock = pcr.min(remainingIrrigationLivestock, \ + pcr.max(0.0, correctedRemainingTotalDemand - correctedRemainingIndustrialDomestic)) + + # the (corrected) remaining total demand (unit: m/day) limited to self.potFossilGroundwaterAbstract + correctedRemainingTotalDemand = correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic + + # TODO: Do the water balance check: correctedRemainingIrrigationLivestock + correctedRemainingIndustrialDomestic <= self.potFossilGroundwaterAbstract + + # constrain the irrigation groundwater demand with groundwater source fraction + correctedRemainingIrrigationLivestock = pcr.min((1.0 - swAbstractionFractionDict['irrigation']) * remainingIrrigationLivestock,\ + correctedRemainingIrrigationLivestock) + correctedRemainingIrrigationLivestock = pcr.max(0.0,\ + pcr.min(correctedRemainingIrrigationLivestock,\ + pcr.max(0.0, totalIrrigationLivestockDemand) * (1.0 - swAbstractionFractionDict['irrigation']) - satisfiedIrrigationDemandFromNonFossilGroundwater)) + + # ignore fossil groundwater abstraction in irrigation areas dominated by swAbstractionFractionDict['irrigation'] + correctedRemainingIrrigationLivestock = pcr.ifthenelse(\ + swAbstractionFractionDict['irrigation'] >= swAbstractionFractionDict['treshold_to_minimize_fossil_groundwater_irrigation'], 0.0,\ + correctedRemainingIrrigationLivestock) + + # reduce the fossil irrigation and livestock demands with enough supply of non fossil groundwater (in order to minimize unrealistic areas of fossil groundwater abstraction) + # - supply from the average recharge (baseflow) and non fossil groundwater allocation + nonFossilGroundwaterSupply = pcr.max(pcr.max(0.0, routing.avgBaseflow) / routing.cellArea, \ + groundwater.avgNonFossilAllocationShort, groundwater.avgNonFossilAllocation) + # - irrigation supply from the non fossil groundwater + nonFossilIrrigationGroundwaterSupply = nonFossilGroundwaterSupply * vos.getValDivZero(remainingIrrigationLivestock, remainingTotalDemand) + # - the corrected/reduced irrigation and livestock demand + correctedRemainingIrrigationLivestock = pcr.max(0.0, correctedRemainingIrrigationLivestock - nonFossilIrrigationGroundwaterSupply) + + # the corrected remaining total demand (unit: m/day) + correctedRemainingTotalDemand = correctedRemainingIndustrialDomestic + correctedRemainingIrrigationLivestock + + ############################################################################################################################### + + # water demand that must be satisfied by fossil groundwater abstraction + self.potFossilGroundwaterAbstract = pcr.min(self.potFossilGroundwaterAbstract, correctedRemainingTotalDemand) + + if groundwater.limitFossilGroundwaterAbstraction == False and self.limitAbstraction == False: + + # Note: If limitFossilGroundwaterAbstraction == False, + # allocation of fossil groundwater abstraction is not needed. + msg = 'Fossil groundwater abstractions are without limit for satisfying local demand. ' + msg = 'Allocation for fossil groundwater abstraction is NOT needed/implemented. ' + msg += 'However, the fossil groundwater abstraction rate still consider the maximumDailyGroundwaterAbstraction.' + logger.debug(msg) + + # fossil groundwater abstraction (unit: m/day) + self.fossilGroundwaterAbstr = self.potFossilGroundwaterAbstract + self.fossilGroundwaterAbstr = \ + pcr.min(\ + pcr.max(0.0, groundwater.maximumDailyGroundwaterAbstraction - self.nonFossilGroundwaterAbs), self.fossilGroundwaterAbstr) + + # fossil groundwater allocation (unit: m/day) + self.fossilGroundwaterAlloc = self.fossilGroundwaterAbstr + + if groundwater.limitFossilGroundwaterAbstraction and self.limitAbstraction == False: + + logger.debug('Fossil groundwater abstractions are allowed, but with limit.') + + # accesible fossil groundwater (unit: m/day) + readAvlFossilGroundwater = pcr.ifthenelse(groundwater.productive_aquifer, groundwater.storGroundwaterFossil, 0.0) + # - residence time (day-1) or safety factor (to avoid 'unrealistic' zero fossil groundwater) + readAvlFossilGroundwater *= 0.10 + # - considering maximum daily groundwater abstraction + readAvlFossilGroundwater = pcr.min(readAvlFossilGroundwater, groundwater.maximumDailyFossilGroundwaterAbstraction, \ + pcr.max(0.0, groundwater.maximumDailyGroundwaterAbstraction - self.nonFossilGroundwaterAbs)) + readAvlFossilGroundwater = pcr.max(pcr.cover(readAvlFossilGroundwater, 0.0), 0.0) + + if groundwater.usingAllocSegments: + + logger.debug('Allocation of fossil groundwater abstraction.') + + # TODO: considering aquifer productivity while doing the allocation. + + # fossil groundwater abstraction and allocation in volume (unit: m3) + volActGroundwaterAbstract, volAllocGroundwaterAbstract = \ + vos.waterAbstractionAndAllocation( + water_demand_volume = self.potFossilGroundwaterAbstract*routing.cellArea,\ + available_water_volume = pcr.max(0.00, readAvlFossilGroundwater*routing.cellArea),\ + allocation_zones = groundwater.allocSegments,\ + zone_area = groundwater.segmentArea,\ + high_volume_treshold = None,\ + debug_water_balance = True,\ + extra_info_for_water_balance_reporting = str(currTimeStep.fulldate), + landmask = self.landmask, + ignore_small_values = False, + prioritizing_local_source = self.prioritizeLocalSourceToMeetWaterDemand) + + # fossil groundwater abstraction and allocation in meter + self.fossilGroundwaterAbstr = volActGroundwaterAbstract /routing.cellArea + self.fossilGroundwaterAlloc = volAllocGroundwaterAbstract/routing.cellArea + + else: + + logger.debug('Fossil groundwater abstraction is only for satisfying local demand. NO Allocation for fossil groundwater abstraction.') + + self.fossilGroundwaterAbstr = pcr.min(pcr.max(0.0, readAvlFossilGroundwater), self.potFossilGroundwaterAbstract) + self.fossilGroundwaterAlloc = self.fossilGroundwaterAbstr + + + # water demand that have been satisfied (m/day) - after desalination, surface water, non fossil groundwater & fossil groundwater + ################################################################################################################################ + + # from fossil groundwater, we should prioritize domestic and industrial water demand + prioritizeFossilGroundwaterForDomesticIndutrial = False # TODO: Define this in the configuration file. + + if prioritizeFossilGroundwaterForDomesticIndutrial: + + # - first priority: for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromFossilGroundwater = pcr.min(self.fossilGroundwaterAlloc, \ + remainingIndustrialDomestic) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + # - for irrigation and livestock demand + satisfiedIrrigationLivestockDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - \ + satisfiedIndustrialDomesticDemandFromFossilGroundwater) + # - for irrigation + satisfiedIrrigationDemand += satisfiedIrrigationLivestockDemandFromFossilGroundwater * vos.getValDivZero(remainingIrrigation, \ + remainingIrrigationLivestock) + # - for livestock + satisfiedLivestockDemand += satisfiedIrrigationLivestockDemandFromFossilGroundwater * vos.getValDivZero(remainingLivestock, \ + remainingIrrigationLivestock) + + else: + + # Distribute fossil water proportionaly based on the amount of each sector + + # - for irrigation and livestock water demand + satisfiedIrrigationLivestockDemandFromFossilGroundwater = self.fossilGroundwaterAlloc * \ + vos.getValDivZero(correctedRemainingIrrigationLivestock, correctedRemainingTotalDemand) + # - for irrigation water demand, but not including livestock + satisfiedIrrigationDemandFromFossilGroundwater = satisfiedIrrigationLivestockDemandFromFossilGroundwater * \ + vos.getValDivZero(remainingIrrigation, remainingIrrigationLivestock) + satisfiedIrrigationDemand += satisfiedIrrigationDemandFromFossilGroundwater + # - for non irrigation water demand: livestock, domestic and industry + satisfiedNonIrrDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - satisfiedIrrigationDemandFromFossilGroundwater) + satisfiedNonIrrDemand += satisfiedNonIrrDemandFromFossilGroundwater + # - for livestock + satisfiedLivestockDemand += pcr.max(0.0, satisfiedIrrigationLivestockDemandFromFossilGroundwater - \ + satisfiedIrrigationDemandFromFossilGroundwater) + # - for industrial and domestic demand (excluding livestock) + satisfiedIndustrialDomesticDemandFromFossilGroundwater = pcr.max(0.0, self.fossilGroundwaterAlloc - \ + satisfiedIrrigationLivestockDemandFromFossilGroundwater) + # - for domestic + satisfiedDomesticDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingDomestic, \ + remainingIndustrialDomestic) + # - for industry + satisfiedIndustryDemand += satisfiedIndustrialDomesticDemandFromFossilGroundwater * vos.getValDivZero(remainingIndustry, \ + remainingIndustrialDomestic) + + # water demand limited to available/allocated water + self.totalPotentialGrossDemand = self.fossilGroundwaterAlloc +\ + self.allocNonFossilGroundwater +\ + self.allocSurfaceWaterAbstract +\ + self.desalinationAllocation + + # total groundwater abstraction and allocation (unit: m/day) + self.totalGroundwaterAllocation = self.allocNonFossilGroundwater + self.fossilGroundwaterAlloc + self.totalGroundwaterAbstraction = self.fossilGroundwaterAbstr + self.nonFossilGroundwaterAbs + + # irrigation water demand (excluding livestock) limited to available/allocated water (unit: m/day) + self.irrGrossDemand = satisfiedIrrigationDemand # not including livestock + + # irrigation gross demand (m) per cover type (limited by available water) + self.irrGrossDemandPaddy = 0.0 + self.irrGrossDemandNonPaddy = 0.0 + if self.name == 'irrPaddy' or self.name == "irr_paddy": self.irrGrossDemandPaddy = self.irrGrossDemand + if self.name == 'irrNonPaddy' or self.name == "irr_non_paddy" or self.name == "irr_non_paddy_crops": self.irrGrossDemandNonPaddy = self.irrGrossDemand + + # non irrigation water demand (including livestock) limited to available/allocated water (unit: m/day) + self.nonIrrGrossDemand = pcr.max(0.0, \ + self.totalPotentialGrossDemand - self.irrGrossDemand) # livestock, domestic and industry + self.domesticWaterWithdrawal = satisfiedDomesticDemand + self.industryWaterWithdrawal = satisfiedIndustryDemand + self.livestockWaterWithdrawal = satisfiedLivestockDemand + + # return flow (unit: m/day) from non irrigation withdrawal (from domestic, industry and livestock) + self.nonIrrReturnFlow = nonIrrGrossDemandDict['return_flow_fraction']['domestic'] * self.domesticWaterWithdrawal +\ + nonIrrGrossDemandDict['return_flow_fraction']['industry'] * self.industryWaterWithdrawal +\ + nonIrrGrossDemandDict['return_flow_fraction']['livestock']* self.livestockWaterWithdrawal + # - ignore very small return flow (less than 0.1 mm) + self.nonIrrReturnFlow = pcr.rounddown(self.nonIrrReturnFlow * 10000.)/10000. + self.nonIrrReturnFlow = pcr.min(self.nonIrrReturnFlow, self.nonIrrGrossDemand) + + if self.debugWaterBalance: + vos.waterBalanceCheck([self.irrGrossDemand,\ + self.nonIrrGrossDemand],\ + [self.totalPotentialGrossDemand],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)] ,\ + 'waterAllocationForAllSectors',True,\ + currTimeStep.fulldate,threshold=1e-4) + vos.waterBalanceCheck([self.domesticWaterWithdrawal,\ + self.industryWaterWithdrawal,\ + self.livestockWaterWithdrawal],\ + [self.nonIrrGrossDemand],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)] ,\ + 'waterAllocationForNonIrrigationSectors',True,\ + currTimeStep.fulldate,threshold=1e-4) + vos.waterBalanceCheck([self.irrGrossDemand,\ + self.domesticWaterWithdrawal,\ + self.industryWaterWithdrawal,\ + self.livestockWaterWithdrawal],\ + [self.totalPotentialGrossDemand],\ + [pcr.scalar(0.0)],\ + [pcr.scalar(0.0)] ,\ + 'waterAllocationPerSector',True,\ + currTimeStep.fulldate,threshold=1e-4) + + # TODO: Perform water balance checks for all sources: desalination, surface water, non-fossil groundwater and fossil groundwater