def calc_net(self, albedo_vis, albedo_ir): """ Calculate the net radiation using the vegetation adjusted radiation. Sets :py:attr:`net_solar`. Args: albedo_vis: numpy array for visible albedo, from :mod:`smrf.distribute.albedo.Albedo.albedo_vis` albedo_ir: numpy array for infrared albedo, from :mod:`smrf.distribute.albedo.Albedo.albedo_ir` """ self._logger.debug('Calculating net radiation') # calculate net visible vv_n = (self.vis_beam + self.vis_diffuse) * (1 - albedo_vis) vv_n = utils.set_min_max(vv_n, self.min, self.max) # calculate net ir vir_n = (self.ir_beam + self.ir_diffuse) * (1 - albedo_ir) vir_n = utils.set_min_max(vir_n, self.min, self.max) # calculate total net self.net_solar = vv_n + vir_n self.net_solar = utils.set_min_max(self.net_solar, self.min, self.max)
def distribute(self, current_time_step, cosz, storm_day): """ Distribute air temperature given a Panda's dataframe for a single time step. Calls :mod:`smrf.distribute.image_data.image_data._distribute`. Args: current_time_step: Current time step in datetime object cosz: numpy array of the illumination angle for the current time step storm_day: numpy array of the decimal days since it last snowed at a grid cell """ self._logger.debug('%s Distributing albedo' % current_time_step) # only need to calculate albedo if the sun is up if cosz is not None: alb_v, alb_ir = radiation.albedo(storm_day, cosz, self.config['grain_size'], self.config['max_grain'], self.config['dirt']) # Perform litter decay if self.config['decay_method'] == 'date_method': alb_v_d, alb_ir_d = radiation.decay_alb_power(self.veg, self.veg_type, self.config['date_method_start_decay'], self.config['date_method_end_decay'], current_time_step, self.config['date_method_decay_power'], alb_v, alb_ir) alb_v = alb_v_d alb_ir = alb_ir_d elif self.config['decay_method'] == 'hardy2000': alb_v_d, alb_ir_d = radiation.decay_alb_hardy(self.litter, self.veg_type, storm_day, alb_v, alb_ir) alb_v = alb_v_d alb_ir = alb_ir_d self.albedo_vis = utils.set_min_max(alb_v, self.min, self.max) self.albedo_ir = utils.set_min_max(alb_ir, self.min, self.max) else: self.albedo_vis = np.zeros(storm_day.shape) self.albedo_ir = np.zeros(storm_day.shape)
def distribute(self, data, ta): """ Distribute air temperature given a Panda's dataframe for a single time step. Calls :mod:`smrf.distribute.image_data.image_data._distribute`. The following steps are performed when distributing vapor pressure: 1. Distribute the point vapor pressure measurements 2. Calculate dew point temperature using :mod:`smrf.envphys.core.envphys_c.cdewpt` 3. Adjust dew point values to not exceed the air temperature Args: data: Pandas dataframe for a single time step from precip ta: air temperature numpy array that will be used for calculating dew point temperature """ self._logger.debug('%s -- Distributing vapor_pressure' % data.name) # calculate the vapor pressure self._distribute(data) # set the limits self.vapor_pressure = utils.set_min_max(self.vapor_pressure, self.min, self.max) # calculate the dew point self._logger.debug('%s -- Calculating dew point' % data.name) # use the core_c to calculate the dew point dpt = np.zeros_like(self.vapor_pressure, dtype=np.float64) envphys_c.cdewpt(self.vapor_pressure, dpt, self.config['dew_point_tolerance'], self.config['dew_point_nthreads']) # find where dpt > ta ind = dpt >= ta if (np.sum(ind) > 0): # or np.sum(indm) > 0): dpt[ind] = ta[ind] - 0.2 self.dew_point = dpt # calculate wet bulb temperature if self.precip_temp_method == 'wet_bulb': # initialize timestep wet_bulb wet_bulb = np.zeros_like(self.vapor_pressure, dtype=np.float64) # calculate wet_bulb envphys_c.cwbt(ta, dpt, self.dem, wet_bulb, self.config['dew_point_tolerance'], self.config['dew_point_nthreads']) # # store last time step of wet_bulb # self.wet_bulb_old = wet_bulb.copy() # store in precip temp for use in precip self.precip_temp = wet_bulb else: self.precip_temp = dpt
def distribute_for_susong1999(self, data, ppt_temp, time, mask=None): """ Docs for susong1999 """ if data.sum() > 0: # distribute data and set the min/max self._distribute(data) # see if the mass threshold has been passed self.precip = utils.set_min_max(self.precip, self.min, self.max) # determine the precip phase and den snow_den, perc_snow = snow.calc_phase_and_density( ppt_temp, self.precip, nasde_model=self.nasde_model) # determine the time since last storm stormDays, stormPrecip = storms.time_since_storm( self.precip, perc_snow, time_step=self.time_step / 60 / 24, mass=self.ppt_threshold, time=self.time_to_end_storm, stormDays=self.storm_days, stormPrecip=self.storm_total) # save the model state self.percent_snow = perc_snow self.snow_density = snow_den self.storm_days = stormDays self.storm_total = stormPrecip else: self.storm_days += self.time_step / 60 / 24 # make everything else zeros self.precip = np.zeros(self.storm_days.shape) self.percent_snow = np.zeros(self.storm_days.shape) self.snow_density = np.zeros(self.storm_days.shape) # day of last storm, this will be used in albedo self.last_storm_day = utils.water_day(data.name)[0] - \ self.storm_days - 0.001 # get the time since most recent storm if mask is not None: self.last_storm_day_basin = np.max(mask * self.last_storm_day) else: self.last_storm_day_basin = np.max(self.last_storm_day)
def distribute(self, data): """ Distribute air temperature given a Panda's dataframe for a single time step. Calls :mod:`smrf.distribute.image_data.image_data._distribute`. Args: data: Pandas dataframe for a single time step from air_temp """ self._logger.debug('{} Distributing air_temp'.format(data.name)) self._distribute(data) self.air_temp = utils.set_min_max(self.air_temp, self.min, self.max)
def distribute(self, data): """ Distribute cloud factor given a Panda's dataframe for a single time step. Calls :mod:`smrf.distribute.image_data.image_data._distribute`. Args: data: Pandas dataframe for a single time step from cloud_factor """ self._logger.debug('{} Distributing cloud_factor'.format(data.name)) self._distribute(data) self.cloud_factor = utils.set_min_max(self.cloud_factor, self.min, self.max)
def distribute(self, data_speed, data_direction, t): """ Distribute given a Panda's dataframe for a single time step. Calls :mod:`smrf.distribute.image_data.image_data._distribute` for the `wind_model` chosen. Args: data_speed: Pandas dataframe for single time step from wind_speed data_direction: Pandas dataframe for single time step from wind_direction t: time stamp """ self._logger.debug( '{} Distributing wind_direction and wind_speed'.format( data_speed.name)) if self.model_type(self.INTERP): self._distribute(data_speed, other_attribute='wind_speed') # wind direction components at the station self.u_direction = np.sin(data_direction * np.pi / 180) self.v_direction = np.cos(data_direction * np.pi / 180) # distribute u_direction and v_direction self._distribute(self.u_direction, other_attribute='u_direction_distributed') self._distribute(self.v_direction, other_attribute='v_direction_distributed') # combine u and v to azimuth az = np.arctan2(self.u_direction_distributed, self.v_direction_distributed) * 180 / np.pi az[az < 0] = az[az < 0] + 360 self.wind_direction = az else: self.wind_model.distribute(data_speed, data_direction) for v in self.output_variables.keys(): setattr(self, v, getattr(self.wind_model, v)) # set min and max self.wind_speed = utils.set_min_max(self.wind_speed, self.wind_model.min, self.wind_model.max)
def distribute_for_susong1999(self, data, ppt_temp, time): """Susong 1999 estimates percent snow and snow density based on Susong et al, (1999) :cite:`Susong&al:1999`. Args: data (pd.DataFrame): Precipitation mass data ppt_temp (pd.DataFrame): Precipitation temperature data time : Unused """ if data.sum() > 0: self._distribute(data) self.precip = utils.set_min_max(self.precip, self.min, self.max) # determine the precip phase and den snow_den, perc_snow = Snow.phase_and_density( ppt_temp, self.precip, nasde_model=self.nasde_model) # determine the time since last storm stormDays, stormPrecip = storms.time_since_storm( self.precip, perc_snow, storm_days=self.storm_days, storm_precip=self.storm_total, time_step=self.time_step/60/24, mass_threshold=self.ppt_threshold, ) # save the model state self.percent_snow = perc_snow self.snow_density = snow_den self.storm_days = stormDays self.storm_total = stormPrecip else: self.storm_days += self.time_step/60/24 # make everything else zeros self.precip = np.zeros(self.storm_days.shape) self.percent_snow = np.zeros(self.storm_days.shape) self.snow_density = np.zeros(self.storm_days.shape)
def test_wind_ninja(self): config = self.base_config.cfg topo, wn, g_vel, g_ang = self.setup_wind_ninja(config) # The x values are ascending self.assertTrue(np.all(np.diff(wn.windninja_x) > 0)) # The y values are descnding self.assertTrue(np.all(np.diff(wn.windninja_y) < 0)) # compare against gold g_vel = utils.set_min_max(g_vel, wn.config['min'], wn.config['max']) # check against gold # The two are not exactly the same as there is some float # precision error with netcdf n = nc.Dataset(self.gold_dir.joinpath('wind_speed.nc')) np.testing.assert_allclose(n.variables['wind_speed'][0, :], g_vel) n.close() n = nc.Dataset(self.gold_dir.joinpath('wind_direction.nc')) np.testing.assert_allclose(n.variables['wind_direction'][0, :], g_ang) n.close()
def simulateWind(self, data_speed): """ Calculate the simulated wind speed at each cell from flatwind and the distributed directions. Each cell's maxus value is pulled from the maxus library based on the distributed wind direction. The cell's maxus is further adjusted based on the vegetation type and the factors provided in the [wind] section of the configuration file. Args: data_speed: Pandas dataframe for a single time step of wind speed to make the pixel locations same as the measured values """ # combine u and v to azimuth az = np.arctan2(self.u_direction_distributed, self.v_direction_distributed)*180/np.pi az[az < 0] = az[az < 0] + 360 dir_round_cell = np.ceil((az - self.nstep/2) / self.nstep) * self.nstep dir_round_cell[dir_round_cell < 0] = dir_round_cell[dir_round_cell < 0] + 360 dir_round_cell[dir_round_cell == -0] = 0 dir_round_cell[dir_round_cell == 360] = 0 cellmaxus = np.zeros(dir_round_cell.shape) cellwind = np.zeros(dir_round_cell.shape) dir_unique = np.unique(dir_round_cell) for d in dir_unique: # find all values for matching direction ind = dir_round_cell == d i = np.argwhere(self.maxus_direction == d)[0][0] cellmaxus[ind] = self.maxus[i][ind] # correct for veg dynamic_mask = np.ones(cellmaxus.shape) for k,v in self.veg.items(): # Adjust veg types that were specified by the user if k != 'default': ind = self.veg_type == int(k) dynamic_mask[ind] = 0 cellmaxus[ind] += v # Apply the veg default to those that weren't messed with if self.veg['default'] != 0: cellmaxus[dynamic_mask == 1] += self.veg['default'] # correct unreasonable values cellmaxus[cellmaxus > 32] = 32 cellmaxus[cellmaxus < -32] = -32 # determine wind factor = float(self.config['reduction_factor']) ind = cellmaxus < -30.10 cellwind[ind] = factor * self.flatwind[ind] * 4.211 ind = (cellmaxus > -30.10) & (cellmaxus < -21.3) c = np.abs(cellmaxus[ind]) cellwind[ind] = factor * self.flatwind[ind] * \ (1.756507 - 0.1678945 * c + 0.01927844 * np.power(c, 2) - 0.0003651592 * np.power(c, 3)) ind = (cellmaxus > -21.3) & (cellmaxus < 0) c = np.abs(cellmaxus[ind]) cellwind[ind] = factor * self.flatwind[ind] * \ (1.0 + 0.1031717 * c - 0.008003561 * np.power(c, 2) + 0.0003996581 * np.power(c, 3)) ind = cellmaxus > 30.10 cellwind[ind] = self.flatwind[ind] / 4.211 ind = (cellmaxus < 30.10) & (cellmaxus > 21.3) c = cellmaxus[ind] cellwind[ind] = self.flatwind[ind] / \ (1.756507 - 0.1678945 * c + 0.01927844 * np.power(c, 2) - 0.0003651592 * np.power(c, 3)) ind = (cellmaxus < 21.3) & (cellmaxus >= 0) c = cellmaxus[ind] cellwind[ind] = self.flatwind[ind] / \ (1.0 + 0.1031717 * c - 0.008003561 * np.power(c, 2) + 0.0003996581 * np.power(c, 3)) # Convert from 3m to 5m wind speed cellwind *= 1.07985 # preseve the measured values cellwind[self.metadata.yi, self.metadata.xi] = data_speed # check for NaN nans, x = utils.nan_helper(cellwind) if np.sum(nans) > 0: cellwind[nans] = np.interp(x(nans), x(~nans), cellwind[~nans]) self.wind_speed = utils.set_min_max(cellwind, self.min, self.max) self.wind_direction = az self.cellmaxus = cellmaxus self.dir_round_cell = dir_round_cell
def distribute(self, data_speed, data_direction, t): """ Distribute given a Panda's dataframe for a single time step. Calls :mod:`smrf.distribute.image_data.image_data._distribute`. Follows the following steps for station measurements: 1. Adjust measured wind speeds at the stations and determine the wind direction componenets 2. Distribute the flat wind speed 3. Distribute the wind direction components 4. Simulate the wind speeds based on the distribute flat wind, wind direction, and maxus values Gridded interpolation distributes the given wind speed and direction. Args: data_speed: Pandas dataframe for single time step from wind_speed data_direction: Pandas dataframe for single time step from wind_direction t: time stamp """ self._logger.debug('{} Distributing wind_direction and wind_speed' .format(data_speed.name)) data_speed = data_speed[self.stations] data_direction = data_direction[self.stations] if self.gridded: # check if running with windninja if self.wind_ninja_dir is not None: wind_speed, wind_direction = self.convert_wind_ninja(t) self.wind_speed = wind_speed self.wind_direction = wind_direction else: self._distribute(data_speed, other_attribute='wind_speed') # wind direction components at the station self.u_direction = np.sin(data_direction * np.pi/180) # u self.v_direction = np.cos(data_direction * np.pi/180) # v # distribute u_direction and v_direction self._distribute(self.u_direction, other_attribute='u_direction_distributed') self._distribute(self.v_direction, other_attribute='v_direction_distributed') # combine u and v to azimuth az = np.arctan2(self.u_direction_distributed, self.v_direction_distributed)*180/np.pi az[az < 0] = az[az < 0] + 360 self.wind_direction = az # set min and max self.wind_speed = utils.set_min_max(self.wind_speed, self.min, self.max) else: # calculate the maxus at each site self.stationMaxus(data_speed, data_direction) # distribute the flatwind self._distribute(self.flatwind_point, other_attribute='flatwind') # distribute u_direction and v_direction self._distribute(self.u_direction, other_attribute='u_direction_distributed') self._distribute(self.v_direction, other_attribute='v_direction_distributed') # Calculate simulated wind speed at each cell from flatwind self.simulateWind(data_speed)
def dist_precip_wind(precip, precip_temp, az, dir_round_cell, wind_speed, cell_maxus, tbreak, tbreak_direction, veg_type, veg_fact, cfg, mask=None): """ Redistribute the precip based on wind speed and direciton to account for drifting. Args: precip: distributed precip precip_temp: precip_temp array az: wind direction dir_round_cell: from wind module wind_speed: wind speed array cell_maxus: max upwind slope from maxus file tbreak: relative local slope from tbreak file tbreak_direction: direction array from tbreak file veg_type: user input veg types to correct veg_factor: interception correction for veg types. ppt mult is multiplied by 1/veg_factor Returns: precip_drift: numpy array of precip redistributed for wind """ # thresholds tbreak_threshold = cfg['tbreak_threshold'] min_scour = cfg['winstral_min_scour'] max_scour = cfg['winstral_max_scour'] min_drift = cfg['winstral_min_drift'] max_drift = cfg['winstral_max_drift'] precip_temp_threshold = 0.5 # polynomial factors drift_poly = {} drift_poly['a'] = 0.0289 drift_poly['b'] = -0.0956 drift_poly['c'] = 1.000761 ppt_poly = {} ppt_poly['a'] = 0.0001737 ppt_poly['b'] = 0.002549 ppt_poly['c'] = 0.03265 ppt_poly['d'] = 0.5929 # initialize arrays celltbreak = np.ones(dir_round_cell.shape) drift_factor = np.ones(dir_round_cell.shape) precip_drift = np.zeros(dir_round_cell.shape) pptmult = np.ones(dir_round_cell.shape) # classify tbreak dir_unique = np.unique(dir_round_cell) for d in dir_unique: # find all values for matching direction ind = dir_round_cell == d i = np.argwhere(tbreak_direction == d)[0][0] celltbreak[ind] = tbreak[i][ind] # ################################################### # # Routine for drift cells # ################################################### # # for tbreak cells > threshold idx = ((celltbreak > tbreak_threshold) & (precip_temp < precip_temp_threshold)) # calculate drift factor drift_factor[idx] = drift_poly['c'] * np.exp( drift_poly['b'] * wind_speed[idx] + drift_poly['a'] * wind_speed[idx]**2) # cap drift factor drift_factor[idx] = utils.set_min_max(drift_factor[idx], min_drift, max_drift) # multiply precip by drift factor for drift cells precip_drift[idx] = drift_factor[idx] * precip[idx] # ################################################### # # Now lets deal with non drift cells # ################################################### # # for tbreak cells <= threshold (i.e. the rest of them) idx = ((celltbreak <= tbreak_threshold) & (precip_temp < precip_temp_threshold)) # reset pptmult for exposed pixels pptmult = np.ones(dir_round_cell.shape) # original from manuscript pptmult[idx] = ppt_poly['a'] + ppt_poly['c'] * cell_maxus[idx] - \ ppt_poly['b'] * cell_maxus[idx]**2 + ppt_poly['a'] * cell_maxus[idx]**3 # veg effects at indices that we are working on where veg type matches for i, v in enumerate(veg_fact): idv = ((veg_type == int(v)) & (idx)) pptmult[idv] = pptmult[idv] * 1.0 / veg_fact[v] # hardcode for pine pptmult[(idx) & ((veg_type == 3055) | (veg_type == 3053) | (veg_type == 42))] = 1.0 # 0.92 # cap ppt mult pptmult[idx] = utils.set_min_max(pptmult[idx], min_scour, max_scour) # multiply precip by scour factor precip_drift[idx] = pptmult[idx] * precip[idx] # ############################## # # no precip redistribution where dew point >= threshold idx = precip_temp >= precip_temp_threshold precip_drift[idx] = precip[idx] return precip_drift
def distribute_for_marks2017(self, data, precip_temp, ta, time, mask=None): """ Specialized distribute function for working with the new accumulated snow density model Marks2017 requires storm total and a corrected precipitation as to avoid precip between storms. """ #self.corrected_precip # = data.mul(self.storm_correction) if data.sum() > 0.0: # Check for time in every storm for i, s in self.storms.iterrows(): storm_start = s['start'] storm_end = s['end'] if time >= storm_start and time <= storm_end: # establish storm info self.storm_id = i storm = self.storms.iloc[self.storm_id] self.storming = True break else: self.storming = False self._logger.debug("Storming? {0}".format(self.storming)) self._logger.debug("Current Storm ID = {0}".format(self.storm_id)) # distribute data and set the min/max self._distribute(data, zeros=None) self.precip = utils.set_min_max(self.precip, self.min, self.max) if time == storm_start: # Entered into a new storm period distribute the storm total self._logger.debug('{0} Entering storm #{1}'.format( data.name, self.storm_id + 1)) if precip_temp.min() < 2.0: self._logger.debug('''Distributing Total Precip for Storm #{0}'''.format( self.storm_id + 1)) self._distribute(storm[self.stations].astype(float), other_attribute='storm_total') self.storm_total = utils.set_min_max( self.storm_total, self.min, self.max) if self.storming and precip_temp.min() < 2.0: self._logger.debug('''Calculating new snow density for storm #{0}'''.format(self.storm_id + 1)) # determine the precip phase and den snow_den, perc_snow = snow.calc_phase_and_density( precip_temp, self.precip, nasde_model=self.nasde_model) else: snow_den = np.zeros(self.precip.shape) perc_snow = np.zeros(self.precip.shape) # calculate decimal days since last storm self.storm_days = storms.time_since_storm_pixel( self.precip, precip_temp, perc_snow, storming=self.storming, time_step=self.time_step / 60.0 / 24.0, stormDays=self.storm_days, mass=self.ppt_threshold) else: self.storm_days += self.time_step / 60.0 / 24.0 self.precip = np.zeros(self.storm_days.shape) perc_snow = np.zeros(self.storm_days.shape) snow_den = np.zeros(self.storm_days.shape) # save the model state self.percent_snow = perc_snow self.snow_density = snow_den # day of last storm, this will be used in albedo self.last_storm_day = utils.water_day(data.name)[0] - \ self.storm_days - 0.001 # get the time since most recent storm if mask is not None: self.last_storm_day_basin = np.max(mask * self.last_storm_day) else: self.last_storm_day_basin = np.max(self.last_storm_day)
def distribute(self, date_time, air_temp, vapor_pressure=None, dew_point=None, cloud_factor=None): """ Distribute for a single time step. The following steps are taken when distributing thermal: 1. Calculate the clear sky thermal radiation from :mod:`smrf.envphys.core.envphys_c.ctopotherm` 2. Correct the clear sky thermal for the distributed cloud factor 3. Correct for canopy affects Args: date_time: datetime object for the current step air_temp: distributed air temperature for the time step vapor_pressure: distributed vapor pressure for the time step dew_point: distributed dew point for the time step cloud_factor: distributed cloud factor for the time step measured/modeled """ self._logger.debug('%s Distributing thermal' % date_time) # calculate clear sky thermal if self.clear_sky_method == 'marks1979': cth = np.zeros_like(air_temp, dtype=np.float64) envphys_c.ctopotherm(air_temp, dew_point, self.dem, self.sky_view_factor, cth, self.config['marks1979_nthreads']) elif self.clear_sky_method == 'dilley1998': cth = clear_sky.Dilly1998(air_temp, vapor_pressure / 1000) elif self.clear_sky_method == 'prata1996': cth = clear_sky.Prata1996(air_temp, vapor_pressure / 1000) elif self.clear_sky_method == 'angstrom1918': cth = clear_sky.Angstrom1918(air_temp, vapor_pressure / 1000) # terrain factor correction if (self.sky_view_factor is not None) and \ (self.clear_sky_method != 'marks1979'): # apply (emiss * skvfac) + (1.0 - skvfac) to the longwave cth = cth * self.sky_view_factor + (1.0 - self.sky_view_factor) * \ STEF_BOLTZ * air_temp**4 # make output variable self.thermal_clear = cth.copy() # correct for the cloud factor # ratio of measured/modeled solar indicates the thermal correction if self.correct_cloud: if self.cloud_method == 'garen2005': cth = cloud.Garen2005(cth, cloud_factor) elif self.cloud_method == 'unsworth1975': cth = cloud.Unsworth1975(cth, air_temp, cloud_factor) elif self.cloud_method == 'kimball1982': cth = cloud.Kimball1982(cth, air_temp, vapor_pressure / 1000, cloud_factor) elif self.cloud_method == 'crawford1999': cth = cloud.Crawford1999(cth, air_temp, cloud_factor) # make output variable self.thermal_cloud = cth.copy() # correct for vegetation if self.correct_veg: cth = vegetation.thermal_correct_canopy(cth, air_temp, self.veg_tau, self.veg_height) # make output variable self.thermal_veg = cth.copy() self.thermal = utils.set_min_max(cth, self.min, self.max)