Source code for pvdeg.humidity

"""Collection of classes and functions for humidity calculations.
"""

import numpy as np
import pandas as pd
import pvlib
from numba import jit
from rex import NSRDBX
from rex import Outputs
from pathlib import Path
from concurrent.futures import ProcessPoolExecutor, as_completed

from . import temperature
from . import spectral
from . import weather


def _ambient(weather_df):
    """
    Calculate ambient relative humidity from dry bulb air temperature and dew point

    references:
    Alduchov, O. A., and R. E. Eskridge, 1996: Improved Magnus' form approximation of saturation
    vapor pressure. J. Appl. Meteor., 35, 601–609.
    August, E. F., 1828: Ueber die Berechnung der Expansivkraft des Wasserdunstes. Ann. Phys. Chem.,
    13, 122–137.
    Magnus, G., 1844: Versuche über die Spannkräfte des Wasserdampfs. Ann. Phys. Chem., 61, 225–247.

    Parameters:
    -----------
    weather_df : pd.DataFrame
        Datetime-indexed weather dataframe which contains (at minimum) Ambient temperature
        ('temp_air') and dew point ('temp_dew') in units [C]

    Returns:
    --------
    weather_df : pd.DataFrame
        identical datetime-indexed dataframe with addional column 'relative_humidity' containing
        ambient relative humidity [%]
    """
    temp_air = weather_df["temp_air"]
    # "Dew Point" fallback handles key-name bug in pvlib < v0.10.3.
    dew_point = weather_df.get("dew_point")

    num = np.exp(17.625 * dew_point / (243.04 + dew_point))
    den = np.exp(17.625 * temp_air / (243.04 + temp_air))
    rh_ambient = 100 * num / den

    weather_df["relative_humidity"] = rh_ambient

    return weather_df


# TODO: When is dew_yield used?
[docs] @jit(nopython=True, error_model="python") def dew_yield(elevation, dew_point, dry_bulb, wind_speed, n): """ Estimates the dew yield in [mm/day]. Calculation taken from: Beysens, "Estimating dew yield worldwide from a few meteo data", Atmospheric Research 167 (2016) 146-155 Parameters ----------- elevation : int Site elevation [km] dew_point : float Dewpoint temperature in Celsius [°C] dry_bulb : float Air temperature "dry bulb temperature" [°C] wind_speed : float Air or windspeed measure [m/s] n : float Total sky cover(okta) This is a quasi emperical scale from 0 to 8 used in meterology which corresponds to 0-sky completely clear, to 8-sky completely cloudy. Does not account for cloud type or thickness. Returns ------- dew_yield : float Amount of dew yield in [mm/day] """ wind_speed_cut_off = 4.4 dew_yield = (1 / 12) * ( 0.37 * ( 1 + (0.204323 * elevation) - (0.0238893 * elevation**2) - (18.0132 - (1.04963 * elevation**2) + (0.21891 * elevation**2)) * (10 ** (-3) * dew_point) ) * ((((dew_point + 273.15) / 285) ** 4) * (1 - (n / 8))) + (0.06 * (dew_point - dry_bulb)) * (1 + 100 * (1 - np.exp(-((wind_speed / wind_speed_cut_off) ** 20)))) ) return dew_yield
[docs] def psat(temp, average=True): """ Function calculated the water saturation temperature or dew point for a given water vapor pressure. Water vapor pressure model created from an emperical fit of ln(Psat) vs temperature using a 6th order polynomial fit. The fit produced R^2=0.999813. Calculation created by Michael Kempe, unpublished data. Parameters: ----------- temp : series, float The air temperature (dry bulb) as a time-indexed series [C] average : boolean, default = True If true, return both psat serires and average psat (used for certain calcs) Returns: -------- psat : array, float Saturation point avg_psat : float, optional mean saturation point for the series given """ psat = np.exp( (3.2575315268e-13 * temp**6) - (1.5680734584e-10 * temp**5) + (2.2213041913e-08 * temp**4) + (2.3720766595e-7 * temp**3) - (4.0316963015e-04 * temp**2) + (7.9836323361e-02 * temp) - (5.6983551678e-1) ) if average: return psat, psat.mean() else: return psat
[docs] def surface_outside(rh_ambient, temp_ambient, temp_module): """ Function calculates the Relative Humidity of a Solar Panel Surface at module temperature Parameters ---------- rh_ambient : float The ambient outdoor environmnet relative humidity [%]. temp_ambient : float The ambient outdoor environmnet temperature [°C] temp_module : float The surface temperature of the solar panel module [°C] Returns -------- rh_Surface : float The relative humidity of the surface of a solar module as a fraction or percent depending on input. """ rh_Surface = rh_ambient * (psat(temp_ambient)[0] / psat(temp_module)[0]) return rh_Surface
########### # Front Encapsulant RH ########### def _diffusivity_numerator( rh_ambient, temp_ambient, temp_module, So=1.81390702, Eas=16.729, Ead=38.14 ): """ Calculation is used in determining a weighted average Relative Humidity of the outside surface of a module. This funciton is used exclusively in the function _diffusivity_weighted_water and could be combined. The function returns values needed for the numerator of the Diffusivity weighted water content equation. This function will return a pandas series prior to summation of the numerator Parameters ---------- rh_ambient : pandas series (float) The ambient outdoor environmnet relative humidity in [%] EXAMPLE: "50 = 50% NOT .5 = 50%" temp_ambient : pandas series (float) The ambient outdoor environmnet temperature [C] temp_module : pandas series (float) The surface temperature of the solar panel module [C] So : float Float, Encapsulant solubility prefactor in [g/cm3] So = 1.81390702(g/cm3) is the suggested value for EVA. Eas : float Encapsulant solubility activation energy in [kJ/mol] Eas = 16.729(kJ/mol) is the suggested value for EVA. Ead : float Encapsulant diffusivity activation energy in [kJ/mol] Ead = 38.14(kJ/mol) is the suggested value for EVA. Returns ------- diff_numerator : pandas series (float) Nnumerator of the Sdw equation prior to summation """ # Get the relative humidity of the surface rh_surface = surface_outside(rh_ambient, temp_ambient, temp_module) # Generate a series of the numerator values "prior to summation" diff_numerator = ( So * np.exp(-(Eas / (0.00831446261815324 * (temp_module + 273.15)))) * rh_surface * np.exp(-(Ead / (0.00831446261815324 * (temp_module + 273.15)))) ) return diff_numerator def _diffusivity_denominator(temp_module, Ead=38.14): """ Calculation is used in determining a weighted average Relative Humidity of the outside surface of a module. This funciton is used exclusively in the function _diffusivity_weighted_water and could be combined. The function returns values needed for the denominator of the Diffusivity weighted water content equation(diffuse_water). This function will return a pandas series prior to summation of the denominator Parameters ---------- Ead : float Encapsulant diffusivity activation energy in [kJ/mol] 38.14(kJ/mol) is the suggested value for EVA. temp_module : pandas series (float) The surface temperature in Celsius of the solar panel module Returns ------- diff_denominator : pandas series (float) Denominator of the diffuse_water equation prior to summation """ diff_denominator = np.exp(-(Ead / (0.00831446261815324 * (temp_module + 273.15)))) return diff_denominator def _diffusivity_weighted_water( rh_ambient, temp_ambient, temp_module, So=1.81390702, Eas=16.729, Ead=38.14 ): """ Calculation is used in determining a weighted average water content at the surface of a module. It is used as a constant water content that is equivalent to the time varying one with respect to moisture ingress. The function calculates the Diffusivity weighted water content. Parameters ---------- rh_ambient : pandas series (float) The ambient outdoor environmnet relative humidity in (%) EXAMPLE: "50 = 50% NOT .5 = 50%" temp_ambient : pandas series (float) The ambient outdoor environmnet temperature in Celsius temp_module : pandas series (float) The surface temperature in Celsius of the solar panel module So : float Float, Encapsulant solubility prefactor in [g/cm3] So = 1.81390702(g/cm3) is the suggested value for EVA. Eas : float Encapsulant solubility activation energy in [kJ/mol] Eas = 16.729(kJ/mol) is the suggested value for EVA. Ead : float Encapsulant diffusivity activation energy in [kJ/mol] Ead = 38.14(kJ/mol) is the suggested value for EVA. Returns ------ diffuse_water : float Diffusivity weighted water content """ numerator = _diffusivity_numerator( rh_ambient, temp_ambient, temp_module, So, Eas, Ead ) # get the summation of the numerator numerator = numerator.sum(axis=0, skipna=True) denominator = _diffusivity_denominator(temp_module, Ead) # get the summation of the denominator denominator = denominator.sum(axis=0, skipna=True) diffuse_water = (numerator / denominator) / 100 return diffuse_water
[docs] def front_encap(rh_ambient, temp_ambient, temp_module, So=1.81390702, Eas=16.729): """ Function returns a diffusivity weighted average Relative Humidity of the module surface. Parameters ---------- rh_ambient : series (float) ambient Relative Humidity [%] temp_ambient : series (float) ambient outdoor temperature [°C] temp_module : pandas series (float) The surface temperature in Celsius of the solar panel module "module temperature [°C]" So : float Encapsulant solubility prefactor in [g/cm3] So = 1.81390702(g/cm3) is the suggested value for EVA. Eas : float Encapsulant solubility activation energy in [kJ/mol] Eas = 16.729(kJ/mol) is the suggested value for EVA. Return ------ RHfront_series : pandas series (float) Relative Humidity of Frontside Solar module Encapsulant [%] """ diffuse_water = _diffusivity_weighted_water( rh_ambient=rh_ambient, temp_ambient=temp_ambient, temp_module=temp_module ) RHfront_series = ( diffuse_water / (So * np.exp(-(Eas / (0.00831446261815324 * (temp_module + 273.15))))) ) * 100 return RHfront_series
########### # Back Encapsulant Relative Humidity ########### def _csat(temp_module, So=1.81390702, Eas=16.729): """ Calculation is used in determining Relative Humidity of Backside Solar Module Encapsulant, and returns saturation of Water Concentration [g/cm³] Parameters ----------- temp_module : pandas series (float) The surface temperature in Celsius of the solar panel module "module temperature [°C]" So : float Encapsulant solubility prefactor in [g/cm3] So = 1.81390702(g/cm3) is the suggested value for EVA. Eas : float Encapsulant solubility activation energy in [kJ/mol] Eas = 16.729(kJ/mol) is the suggested value for EVA. Returns ------- Csat : pandas series (float) Saturation of Water Concentration [g/cm³] """ # Saturation of water concentration Csat = So * np.exp(-(Eas / (0.00831446261815324 * (273.15 + temp_module)))) return Csat def _ceq(Csat, rh_SurfaceOutside): """ Calculation is used in determining Relative Humidity of Backside Solar Module Encapsulant, and returns Equilibration water concentration (g/cm³) Parameters ------------ Csat : pandas series (float) Saturation of Water Concentration (g/cm³) rh_SurfaceOutside : pandas series (float) The relative humidity of the surface of a solar module (%) Returns -------- Ceq : pandas series (float) Equilibration water concentration (g/cm³) """ Ceq = Csat * (rh_SurfaceOutside / 100) return Ceq
[docs] @jit(nopython=True) def Ce_numba( start, temp_module, rh_surface, WVTRo=7970633554, EaWVTR=55.0255, So=1.81390702, l=0.5, Eas=16.729, ): """ Calculation is used in determining Relative Humidity of Backside Solar Module Encapsulant. This function returns a numpy array of the Concentration of water in the encapsulant at every time step Numba was used to isolate recursion requiring a for loop Numba Functions compile and run in machine code but can not use pandas (Very fast). Parameters ----------- start : float Initial value of the Concentration of water in the encapsulant currently takes the first value produced from the _ceq(Saturation of Water Concentration) as a point of acceptable equilibrium temp_module : pandas series (float) The surface temperature in Celsius of the solar panel module "module temperature [°C]" rh_Surface : list (float) The relative humidity of the surface of a solar module [%] EXAMPLE: "50 = 50% NOT .5 = 50%" WVTRo : float Water Vapor Transfer Rate prefactor [g/m2/day]. The suggested value for EVA is WVTRo = 7970633554(g/m2/day). EaWVTR : float Water Vapor Transfer Rate activation energy [kJ/mol] . It is suggested to use 0.15[mm] thick PET as a default for the backsheet and set EaWVTR=55.0255[kJ/mol] So : float Encapsulant solubility prefactor in [g/cm3] So = 1.81390702(g/cm3) is the suggested value for EVA. l : float Thickness of the backside encapsulant [mm]. The suggested value for encapsulat is EVA l=0.5(mm) Eas : float Encapsulant solubility activation energy in [kJ/mol] Eas = 16.729[kJ/mol] is the suggested value for EVA. Returns -------- Ce_list : numpy array Concentration of water in the encapsulant at every time step """ dataPoints = len(temp_module) Ce_list = np.zeros(dataPoints) for i in range(0, len(rh_surface)): if i == 0: # Ce = Initial start of concentration of water Ce = start else: Ce = Ce_list[i - 1] Ce = Ce + ( ( WVTRo / 100 / 100 / 24 * np.exp( -((EaWVTR) / (0.00831446261815324 * (temp_module[i] + 273.15))) ) ) / ( So * l / 10 * np.exp(-((Eas) / (0.00831446261815324 * (temp_module[i] + 273.15)))) ) * ( rh_surface[i] / 100 * So * np.exp(-((Eas) / (0.00831446261815324 * (temp_module[i] + 273.15)))) - Ce ) ) Ce_list[i] = Ce return Ce_list
[docs] def back_encap( rh_ambient, temp_ambient, temp_module, WVTRo=7970633554, EaWVTR=55.0255, So=1.81390702, l=0.5, Eas=16.729, ): """ rh_back_encap() Function to calculate the Relative Humidity of Backside Solar Module Encapsulant and return a pandas series for each time step Parameters ----------- rh_ambient : pandas series (float) The ambient outdoor environmnet relative humidity in [%] EXAMPLE: "50 = 50% NOT .5 = 50%" temp_ambient : pandas series (float) The ambient outdoor environmnet temperature in Celsius temp_module : list (float) The surface temperature in Celsius of the solar panel module "module temperature [°C]" WVTRo : float Water Vapor Transfer Rate prefactor [g/m2/day]. The suggested value for EVA is WVTRo = 7970633554[g/m2/day]. EaWVTR : float Water Vapor Transfer Rate activation energy [kJ/mol] . It is suggested to use 0.15[mm] thick PET as a default for the backsheet and set EaWVTR=55.0255[kJ/mol] So : float Encapsulant solubility prefactor in [g/cm3] So = 1.81390702[g/cm3] is the suggested value for EVA. l : float Thickness of the backside encapsulant [mm]. The suggested value for encapsulat is EVA l=0.5[mm] Eas : float Encapsulant solubility activation energy in [kJ/mol] Eas = 16.729[kJ/mol] is the suggested value for EVA. Returns -------- RHback_series : pandas series (float) Relative Humidity of Backside Solar Module Encapsulant [%] """ rh_surface = surface_outside( rh_ambient=rh_ambient, temp_ambient=temp_ambient, temp_module=temp_module ) Csat = _csat(temp_module=temp_module, So=So, Eas=Eas) Ceq = _ceq(Csat=Csat, rh_SurfaceOutside=rh_surface) start = Ceq.iloc[0] # Need to convert these series to numpy arrays for numba function temp_module_numba = temp_module.to_numpy() rh_surface_numba = rh_surface.to_numpy() Ce_nparray = Ce_numba( start=start, temp_module=temp_module_numba, rh_surface=rh_surface_numba, WVTRo=WVTRo, EaWVTR=EaWVTR, So=So, l=l, Eas=Eas, ) # RHback_series = 100 * (Ce_nparray / (So * np.exp(-( (Eas) / # (0.00831446261815324 * (temp_module + 273.15)) )) )) RHback_series = 100 * (Ce_nparray / Csat) return RHback_series
[docs] def backsheet_from_encap(rh_back_encap, rh_surface_outside): """ Function to calculate the Relative Humidity of solar module backsheet as timeseries. Requires the RH of the backside encapsulant and the outside surface of the module. Parameters ---------- rh_back_encap : pandas series (float) Relative Humidity of Frontside Solar module Encapsulant. *See rh_back_encap() rh_surface_outside : pandas series (float) The relative humidity of the surface of a solar module. *See rh_surface_outside() Returns -------- RHbacksheet_series : pandas series (float) Relative Humidity of Backside Backsheet of a Solar Module [%] """ RHbacksheet_series = (rh_back_encap + rh_surface_outside) / 2 return RHbacksheet_series
[docs] def backsheet( rh_ambient, temp_ambient, temp_module, WVTRo=7970633554, EaWVTR=55.0255, So=1.81390702, l=0.5, Eas=16.729, ): """Function to calculate the Relative Humidity of solar module backsheet as timeseries. Parameters ---------- rh_ambient : pandas series (float) The ambient outdoor environmnet relative humidity in (%) EXAMPLE: "50 = 50% NOT .5 = 50%" temp_ambient : pandas series (float) The ambient outdoor environmnet temperature in Celsius temp_module : list (float) The surface temperature in Celsius of the solar panel module "module temperature [°C]" WVTRo : float Water Vapor Transfer Rate prefactor [g/m2/day]. The suggested value for EVA is WVTRo = 7970633554[g/m2/day]. EaWVTR : float Water Vapor Transfer Rate activation energy [kJ/mol] . It is suggested to use 0.15[mm] thick PET as a default for the backsheet and set EaWVTR=55.0255[kJ/mol] So : float Encapsulant solubility prefactor in [g/cm3] So = 1.81390702[g/cm3] is the suggested value for EVA. l : float Thickness of the backside encapsulant [mm]. The suggested value for encapsulat is EVA l=0.5[mm] Eas : float Encapsulant solubility activation energy in [kJ/mol] Eas = 16.729[kJ/mol] is the suggested value for EVA. Returns -------- rh_backsheet : float series or array relative humidity of the PV backsheet as a time-series [%] """ RHback_series = back_encap( rh_ambient=rh_ambient, temp_ambient=temp_ambient, temp_module=temp_module, WVTRo=WVTRo, EaWVTR=EaWVTR, So=So, l=l, Eas=Eas, ) surface = surface_outside( rh_ambient=rh_ambient, temp_ambient=temp_ambient, temp_module=temp_module ) backsheet = (RHback_series + surface) / 2 return backsheet
[docs] def module( weather_df, meta, tilt=None, azimuth=180, sky_model="isotropic", temp_model="sapm", conf="open_rack_glass_glass", WVTRo=7970633554, EaWVTR=55.0255, So=1.81390702, l=0.5, Eas=16.729, wind_factor=0.33, ): """Calculate the Relative Humidity of solar module backsheet from timeseries data. Parameters ---------- weather_df : pd.DataFrame Weather data for a single location. meta : pd.DataFrame Meta data for a single location. tilt : float, optional Tilt angle of PV system relative to horizontal. azimuth : float, optional Azimuth angle of PV system relative to north. sky_model : str, optional Options: 'isotropic', 'klucher', 'haydavies', 'reindl', 'king', 'perez'. temp_model : str, optional Options: 'sapm', 'pvsyst', 'faiman', 'sandia'. mount_type : str, optional Options: 'insulated_back_glass_polymer', 'open_rack_glass_polymer' 'close_mount_glass_glass', 'open_rack_glass_glass' WVTRo : float Water Vapor Transfer Rate prefactor (g/m2/day). The suggested value for EVA is WVTRo = 7970633554(g/m2/day). EaWVTR : float Water Vapor Transfer Rate activation energy (kJ/mol) . It is suggested to use 0.15(mm) thick PET as a default for the backsheet and set EaWVTR=55.0255(kJ/mol) So : float Encapsulant solubility prefactor in [g/cm3] So = 1.81390702(g/cm3) is the suggested value for EVA. l : float Thickness of the backside encapsulant (mm). The suggested value for encapsulat is EVA l=0.5(mm) Eas : float Encapsulant solubility activation energy in [kJ/mol] Eas = 16.729(kJ/mol) is the suggested value for EVA. wind_factor : float, optional Wind speed correction exponent to account for different wind speed measurement heights between weather database (e.g. NSRDB) and the tempeature model (e.g. SAPM) The NSRDB provides calculations at 2 m (i.e module height) but SAPM uses a 10 m height. It is recommended that a power-law relationship between height and wind speed of 0.33 be used. This results in a wind speed that is 1.7 times higher. It is acknowledged that this can vary significantly. Returns -------- rh_backsheet : float series or array relative humidity of the PV backsheet as a time-series """ # solar_position = spectral.solar_position(weather_df, meta) # poa = spectral.poa_irradiance(weather_df, meta, solar_position, tilt, azimuth, sky_model) # temp_module = temperature.module(weather_df, poa, temp_model, mount_type, wind_factor) poa = spectral.poa_irradiance( weather_df=weather_df, meta=meta, tilt=tilt, azimuth=azimuth, sky_model=sky_model, ) temp_module = temperature.module( weather_df, meta, poa=poa, temp_model=temp_model, conf=conf, wind_factor=wind_factor, ) rh_surface_outside = surface_outside( rh_ambient=weather_df["relative_humidity"], temp_ambient=weather_df["temp_air"], temp_module=temp_module, ) rh_front_encap = front_encap( rh_ambient=weather_df["relative_humidity"], temp_ambient=weather_df["temp_air"], temp_module=temp_module, So=So, Eas=Eas, ) rh_back_encap = back_encap( rh_ambient=weather_df["relative_humidity"], temp_ambient=weather_df["temp_air"], temp_module=temp_module, WVTRo=WVTRo, EaWVTR=EaWVTR, So=So, l=l, Eas=Eas, ) rh_backsheet = backsheet_from_encap( rh_back_encap=rh_back_encap, rh_surface_outside=rh_surface_outside ) data = { "RH_surface_outside": rh_surface_outside, "RH_front_encap": rh_front_encap, "RH_back_encap": rh_back_encap, "RH_backsheet": rh_backsheet, } results = pd.DataFrame(data=data) return results
# def run_module( # project_points, # out_dir, # tag, # weather_db, # weather_satellite, # weather_names, # max_workers=None, # tilt=None, # azimuth=180, # sky_model='isotropic', # temp_model='sapm', # mount_type='open_rack_glass_glass', # WVTRo=7970633554, # EaWVTR=55.0255, # So=1.81390702, # l=0.5, # Eas=16.729, # wind_factor=1 # ): # """Run the relative humidity calculation for a set of project points.""" # #inputs # weather_arg = {} # weather_arg['satellite'] = weather_satellite # weather_arg['names'] = weather_names # weather_arg['NREL_HPC'] = True #TODO: add argument or auto detect # weather_arg['attributes'] = [ # 'temp_air', # 'wind_speed', # 'dhi', 'ghi', # 'dni','relative_humidity' # ] # #TODO: is there a better way to add the meta data? # nsrdb_fnames, hsds = weather.get_NSRDB_fnames( # weather_arg['satellite'], # weather_arg['names'], # weather_arg['NREL_HPC']) # with NSRDBX(nsrdb_fnames[0], hsds=hsds) as f: # meta = f.meta[f.meta.index.isin(project_points.gids)] # ti = f.time_index # all_fields = ['RH_surface_outside', # 'RH_front_encap', # 'RH_back_encap', # 'RH_backsheet'] # out_fp = Path(out_dir) / f"out_rel_humidity{tag}.h5" # shapes = {n : (len(ti), len(project_points)) for n in all_fields} # attrs = {n : {'units': '%'} for n in all_fields} # chunks = {n : None for n in all_fields} # dtypes = {n : "float32" for n in all_fields} # Outputs.init_h5( # out_fp, # all_fields, # shapes, # attrs, # chunks, # dtypes, # meta=meta.reset_index(), # time_index=ti # ) # future_to_point = {} # with ProcessPoolExecutor(max_workers=max_workers) as executor: # for point in project_points: # gid = int(point.gid) # weather_df, meta = weather.load( # database = weather_db, # id = gid, # **weather_arg) # future = executor.submit( # module, # weather_df, # meta, # tilt, # azimuth, # sky_model, # temp_model, # mount_type, # WVTRo, # EaWVTR, # So, # l, # Eas, # wind_factor # ) # future_to_point[future] = gid # with Outputs(out_fp, mode="a") as out: # for future in as_completed(future_to_point): # result = future.result() # gid = future_to_point.pop(future) # ind = project_points.index(gid) # for dset, data in result.items(): # out[dset, :, ind] = data.values # return out_fp.as_posix()