def vapor_pressure(tdew): """ Computes vapor pressure Note this should be jointly computed iteratively with ``pet``, ``tdew``, and ``shortwave`` as used in the main ``run`` function. Parameters ---------- tdew: Daily dewpoint temperature Returns ------- vapor_pressure: Estimated vapor pressure """ return svp(tdew)
def relative_humidity(vapor_pressure: pd.Series, temp: pd.Series): """ Calculate relative humidity from vapor pressure and temperature. Parameters ---------- vapor_pressure: A sub-daily timeseries of vapor pressure temp: A sub-daily timeseries of temperature Returns ------- rh: A sub-daily timeseries of relative humidity """ rh = cnst.MAX_PERCENT * cnst.MBAR_PER_BAR * (vapor_pressure / svp(temp)) return rh.where(rh < cnst.MAX_PERCENT, cnst.MAX_PERCENT)
def calc_srad_hum(df: pd.DataFrame, sg: dict, elev: float, params: dict): """ Calculate shortwave, humidity Parameters ---------- df: Dataframe containing daily timeseries elev: Elevation in meters params: A dictionary of parameters from the MetSim object """ def _calc_tfmax(prec, dtr, sm_dtr): """Estimate cloudy day transmittance""" b = cnst.B0 + cnst.B1 * np.exp(-cnst.B2 * sm_dtr) t_fmax = 1.0 - 0.9 * np.exp(-b * np.power(dtr, cnst.C)) inds = np.array(prec > params['sw_prec_thresh']) t_fmax[inds] *= params['rain_scalar'] return t_fmax # Calculate the diurnal temperature range df['t_max'] = np.maximum(df['t_max'], df['t_min']) dtr = df['t_max'] - df['t_min'] df['dtr'] = dtr sm_dtr = df['smoothed_dtr'] df['tfmax'] = _calc_tfmax(df['prec'], dtr, sm_dtr) tdew = df.get('tdew', df['t_min']) pva = df.get('hum', svp(tdew.values)) pa = atm_pres(elev, params['lapse_rate']) yday = df.index.dayofyear - 1 df['dayl'] = sg['daylength'][yday] # Calculation of tdew and shortwave. tdew is iterated on until # it converges sufficiently tdew_old = tdew tdew, pva = sw_hum_iter(df, sg, pa, pva, dtr, params) while (np.sqrt(np.mean((tdew - tdew_old)**2)) > params['tdew_tol']): tdew_old = np.copy(tdew) tdew, pva = sw_hum_iter(df, sg, pa, pva, dtr, params) df['vapor_pressure'] = pva
def relative_humidity(vapor_pressure: np.array, temp: np.array) -> np.array: """ Calculate relative humidity from vapor pressure and temperature. Parameters ---------- vapor_pressure: A sub-daily timeseries of vapor pressure temp: A sub-daily timeseries of temperature Returns ------- rh: A sub-daily timeseries of relative humidity """ rh = (cnst.MAX_PERCENT * cnst.MBAR_PER_BAR * (vapor_pressure / svp(temp))) rh[rh > cnst.MAX_PERCENT] = cnst.MAX_PERCENT return rh
def vapor_pressure(vp_daily: np.array, temp: np.array, t_t_min: np.array, n_out: int, ts: int) -> np.array: """ Calculate vapor pressure. First a linear interpolation of the daily values is calculated. Then this is compared to the saturated vapor pressure calculated using the disaggregated temperature. When the interpolated vapor pressure is greater than the calculated saturated vapor pressure, the interpolation is replaced with the saturation value. Parameters ---------- vp_daily: Daily vapor pressure temp: Sub-daily temperature t_t_min: Timeseries of minimum daily temperature n_out: Number of output observations ts: Timestep to disaggregate down to Returns ------- vp: A sub-daily timeseries of the vapor pressure """ # Linearly interpolate the values interp = scipy.interpolate.interp1d(t_t_min, vp_daily / cnst.MBAR_PER_BAR, bounds_error=False) vp_disagg = interp(ts * np.arange(0, n_out)) # Account for situations where vapor pressure is higher than # saturation point vp_sat = svp(temp) / cnst.MBAR_PER_BAR vp_disagg = np.where(vp_sat < vp_disagg, vp_sat, vp_disagg) return vp_disagg
def sw_hum_iter(df: pd.DataFrame, sg: dict, pa: float, pva: pd.Series, dtr: pd.Series, params: dict): """ Calculated updated values for dewpoint temperature and saturation vapor pressure. Parameters ---------- df: Dataframe containing daily timeseries of cloud cover fraction, tfmax, swe, and shortwave radiation sg: Solar geometry dictionary, calculated with `metsim.physics.solar_geom`. pa: Air pressure in Pascals pva: Vapor presure in Pascals dtr: Daily temperature range params: A dictionary of parameters from a MetSim object Returns ------- (tdew, svp): A tuple of dewpoint temperature and saturation vapor pressure """ tt_max0 = sg['tt_max0'] potrad = sg['potrad'] daylength = sg['daylength'] yday = df.index.dayofyear - 1 t_tmax = np.maximum(tt_max0[yday] + (cnst.ABASE * pva), 0.0001) t_final = t_tmax * df['tfmax'] df['pva'] = pva df['tfinal'] = t_final # Snowpack contribution sc = np.zeros_like(df['swe']) if (params['mtclim_swe_corr']): inds = np.logical_and(df['swe'] > 0., daylength[yday] > 0.) sc[inds] = ((1.32 + 0.096 * df['swe'][inds]) * 1.0e6 / daylength[yday][inds]) sc = np.maximum(sc, 100.) # Calculation of shortwave is split into 2 components: # 1. Radiation from incident light # 2. Influence of snowpack - optionally set by MTCLIM_SWE_CORR df['shortwave'] = potrad[yday] * t_final + sc # Calculate cloud effect if (params['lw_cloud'].upper() == 'CLOUD_DEARDORFF'): df['tskc'] = (1. - df['tfmax']) else: df['tskc'] = np.sqrt((1. - df['tfmax']) / 0.65) # Compute PET using SW radiation estimate, and update Tdew, pva ** pet = calc_pet(df['shortwave'].values, df['t_day'].values, df['dayl'].values, pa) # Calculate ratio (PET/effann_prcp) and correct the dewpoint parray = df['seasonal_prec'] / cnst.MM_PER_CM ratio = pet / parray.where(parray > 8.0, 8.0) df['pet'] = pet * cnst.MM_PER_CM tmink = df['t_min'] + cnst.KELVIN tdew = tmink * (-0.127 + 1.121 * (1.003 - 1.444 * ratio + 12.312 * np.power(ratio, 2) - 32.766 * np.power(ratio, 3)) + 0.0006 * dtr) - cnst.KELVIN return tdew, svp(tdew.values)
def calc_srad_hum(df: pd.DataFrame, sg: dict, elev: float, params: dict, win_type: str = 'boxcar'): """ Calculate shortwave, humidity Parameters ---------- df: Dataframe containing daily timeseries elev: Elevation in meters params: A dictionary of parameters from the MetSim object win_type: (Optional) The method used to calculate the 60 day rolling average of precipitation """ def _calc_tfmax(prec, dtr, sm_dtr): b = cnst.B0 + cnst.B1 * np.exp(-cnst.B2 * sm_dtr) t_fmax = 1.0 - 0.9 * np.exp(-b * np.power(dtr, cnst.C)) inds = np.array(prec > params['sw_prec_thresh']) t_fmax[inds] *= cnst.RAIN_SCALAR return t_fmax # Calculate the diurnal temperature range df['t_max'] = np.maximum(df['t_max'], df['t_min']) dtr = df['t_max'] - df['t_min'] sm_dtr = pd.Series(dtr).rolling(window=30, win_type=win_type, axis=0).mean().fillna(method='bfill') if params['n_days'] <= 30: warn('Timeseries is shorter than rolling mean window, filling ') warn('missing values with unsmoothed data') sm_dtr.fillna(dtr, inplace=True) # Effective annual prec if params['n_days'] <= 90: # Simple scaled method, minimum of 8 cm sum_prec = df['prec'].values.sum() eff_ann_prec = (sum_prec / params['n_days']) * cnst.DAYS_PER_YEAR eff_ann_prec = np.maximum(eff_ann_prec, 8.0) parray = pd.Series(eff_ann_prec, index=df.index) else: # Calculate effective annual prec using 3 month moving window window = pd.Series(np.zeros(params['n_days'] + 90)) window[90:] = df['prec'] # If yeardays at end match with those at beginning we can use # the end of the input to generate the beginning by looping around # If not, just duplicate the first 90 days start_day, end_day = df.index.dayofyear[0], df.index.dayofyear[-1] if ((start_day % 365 == (end_day % 365) + 1) or (start_day % 366 == (end_day % 366) + 1)): window[:90] = df['prec'][-90:] else: window[:90] = df['prec'][:90] parray = cnst.DAYS_PER_YEAR * window.rolling( window=90, win_type=win_type, axis=0).mean()[90:] # Convert to cm parray = parray.where(parray > 80.0, 80.0) / cnst.MM_PER_CM # Doing this way because parray.reindex_like(df) returns all nan parray.index = df.index df['tfmax'] = _calc_tfmax(df['prec'], dtr, sm_dtr) tdew = df.get('tdew', df['t_min']) pva = df.get('hum', svp(tdew)) pa = atm_pres(elev) yday = df.index.dayofyear - 1 df['dayl'] = sg['daylength'][yday] # Calculation of tdew and swrad. tdew is iterated on until # it converges sufficiently tdew_old = tdew tdew, pva = sw_hum_iter(df, sg, pa, pva, parray, dtr, params) while (np.sqrt(np.mean((tdew - tdew_old)**2)) > params['tdew_tol']): tdew_old = np.copy(tdew) tdew, pva = sw_hum_iter(df, sg, pa, pva, parray, dtr, params) df['vapor_pressure'] = pva
def calc_srad_hum_it(df, tol=0.01, win_type='boxcar'): """ TODO """ window = np.zeros(n_days + 90) t_fmax = np.zeros(n_days) df['s_tfmax'] = 0.0 df['t_max'] = np.maximum(df['t_max'], df['t_min']) dtr = df['t_max'] - df['t_min'] sm_dtr = pd.rolling_window(dtr, window=30, freq='D', win_type=win_type).fillna(method='bfill') if n_days <= 30: print('Timeseries is shorter than rolling mean window, filling ') print('missing values with unsmoothed data') sm_dtr.fillna(dtr, inplace=True) sum_precip = df['s_precip'].values.sum() ann_precip = (sum_precip / n_days) * consts['DAYS_PER_YEAR'] if ann_precip == 0.0: ann_precip = 1.0 if n_days <= 90: sum_precip = df['s_precip'].values.sum() eff_ann_precip = (sum_precip / n_days) * consts['DAYS_PER_YEAR'] eff_ann_precip = np.maximum(eff_ann_precip, 8.0) parray = eff_ann_precip else: parray = np.zeros(n_days) start_yday = df['day_of_year'][0] end_yday = df['day_of_year'][-1] if start_yday != 1: if end_yday == start_yday - 1: isloop = True else: if end_yday == 365 or end_yday == 366: isloop = True if isloop: for i in range(90): window[i] = df['s_precip'][n_days - 90 + i] else: for i in range(90): window[i] = df['s_precip'][i] window[90:] = df['s_precip'] for i in range(n_days): sum_precip = 0.0 for j in range(90): sum_precip += window[i + j] sum_precip = (sum_precip / 90.) * consts['DAYS_PER_YEAR'] sum_precip = np.maximum(sum_precip, 8.0) parray[i] = sum_precip # FIXME: This is still bad form tt_max0, flat_potrad, slope_potrad, daylength, tiny_rad_fract = calc_solar_geom( ) # NOTE: Be careful with this one! disaggregate.tiny_rad_fract = tiny_rad_fract avg_horizon = (params['site_east_horiz'] + params['site_west_horiz']) / 2.0 horizon_scalar = 1.0 - np.sin(avg_horizon * consts['RADPERDEG']) if (params['site_slope'] > avg_horizon): slope_excess = params['site_slope'] - avg_horizon else: slope_excess = 0. if (2.0 * avg_horizon > 180.): slope_scalar = 0. else: slope_scalar = np.clip( 1. - (slope_excess / (180.0 - 2.0 * avg_horizon)), 0, None) sky_prop = horizon_scalar * slope_scalar b = params['B0'] + params['B1'] * np.exp(-params['B2'] * sm_dtr) t_fmax = 1.0 - 0.9 * np.exp(-b * np.power(dtr, params['C'])) inds = np.nonzero(df['precip'] > options['SW_PREC_THRESH'])[0] t_fmax[inds] *= params['RAIN_SCALAR'] df['s_tfmax'] = t_fmax tdew = df.get('tdew', df['s_t_min']) pva = df['s_hum'] if 's_hum' in df else svp(tdew) pa = atm_pres(params['site_elev']) yday = df['day_of_year'] - 1 df['s_dayl'] = daylength[yday] tdew_save = tdew pva_save = pva # FIXME: This function has lots of inputs and outputs tdew, pet = _compute_srad_humidity_onetime(tdew, pva, tt_max0, flat_potrad, slope_potrad, sky_prop, daylength, parray, pa, dtr, df) sum_pet = pet.values.sum() ann_pet = (sum_pet / n_days) * consts['DAYS_PER_YEAR'] # FIXME: Another really long conditional if (('tdew' in df) or ('s_hum' in df) or (options['VP_ITER'].upper() == 'VP_ITER_ANNUAL' and ann_pet / ann_precip >= 2.5)): tdew = tdew_save[:] pva = pva_save[:] # FIXME: Another really long conditional #if (options['VP_ITER'].upper() == 'VP_ITER_ALWAYS' or # (options['VP_ITER'].upper() == 'VP_ITER_ANNUAL' and # ann_pet / ann_precip >= 2.5) or # options['VP_ITER'].upper() == 'VP_ITER_CONVERGE'): # if (options['VP_ITER'].upper() == 'VP_ITER_CONVERGE'): # max_iter = 100 # else: # max_iter = 2 #else: # max_iter = 1 #FIXME Still want to reduce the number of args here #FIXME This also takes up the majority of the mtclim runtime rmse_tdew = tol + 1 #f = lambda x : rmse(_compute_srad_humidity_onetime(x, pva, tt_max0, flat_potrad, # slope_potrad, sky_prop, daylength, # parray, pa, dtr, df)[0], tdew) def f(x): tdew_calc = _compute_srad_humidity_onetime(x, pva, tt_max0, flat_potrad, slope_potrad, sky_prop, daylength, parray, pa, dtr, df)[0] print(tdew_calc - tdew) err = rmse(tdew_calc, tdew) print(err) return err res = minimize(f, tdew, tol=rmse_tdew) tdew = res.x pva = svp(tdew) if 's_hum' not in df: df['s_hum'] = pva pvs = svp(df['s_t_day']) vpd = pvs - pva df['s_vpd'] = np.maximum(vpd, 0.)
def vapor_pressure(tdew): return svp(tdew)