Beispiel #1
0
    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)
Beispiel #2
0
    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)
Beispiel #3
0
    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
Beispiel #4
0
    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)
Beispiel #5
0
    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)
Beispiel #6
0
    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)
Beispiel #7
0
    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)
Beispiel #8
0
    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()
Beispiel #10
0
    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
Beispiel #11
0
    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)
Beispiel #12
0
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
Beispiel #13
0
    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)
Beispiel #14
0
    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)