Source code for roger.models.svat_crop.svat_crop

from pathlib import Path
import h5netcdf
from roger import RogerSetup, roger_routine, roger_kernel, KernelOutput
from roger.variables import allocate
from roger.core.operators import numpy as npx, update, at
from roger.core.surface import calc_parameters_surface_kernel
import roger.lookuptables as lut
import numpy as onp


[docs]class SVATCROPSetup(RogerSetup): """A SVAT model including crop phenology/crop rotation. """ _base_path = Path(__file__).parent _input_dir = _base_path / "input" # custom helper functions def _read_var_from_nc(self, var, path_dir, file, group=None): nc_file = path_dir / file if group: with h5netcdf.File(nc_file, "r", decode_vlen_strings=False) as infile: var_obj = infile.groups[group].variables[var] return npx.array(var_obj) else: with h5netcdf.File(nc_file, "r", decode_vlen_strings=False) as infile: var_obj = infile.variables[var] return npx.array(var_obj) def _get_nitt(self, path_dir, file): nc_file = path_dir / file with h5netcdf.File(nc_file, "r", decode_vlen_strings=False) as infile: var_obj = infile.variables['Time'] return len(onp.array(var_obj)) def _get_runlen(self, path_dir, file): nc_file = path_dir / file with h5netcdf.File(nc_file, "r", decode_vlen_strings=False) as infile: var_obj = infile.variables['dt'] return onp.sum(onp.array(var_obj)) def _get_time_origin(self, path_dir, file): nc_file = path_dir / file with h5netcdf.File(nc_file, "r", decode_vlen_strings=False) as infile: date = infile.variables['Time'].attrs['time_origin'].split(" ")[0] return f"{date} 00:00:00" def _get_ncr(self, path_dir, file): nc_file = path_dir / file with h5netcdf.File(nc_file, "r", decode_vlen_strings=False) as infile: var_obj = infile.variables['year_season'] return len(onp.array(var_obj)) @roger_routine def set_settings(self, state): settings = state.settings settings.identifier = "SVATCROP" # output frequency (in seconds) settings.output_frequency = 86400 # total grid numbers in x- and y-direction settings.nx, settings.ny = 1, 1 # length of simulation (in seconds) settings.runlen = self._get_runlen(self._input_dir, 'forcing.nc') # spatial discretization (in meters) settings.dx = 1 settings.dy = 1 # origin of spatial grid settings.x_origin = 0.0 settings.y_origin = 0.0 # origin of time steps (e.g. 01-01-2023) settings.time_origin = self._get_time_origin(self._input_dir, 'forcing.nc') # enable specific processes settings.enable_groundwater_boundary = False settings.enable_crop_water_stress = True settings.enable_crop_phenology = True settings.enable_crop_rotation = True settings.enable_macropore_lower_boundary_condition = False settings.enable_adaptive_time_stepping = True if settings.enable_crop_rotation: settings.ncrops = 3 settings.ncr = 3 @roger_routine( dist_safe=False, local_variables=[ "x", "y", ], ) def set_grid(self, state): vs = state.variables settings = state.settings # spatial grid dx = allocate(state.dimensions, ("x")) dx = update(dx, at[:], settings.dx) dy = allocate(state.dimensions, ("y")) dy = update(dy, at[:], settings.dy) # distance from origin vs.x = update(vs.x, at[3:-2], settings.x_origin + npx.cumsum(dx[3:-2])) vs.y = update(vs.y, at[3:-2], settings.y_origin + npx.cumsum(dy[3:-2])) @roger_routine( dist_safe=False, local_variables=[ "lut_ilu", "lut_gc", "lut_gcm", "lut_is", "lut_rdlu", "lut_crops", ], ) def set_look_up_tables(self, state): vs = state.variables vs.lut_ilu = update(vs.lut_ilu, at[:, :], lut.ARR_ILU) vs.lut_gc = update(vs.lut_gc, at[:, :], lut.ARR_GC) vs.lut_gcm = update(vs.lut_gcm, at[:, :], lut.ARR_GCM) vs.lut_is = update(vs.lut_is, at[:, :], lut.ARR_IS) vs.lut_rdlu = update(vs.lut_rdlu, at[:, :], lut.ARR_RDLU) vs.lut_crops = update(vs.lut_crops, at[:, :], lut.ARR_CP) @roger_routine def set_topography(self, state): pass @roger_routine( dist_safe=False, local_variables=[ "lu_id", "z_soil", "dmpv", "lmpv", "theta_ac", "theta_ufc", "theta_pwp", "ks", "kf", "crop_type", "z_root", "z_root_crop", ], ) def set_parameters_setup(self, state): vs = state.variables vs.lu_id = update(vs.lu_id, at[2:-2, 2:-2], 8) vs.z_soil = update(vs.z_soil, at[2:-2, 2:-2], 1350) vs.dmpv = update(vs.dmpv, at[2:-2, 2:-2], 50) vs.lmpv = update(vs.lmpv, at[2:-2, 2:-2], 1000) vs.theta_ac = update(vs.theta_ac, at[2:-2, 2:-2], 0.1) vs.theta_ufc = update(vs.theta_ufc, at[2:-2, 2:-2], 0.1) vs.theta_pwp = update(vs.theta_pwp, at[2:-2, 2:-2], 0.2) vs.ks = update(vs.ks, at[2:-2, 2:-2], 5) vs.kf = update(vs.kf, at[2:-2, 2:-2], 2500) vs.crop_type = update(vs.crop_type, at[2:-2, 2:-2, 0], 564) vs.crop_type = update(vs.crop_type, at[2:-2, 2:-2, 1], 539) vs.crop_type = update(vs.crop_type, at[2:-2, 2:-2, 2], 564) vs.z_root = update(vs.z_root, at[2:-2, 2:-2, :2], 200) vs.z_root_crop = update( vs.z_root_crop, at[2:-2, 2:-2, :2, 0], 200 ) @roger_routine def set_parameters(self, state): vs = state.variables if (vs.month[vs.tau] != vs.month[vs.taum1]) & (vs.itt > 1): vs.update(calc_parameters_surface_kernel(state)) @roger_routine def set_initial_conditions_setup(self, state): pass @roger_routine def set_initial_conditions(self, state): vs = state.variables vs.theta_rz = update(vs.theta_rz, at[2:-2, 2:-2, :vs.taup1], 0.3) vs.theta_ss = update(vs.theta_ss, at[2:-2, 2:-2, :vs.taup1], 0.3) vs.update(set_initial_conditions_crops_kernel(state)) @roger_routine def set_boundary_conditions_setup(self, state): pass @roger_routine def set_boundary_conditions(self, state): pass @roger_routine def set_forcing_setup(self, state): pass @roger_routine( dist_safe=False, local_variables=[ "time", "itt_day", "itt_forc", "prec_day", "ta_day", "pet_day", "year", "month", "doy", "itt", "itt_cr", "crop_type", ], ) def set_forcing(self, state): vs = state.variables condt = (vs.time % (24 * 60 * 60) == 0) if condt: vs.itt_day = 0 vs.year = update(vs.year, at[1], self._read_var_from_nc("YEAR", self._input_dir, 'forcing.nc')[vs.itt_forc]) vs.month = update(vs.month, at[1], self._read_var_from_nc("MONTH", self._input_dir, 'forcing.nc')[vs.itt_forc]) vs.doy = update(vs.doy, at[1], self._read_var_from_nc("DOY", self._input_dir, 'forcing.nc')[vs.itt_forc]) vs.prec_day = update(vs.prec_day, at[2:-2, 2:-2, :], self._read_var_from_nc("PREC", self._input_dir, 'forcing.nc')[:, :, vs.itt_forc:vs.itt_forc+6*24]) vs.ta_day = update(vs.ta_day, at[2:-2, 2:-2, :], self._read_var_from_nc("TA", self._input_dir, 'forcing.nc')[:, :, vs.itt_forc:vs.itt_forc+6*24]) vs.pet_day = update(vs.pet_day, at[2:-2, 2:-2, :], self._read_var_from_nc("PET", self._input_dir, 'forcing.nc')[:, :, vs.itt_forc:vs.itt_forc+6*24]) vs.itt_forc = vs.itt_forc + 6 * 24 if (vs.year[1] != vs.year[0]) & (vs.itt > 1): vs.itt_cr = vs.itt_cr + 2 vs.crop_type = update(vs.crop_type, at[2:-2, 2:-2, 0], 564) vs.crop_type = update(vs.crop_type, at[2:-2, 2:-2, 1], 539) vs.crop_type = update(vs.crop_type, at[2:-2, 2:-2, 2], 564) @roger_routine def set_diagnostics(self, state): pass @roger_routine def after_timestep(self, state): vs = state.variables vs.update(after_timestep_kernel(state)) vs.update(after_timestep_crops_kernel(state))
@roger_kernel def set_initial_conditions_crops_kernel(state): vs = state.variables # calculate time since growing t_grow = allocate(state.dimensions, ("x", "y", "crops")) t_grow = update( t_grow, at[2:-2, 2:-2, :], npx.where(vs.z_root_crop[2:-2, 2:-2, vs.taum1, :] > 0, (-1 / vs.root_growth_rate[2:-2, 2:-2, :]) * npx.log(1 / ((vs.z_root_crop[2:-2, 2:-2, vs.taum1, :] / 1000 - vs.z_root_crop_max[2:-2, 2:-2, :] / 1000) * (-1 / (vs.z_root_crop_max[2:-2, 2:-2, :] / 1000 - vs.z_evap[2:-2, 2:-2, npx.newaxis] / 1000)))), 0) ) vs.t_grow_cc = update( vs.t_grow_cc, at[2:-2, 2:-2, :2, :], t_grow[2:-2, 2:-2, npx.newaxis, :] ) vs.t_grow_root = update( vs.t_grow_root, at[2:-2, 2:-2, :2, :], t_grow[2:-2, 2:-2, npx.newaxis, :] ) return KernelOutput( t_grow_cc=vs.t_grow_cc, t_grow_root=vs.t_grow_root, ) @roger_kernel def after_timestep_kernel(state): vs = state.variables vs.ta = update( vs.ta, at[2:-2, 2:-2, vs.taum1], vs.ta[2:-2, 2:-2, vs.tau], ) vs.z_root = update( vs.z_root, at[2:-2, 2:-2, vs.taum1], vs.z_root[2:-2, 2:-2, vs.tau], ) vs.ground_cover = update( vs.ground_cover, at[2:-2, 2:-2, vs.taum1], vs.ground_cover[2:-2, 2:-2, vs.tau], ) vs.S_sur = update( vs.S_sur, at[2:-2, 2:-2, vs.taum1], vs.S_sur[2:-2, 2:-2, vs.tau], ) vs.S_int_top = update( vs.S_int_top, at[2:-2, 2:-2, vs.taum1], vs.S_int_top[2:-2, 2:-2, vs.tau], ) vs.S_int_ground = update( vs.S_int_ground, at[2:-2, 2:-2, vs.taum1], vs.S_int_ground[2:-2, 2:-2, vs.tau], ) vs.S_dep = update( vs.S_dep, at[2:-2, 2:-2, vs.taum1], vs.S_dep[2:-2, 2:-2, vs.tau], ) vs.S_snow = update( vs.S_snow, at[2:-2, 2:-2, vs.taum1], vs.S_snow[2:-2, 2:-2, vs.tau], ) vs.swe = update( vs.swe, at[2:-2, 2:-2, vs.taum1], vs.swe[2:-2, 2:-2, vs.tau], ) vs.S_rz = update( vs.S_rz, at[2:-2, 2:-2, vs.taum1], vs.S_rz[2:-2, 2:-2, vs.tau], ) vs.S_ss = update( vs.S_ss, at[2:-2, 2:-2, vs.taum1], vs.S_ss[2:-2, 2:-2, vs.tau], ) vs.S_s = update( vs.S_s, at[2:-2, 2:-2, vs.taum1], vs.S_s[2:-2, 2:-2, vs.tau], ) vs.S = update( vs.S, at[2:-2, 2:-2, vs.taum1], vs.S[2:-2, 2:-2, vs.tau], ) vs.z_sat = update( vs.z_sat, at[2:-2, 2:-2, vs.taum1], vs.z_sat[2:-2, 2:-2, vs.tau], ) vs.z_wf = update( vs.z_wf, at[2:-2, 2:-2, vs.taum1], vs.z_wf[2:-2, 2:-2, vs.tau], ) vs.z_wf_t0 = update( vs.z_wf_t0, at[2:-2, 2:-2, vs.taum1], vs.z_wf_t0[2:-2, 2:-2, vs.tau], ) vs.z_wf_t1 = update( vs.z_wf_t1, at[2:-2, 2:-2, vs.taum1], vs.z_wf_t1[2:-2, 2:-2, vs.tau], ) vs.y_mp = update( vs.y_mp, at[2:-2, 2:-2, vs.taum1], vs.y_mp[2:-2, 2:-2, vs.tau], ) vs.y_sc = update( vs.y_sc, at[2:-2, 2:-2, vs.taum1], vs.y_sc[2:-2, 2:-2, vs.tau], ) vs.theta_rz = update( vs.theta_rz, at[2:-2, 2:-2, vs.taum1], vs.theta_rz[2:-2, 2:-2, vs.tau], ) vs.theta_ss = update( vs.theta_ss, at[2:-2, 2:-2, vs.taum1], vs.theta_ss[2:-2, 2:-2, vs.tau], ) vs.theta = update( vs.theta, at[2:-2, 2:-2, vs.taum1], vs.theta[2:-2, 2:-2, vs.tau], ) vs.k_rz = update( vs.k_rz, at[2:-2, 2:-2, vs.taum1], vs.k_rz[2:-2, 2:-2, vs.tau], ) vs.k_ss = update( vs.k_ss, at[2:-2, 2:-2, vs.taum1], vs.k_ss[2:-2, 2:-2, vs.tau], ) vs.k = update( vs.k, at[2:-2, 2:-2, vs.taum1], vs.k[2:-2, 2:-2, vs.tau], ) vs.h_rz = update( vs.h_rz, at[2:-2, 2:-2, vs.taum1], vs.h_rz[2:-2, 2:-2, vs.tau], ) vs.h_ss = update( vs.h_ss, at[2:-2, 2:-2, vs.taum1], vs.h_ss[2:-2, 2:-2, vs.tau], ) vs.h = update( vs.h, at[2:-2, 2:-2, vs.taum1], vs.h[2:-2, 2:-2, vs.tau], ) vs.z0 = update( vs.z0, at[2:-2, 2:-2, vs.taum1], vs.z0[2:-2, 2:-2, vs.tau], ) # set to 0 for numerical errors vs.S_fp_rz = update( vs.S_fp_rz, at[2:-2, 2:-2], npx.where((vs.S_fp_rz[2:-2, 2:-2] > -1e-6) & (vs.S_fp_rz[2:-2, 2:-2] < 0), 0, vs.S_fp_rz[2:-2, 2:-2]), ) vs.S_lp_rz = update( vs.S_lp_rz, at[2:-2, 2:-2], npx.where((vs.S_lp_rz[2:-2, 2:-2] > -1e-6) & (vs.S_lp_rz[2:-2, 2:-2] < 0), 0, vs.S_lp_rz[2:-2, 2:-2]), ) vs.S_fp_ss = update( vs.S_fp_ss, at[2:-2, 2:-2], npx.where((vs.S_fp_ss[2:-2, 2:-2] > -1e-6) & (vs.S_fp_ss[2:-2, 2:-2] < 0), 0, vs.S_fp_ss[2:-2, 2:-2]), ) vs.S_lp_ss = update( vs.S_lp_ss, at[2:-2, 2:-2], npx.where((vs.S_lp_ss[2:-2, 2:-2] > -1e-6) & (vs.S_lp_ss[2:-2, 2:-2] < 0), 0, vs.S_lp_ss[2:-2, 2:-2]), ) vs.prec = update( vs.prec, at[2:-2, 2:-2, vs.taum1], vs.prec[2:-2, 2:-2, vs.tau], ) vs.event_id = update( vs.event_id, at[vs.taum1], vs.event_id[vs.tau], ) vs.year = update( vs.year, at[vs.taum1], vs.year[vs.tau], ) vs.month = update( vs.month, at[vs.taum1], vs.month[vs.tau], ) vs.doy = update( vs.doy, at[vs.taum1], vs.doy[vs.tau], ) return KernelOutput( ta=vs.ta, z_root=vs.z_root, ground_cover=vs.ground_cover, S_sur=vs.S_sur, S_int_top=vs.S_int_top, S_int_ground=vs.S_int_ground, S_dep=vs.S_dep, S_snow=vs.S_snow, swe=vs.swe, S_rz=vs.S_rz, S_ss=vs.S_ss, S_s=vs.S_s, S=vs.S, z_sat=vs.z_sat, z_wf=vs.z_wf, z_wf_t0=vs.z_wf_t0, z_wf_t1=vs.z_wf_t1, y_mp=vs.y_mp, y_sc=vs.y_sc, theta_rz=vs.theta_rz, theta_ss=vs.theta_ss, theta=vs.theta, h_rz=vs.h_rz, h_ss=vs.h_ss, h=vs.h, k_rz=vs.k_rz, k_ss=vs.k_ss, k=vs.k, z0=vs.z0, prec=vs.prec, event_id=vs.event_id, year=vs.year, month=vs.month, doy=vs.doy, S_fp_rz=vs.S_fp_rz, S_lp_rz=vs.S_lp_rz, S_fp_ss=vs.S_fp_ss, S_lp_ss=vs.S_lp_ss, ) @roger_kernel def after_timestep_crops_kernel(state): vs = state.variables vs.ta_min = update(vs.ta_min, at[2:-2, 2:-2, vs.taum1], vs.ta_min[2:-2, 2:-2, vs.tau]) vs.ta_max = update(vs.ta_max, at[2:-2, 2:-2, vs.taum1], vs.ta_max[2:-2, 2:-2, vs.tau]) vs.gdd_sum = update(vs.gdd_sum, at[2:-2, 2:-2, vs.taum1, :], vs.gdd_sum[2:-2, 2:-2, vs.tau, :]) vs.t_grow_cc = update(vs.t_grow_cc, at[2:-2, 2:-2, vs.taum1, :], vs.t_grow_cc[2:-2, 2:-2, vs.tau, :]) vs.t_grow_root = update(vs.t_grow_root, at[2:-2, 2:-2, vs.taum1, :], vs.t_grow_root[2:-2, 2:-2, vs.tau, :]) vs.ccc = update(vs.ccc, at[2:-2, 2:-2, vs.taum1, :], vs.ccc[2:-2, 2:-2, vs.tau, :]) vs.z_root_crop = update(vs.z_root_crop, at[2:-2, 2:-2, vs.taum1, :], vs.z_root_crop[2:-2, 2:-2, vs.tau, :]) vs.re_rg_pwp = update(vs.re_rg_pwp, at[2:-2, 2:-2], 0) vs.re_rg = update(vs.re_rg, at[2:-2, 2:-2], 0) vs.re_rl_pwp = update(vs.re_rl_pwp, at[2:-2, 2:-2], 0) vs.re_rl = update(vs.re_rl, at[2:-2, 2:-2], 0) return KernelOutput( ta_min=vs.ta_min, ta_max=vs.ta_max, gdd_sum=vs.gdd_sum, t_grow_cc=vs.t_grow_cc, t_grow_root=vs.t_grow_root, ccc=vs.ccc, z_root_crop=vs.z_root_crop, re_rg_pwp=vs.re_rg_pwp, re_rg=vs.re_rg, re_rl_pwp=vs.re_rl_pwp, re_rl=vs.re_rl, )