|
| 1 | +#!/usr/bin/env python |
| 2 | + |
| 3 | +import sys |
| 4 | + |
| 5 | +import xarray as xr |
| 6 | +import numpy as np |
| 7 | +import matplotlib.pyplot as plt |
| 8 | + |
| 9 | +from scipy.optimize import curve_fit |
| 10 | +from datetime import date |
| 11 | + |
| 12 | +def piecewise_exponential_cumulative(ages, k1, k2, m=94): |
| 13 | + ### this function calculates a piecewise exponential function, whose integral equals one |
| 14 | + # k1 is the decay rate of first exponential |
| 15 | + # k2 is decay rate of secodn exponetial |
| 16 | + # m is the boundary between k1 and k2 |
| 17 | + # this function represents the steady-state cumulative age distribution for patch ages given harvest rates for |
| 18 | + # young and mature forests (k1 and k2) where m is the age that distinguishes the two age types |
| 19 | + n_age_bins = len(ages) |
| 20 | + |
| 21 | + # calculate the frequency of youngest age bin |
| 22 | + a = 1./((1./k1)+np.exp(-k1*m)*((-1/k1)+(1/k2))) |
| 23 | + |
| 24 | + # calculate the first piecewise exponential set |
| 25 | + vals = a * np.exp(-k1*ages) |
| 26 | + ##print(len(vals[m:])) |
| 27 | + ##print(len(np.arange(n_age_bins-m))) |
| 28 | + ##print(len(ages.isel(slice(m-n_age_bins, None)))) |
| 29 | + |
| 30 | + # calculate the second piecewise exponential set, beginning with the last element ofthe first set |
| 31 | + vals[m:] = vals[m]*np.exp(-k2*np.arange(n_age_bins-m)) |
| 32 | + |
| 33 | + # rescale last age bin to account for all of the truncated ages beyond |
| 34 | + #vals[-1] = vals[-1]/k2 |
| 35 | + |
| 36 | + vals_cumulative = vals.cumsum() |
| 37 | + return(vals_cumulative) |
| 38 | + |
| 39 | +# this is the year that LUH2 transient data starts |
| 40 | +year_luh2_start = 850 |
| 41 | + |
| 42 | +if len(sys.argv) < 4: |
| 43 | + print("Three command-line arguments needed: ") |
| 44 | + print("first is the path to the LUH data file as processed for FATES. ") |
| 45 | + print("second is the year for which to calculate steady-state.") |
| 46 | + print("third is a grid name (used only for output filename, e.g. ne30 or 4x5)") |
| 47 | + sys.exit(4) |
| 48 | + |
| 49 | +#fin_luh2 = xr.open_dataset('LUH2_states_transitions_management.timeseries_4x5_hist_simyr1650-2015_c240216.nc') |
| 50 | +fin_luh2 = xr.open_dataset(sys.argv[1]) |
| 51 | + |
| 52 | +nages = 300 |
| 53 | +ntime_total = len(fin_luh2.time) |
| 54 | + |
| 55 | +# calculate the year that you want to calculate a steady-state logging rate for |
| 56 | +year_to_calc_steadystate = sys.argv[2] |
| 57 | +if (year_to_calc_steadystate < year_luh2_start) or (year_to_calc_steadystate > year_luh2_start + ntime_total): |
| 58 | + print("year_to_calc_steadystate must be within the time span of data file") |
| 59 | + print(year_to_calc_steadystate) |
| 60 | + sys.exit(4) |
| 61 | + |
| 62 | +gridname = sys.argv[3] |
| 63 | + |
| 64 | + |
| 65 | +print(fin_luh2.data_vars) |
| 66 | +if "lon" in fin_luh2.coords or "lsmlon" in fin_luh2.coords: |
| 67 | + reg_grid = True |
| 68 | + IM = len(fin_luh2.lon) |
| 69 | + JM = len(fin_luh2.lat) |
| 70 | + if "lsmlon" in fin_luh2.dims: |
| 71 | + latname = "lsmlat" |
| 72 | + lonname = "lsmlon" |
| 73 | + lon_coord = fin_luh2.lsmlon |
| 74 | + lat_coord = fin_luh2.lsmlat |
| 75 | + else: |
| 76 | + latname = "lat" |
| 77 | + lonname = "lon" |
| 78 | + lon_coord = fin_luh2.lon |
| 79 | + lat_coord = fin_luh2.lat |
| 80 | +else: |
| 81 | + lsmlon = False |
| 82 | + reg_grid = False |
| 83 | + IM = len(fin_luh2.lndgrid) |
| 84 | + |
| 85 | +ages = xr.DataArray(np.arange(nages), dims=['age'], coords=[np.arange(nages)]) |
| 86 | + |
| 87 | +age_mature=94 |
| 88 | + |
| 89 | +#print(fin_luh2.dims) |
| 90 | + |
| 91 | +area_prim = fin_luh2.primf + fin_luh2.primn |
| 92 | + |
| 93 | +rate_newsec_creation = fin_luh2.primf_harv + fin_luh2.primn_harv + fin_luh2.primf_to_secdn + fin_luh2.primn_to_secdf + fin_luh2.urban_to_secdf + fin_luh2.urban_to_secdn + fin_luh2.c3ann_to_secdf + fin_luh2.c3ann_to_secdn + fin_luh2.c4ann_to_secdf + fin_luh2.c4ann_to_secdn + fin_luh2.c3per_to_secdf + fin_luh2.c3per_to_secdn + fin_luh2.c4per_to_secdf + fin_luh2.c4per_to_secdn + fin_luh2.c3nfx_to_secdf + fin_luh2.c3nfx_to_secdn + fin_luh2.pastr_to_secdf + fin_luh2.pastr_to_secdn + fin_luh2.range_to_secdf + fin_luh2.range_to_secdn |
| 94 | + |
| 95 | +rate_sec_loss_transitions = fin_luh2.secdf_to_urban + fin_luh2.secdf_to_c3ann + fin_luh2.secdf_to_c4ann + fin_luh2.secdf_to_c3per + fin_luh2.secdf_to_c4per + fin_luh2.secdf_to_c3nfx + fin_luh2.secdf_to_pastr + fin_luh2.secdf_to_range + fin_luh2.secdn_to_urban + fin_luh2.secdn_to_c3ann + fin_luh2.secdn_to_c4ann + fin_luh2.secdn_to_c3per + fin_luh2.secdn_to_c4per + fin_luh2.secdn_to_c3nfx + fin_luh2.secdn_to_pastr + fin_luh2.secdn_to_range |
| 96 | + |
| 97 | +secmf_harv = fin_luh2.secmf_harv |
| 98 | +secyf_harv = fin_luh2.secyf_harv |
| 99 | +secnf_harv = fin_luh2.secnf_harv |
| 100 | + |
| 101 | +sectot = fin_luh2.secdf + fin_luh2.secdn |
| 102 | +#print(sectot) |
| 103 | +#sys.exit(4) |
| 104 | + |
| 105 | +if reg_grid: |
| 106 | + ### initialize the secondary forest age as having all zero age at start of dataset |
| 107 | + age_dist = xr.DataArray(np.zeros((ntime_total,nages,JM,IM)), dims=['time','age',latname,lonname], coords=[fin_luh2.time, ages, lat_coord, lon_coord]) |
| 108 | + age_dist[0,0,:,:] = sectot.isel(time=0) |
| 109 | + |
| 110 | + generic_zeros_array = xr.DataArray(np.zeros((ntime_total,JM,IM)), dims=['time',latname,lonname], coords=[fin_luh2.time, lat_coord, lon_coord]) |
| 111 | +else: |
| 112 | + ### initialize the secondary forest age as having all zero age at start of dataset |
| 113 | + age_dist = xr.DataArray(np.zeros((ntime_total,nages, IM)), dims=['time','age','lndgrid'], coords=[fin_luh2.time, ages, fin_luh2.lndgrid]) |
| 114 | + age_dist[0,0,:] = sectot.isel(time=0) |
| 115 | + generic_zeros_array = xr.DataArray(np.zeros((ntime_total, IM)), dims=['time','lndgrid'], coords=[fin_luh2.time, fin_luh2.lndgrid]) |
| 116 | + |
| 117 | +secy_dist = generic_zeros_array.copy()#xr.DataArray(np.zeros((ntime_total,JM,IM)), dims=['time',latname,lonname], coords=[fin_luh2.time, fin_luh2.lsmlat, fin_luh2.lsmlon]) |
| 118 | +secm_dist = generic_zeros_array.copy()#xr.DataArray(np.zeros((ntime_total,JM,IM)), dims=['time',latname,lonname], coords=[fin_luh2.time, fin_luh2.lsmlat, fin_luh2.lsmlon]) |
| 119 | + |
| 120 | +k_secloss_dist = generic_zeros_array.copy()#xr.DataArray(np.zeros((ntime_total,JM,IM)), dims=['time',latname,lonname], coords=[fin_luh2.time, fin_luh2.lsmlat, fin_luh2.lsmlon]) |
| 121 | +k_secyhar_dist = generic_zeros_array.copy()#xr.DataArray(np.zeros((ntime_total,JM,IM)), dims=['time',latname,lonname], coords=[fin_luh2.time, fin_luh2.lsmlat, fin_luh2.lsmlon]) |
| 122 | +k_secthar_dist = generic_zeros_array.copy()#xr.DataArray(np.zeros((ntime_total,JM,IM)), dims=['time',latname,lonname], coords=[fin_luh2.time, fin_luh2.lsmlat, fin_luh2.lsmlon]) |
| 123 | +k_secmhar_dist = generic_zeros_array.copy()#xr.DataArray(np.zeros((ntime_total,JM,IM)), dims=['time',latname,lonname], coords=[fin_luh2.time, fin_luh2.lsmlat, fin_luh2.lsmlon]) |
| 124 | + |
| 125 | + |
| 126 | +for t in range(ntime_total-1): |
| 127 | + if t%50 == 0: |
| 128 | + print('starting year ',t) |
| 129 | + secy = age_dist.isel(time=t).isel(age=slice(0,age_mature)).sum(dim='age') |
| 130 | + secm = age_dist.isel(time=t).isel(age=slice(age_mature,None)).sum(dim='age') |
| 131 | + if reg_grid: |
| 132 | + age_dist[t+1,0,:,:] = rate_newsec_creation.isel(time=t) |
| 133 | + else: |
| 134 | + age_dist[t+1,0,:] = rate_newsec_creation.isel(time=t) |
| 135 | + k_secloss = np.minimum(1.,np.nan_to_num(rate_sec_loss_transitions.isel(time=t)/sectot.isel(time=t))) |
| 136 | + k_secyhar = np.minimum(1.,np.nan_to_num(secyf_harv.isel(time=t)/secy)) |
| 137 | + k_secthar = np.minimum(1.,np.nan_to_num(secnf_harv.isel(time=t)/sectot.isel(time=t))) |
| 138 | + k_secmhar = np.minimum(1.,np.nan_to_num(secmf_harv.isel(time=t)/secm)) |
| 139 | + |
| 140 | + for age in range(age_mature): |
| 141 | + age_dist[t+1,age+1] = age_dist[t,age] * (1. - np.minimum(1.,(k_secloss + k_secyhar + k_secthar))) |
| 142 | + age_dist[t+1,0] = age_dist[t+1,0] + age_dist[t,age] * np.minimum(1.,(k_secyhar + k_secthar)) |
| 143 | + for age in range(age_mature,nages-2): |
| 144 | + age_dist[t+1,age+1] = age_dist[t,age] * (1.- np.minimum(1.,(k_secloss + k_secmhar + k_secthar))) |
| 145 | + age_dist[t+1,0] = age_dist[t+1,0] + age_dist[t,age] * np.minimum(1.,(k_secmhar + k_secthar)) |
| 146 | + age_dist[t+1,nages-1] = (age_dist[t,nages-1] + age_dist[t,nages-2]) * (1.- np.minimum(1.,(k_secloss + k_secmhar + k_secthar))) |
| 147 | + age_dist[t+1,0] = age_dist[t+1,0] + (age_dist[t,nages-1] + age_dist[t,nages-2]) * np.minimum(1.,(k_secmhar + k_secthar)) |
| 148 | + |
| 149 | + if reg_grid: |
| 150 | + secy_dist[t,:,:] = secy |
| 151 | + secm_dist[t,:,:] = secm |
| 152 | + k_secloss_dist[t,:,:] = k_secloss |
| 153 | + k_secyhar_dist[t,:,:] = k_secyhar |
| 154 | + k_secthar_dist[t,:,:] = k_secthar |
| 155 | + k_secmhar_dist[t,:,:] = k_secmhar |
| 156 | + else: |
| 157 | + secy_dist[t,:] = secy |
| 158 | + secm_dist[t,:] = secm |
| 159 | + k_secloss_dist[t,:] = k_secloss |
| 160 | + k_secyhar_dist[t,:] = k_secyhar |
| 161 | + k_secthar_dist[t,:] = k_secthar |
| 162 | + k_secmhar_dist[t,:] = k_secmhar |
| 163 | + |
| 164 | + |
| 165 | +age_dist_cum = age_dist.cumsum(dim='age') |
| 166 | + |
| 167 | + |
| 168 | +mean_age = (age_dist * ages).sum(dim='age') / age_dist.sum(dim='age') |
| 169 | + |
| 170 | + |
| 171 | +age_dist_cum_norm = age_dist_cum / sectot |
| 172 | + |
| 173 | + |
| 174 | +median_secondary_age = np.abs(age_dist_cum_norm.fillna(0.) - 0.5).argmin(dim='age', skipna=True) |
| 175 | +median_secondary_age = (median_secondary_age * sectot / sectot) |
| 176 | + |
| 177 | + |
| 178 | +if reg_grid: |
| 179 | + generic_nan_array = xr.DataArray(np.nan * np.ones([JM,IM]), dims=[latname,lonname], coords=[fin_luh2.lat, fin_luh2.lon]) |
| 180 | +else: |
| 181 | + generic_nan_array = xr.DataArray(np.nan * np.ones([IM]), dims=['lndgrid'], coords=[fin_luh2.lndgrid]) |
| 182 | + |
| 183 | +sec_young_harvestrate_rel = generic_nan_array.copy()#xr.DataArray(np.nan * np.ones([JM,IM]), dims=[latname,lonname], coords=[fin_luh2.lat, fin_luh2.lon]) |
| 184 | +sec_mature_harvestrate_rel = generic_nan_array.copy()#xr.DataArray(np.nan * np.ones([JM,IM]), dims=[latname,lonname], coords=[fin_luh2.lat, fin_luh2.lon]) |
| 185 | + |
| 186 | + |
| 187 | +time = year_to_calc_steadystate - year_luh2_start |
| 188 | +n_age_max = 150 |
| 189 | + |
| 190 | +x = ages.isel(age=slice(0,n_age_max)).data |
| 191 | +fails = generic_nan_array.copy()#xr.DataArray(np.nan * np.ones([JM,IM]), dims=[latname,lonname], coords=[fin_luh2.lat, fin_luh2.lon]) |
| 192 | +if reg_grid: |
| 193 | + for i in range(IM): |
| 194 | + for j in range(JM): |
| 195 | + if latname == "lsmlat": |
| 196 | + if sectot.sel(lsmlat=j,lsmlon=i,time=time) > 0.: |
| 197 | + y = age_dist_cum_norm.isel(lsmlat=j,lsmlon=i,time=time,age=slice(0,n_age_max)).data |
| 198 | + else: |
| 199 | + continue |
| 200 | + else: |
| 201 | + if sectot.isel(lat=j,lon=i,time=time) > 0.: |
| 202 | + y = age_dist_cum_norm.isel(lat=j,lon=i,time=time,age=slice(0,n_age_max)).data |
| 203 | + else: |
| 204 | + continue |
| 205 | + try: |
| 206 | + params = curve_fit(piecewise_exponential_cumulative, x, y, p0=[0.01,0.01], bounds=[0,1]) |
| 207 | + #print(params) |
| 208 | + sec_young_harvestrate_rel[j,i] = params[0][0] |
| 209 | + sec_mature_harvestrate_rel[j,i] = params[0][1] |
| 210 | + except: |
| 211 | + fails[j,i] = 1. |
| 212 | + sec_young_harvestrate_rel[j,i] = 0.05 ### set to some nominal value |
| 213 | + sec_mature_harvestrate_rel[j,i] = 0.05 ### set to some nominal value |
| 214 | +else: |
| 215 | + for i in range(IM): |
| 216 | + if sectot.sel(lndgrid=i,time=time) > 0.: |
| 217 | + y = age_dist_cum_norm.isel(lndgrid=i,time=time,age=slice(0,n_age_max)).data |
| 218 | + try: |
| 219 | + params = curve_fit(piecewise_exponential_cumulative, x, y, p0=[0.01,0.01], bounds=[0,1]) |
| 220 | + #print(params) |
| 221 | + sec_young_harvestrate_rel[i] = params[0][0] |
| 222 | + sec_mature_harvestrate_rel[i] = params[0][1] |
| 223 | + except: |
| 224 | + fails[i] = 1. |
| 225 | + sec_young_harvestrate_rel[i] = 0.05 ### set to some nominal value |
| 226 | + sec_mature_harvestrate_rel[i] = 0.05 ### set to some nominal value |
| 227 | + |
| 228 | + |
| 229 | + |
| 230 | +sec_young_harvestrate_rel.plot() |
| 231 | + |
| 232 | + |
| 233 | +sec_mature_harvestrate_rel.plot() |
| 234 | + |
| 235 | + |
| 236 | +fails.plot() |
| 237 | + |
| 238 | + |
| 239 | +# replace nans with zeros in harvest rates |
| 240 | +sec_young_harvestrate_rel = sec_young_harvestrate_rel.fillna(0.) |
| 241 | +sec_mature_harvestrate_rel = sec_mature_harvestrate_rel.fillna(0.) |
| 242 | + |
| 243 | + |
| 244 | +n_ts_out = 500 |
| 245 | +fout = fin_luh2.sel(time=[time]*n_ts_out) |
| 246 | + |
| 247 | + |
| 248 | +# first zero all transitions |
| 249 | +varlist = list(fout.variables) |
| 250 | +matchers = ['_to_','_harv','_bioh'] |
| 251 | +matching_vars = [s for s in varlist if any(xs in s for xs in matchers)] |
| 252 | +#matching_vars |
| 253 | + |
| 254 | +# Loop over variables governing transition rates and zero them |
| 255 | +for var in matching_vars: |
| 256 | + # Access the variable data |
| 257 | + fout[var] = fout[var] * 0. |
| 258 | + |
| 259 | + |
| 260 | +# now rewrite the secondary harvest rates based on the fits above |
| 261 | +if reg_grid: |
| 262 | + for i_ts in range(n_ts_out): |
| 263 | + fout.secyf_harv[i_ts,:,:] = sec_young_harvestrate_rel * (fout.secdf.isel(time=0) + fout.secdn.isel(time=0)).data |
| 264 | + fout.secmf_harv[i_ts,:,:] = sec_mature_harvestrate_rel * (fout.secdf.isel(time=0) + fout.secdn.isel(time=0)).data |
| 265 | +else: |
| 266 | + for i_ts in range(n_ts_out): |
| 267 | + fout.secyf_harv[i_ts,:] = sec_young_harvestrate_rel * (fout.secdf.isel(time=0) + fout.secdn.isel(time=0)).data |
| 268 | + fout.secmf_harv[i_ts,:] = sec_mature_harvestrate_rel * (fout.secdf.isel(time=0) + fout.secdn.isel(time=0)).data |
| 269 | + |
| 270 | +fout['time'] = np.arange(n_ts_out) |
| 271 | +fout['YEAR'][:] = np.arange(n_ts_out) |
| 272 | + |
| 273 | +fout.to_netcdf('LUH2_states_transitions_management.timeseries_'+gridname+'_hist_steadystate_'+str(year_to_calc_steadystate)+'_'+str(date.today())+'.nc') |
| 274 | + |
| 275 | + |
0 commit comments