Source code for pvdeg.degradation

"""Collection of functions for degradation calculations."""

import numpy as np
import pandas as pd
from typing import Union
from pvdeg import humidity

from . import (
    temperature,
    spectral,
    decorators,
)

R_GAS = 0.00831446261815324  # Gas Constant in [kJ/mol*K]


def _extract_param(parameters, key, default=None):
    """Helper to extract parameter value from nested dict."""
    if parameters is not None and key in parameters:
        value = parameters[key].get("value", None)
        if value is not None:
            return value
    return default


[docs] def arrhenius( weather_df=None, temperature=None, RH=None, irradiance=None, elapsed_time=None, Ro=1, Ea=0, p=0, n=0, C2=0, parameters=None, ): """ Calculate the degradation rate using an Arrhenius function with power law functions for humidity and irradiance dependence. D = R_0 ∫[RH(t)]^n·e^[-E_a/RT(t)] {∫[e^(-C_2∙λ)∙G(λ,t)]^p dλ}dt Parameters ---------- weather_df : pd.DataFrame Dataframe containing temperature, humidity, and irradiance data. Defaults to module surface temperature, surface humidity, and POA global irradiance. temperature : pd.DataFrame Temperature data for Arrhenius degradation calculation. If not specified, uses module surface temperature from weather_df. If Ea=0, temperature is not needed. RH : pd.DataFrame Relative humidity data for Arrhenius degradation calculation. If not specified, uses module surface relative humidity from weather_df. If n=0, humidity is not needed. irradiance : pd.DataFrame Irradiance data for Arrhenius degradation calculation. If not specified, uses module POA irradiance from weather_df. If p=0, irradiance is not needed. If C2 is provided, wavelength spectral intensity data must be provided. The header should start with "spectra", followed by wavelength points. Each element is a list of intensity values at each wavelength [W/m²/nm]. elapsed_time : pd.DataFrame If the time step for each interval is not constant, this can be used to provide a different elapsed time value for each element. If it is included in the weather_df, it must be under a column named "elapsed_time". Ro : float Degradation rate prefactor [e.g. %/h/%RH/(1000 W/m²)]. Defaults to 1 if not provided. Ea : float Degradation Activation Energy [kJ/mol]. If Ea=0, no temperature dependence and degradation will proceed according to the amount of light an humidity. p : float Power law coefficient for irradiance dependence. If p=0, ignores light. Small p (e.g. 0.0001) means little dependence of degradation on irradiance, but only daylight is considered. n : float Power law coefficient for humidity dependence. If n=0, ignores humidity. C2 : float Coefficient for spectral response dependence on wavelength. parameters : json Database containing parameters for Arrhenius calculation. If Ea, n, or p are not provided, values are taken from this json database. Returns ------- degradation : float Total degradation with units as determined by Ro. """ # override defaults with parameters if provided if parameters is not None: Ro = _extract_param(parameters, "R_0", Ro) Ea = _extract_param(parameters, "E_a", Ea) n = _extract_param(parameters, "n", n) p = _extract_param(parameters, "p", p) C2 = _extract_param(parameters, "C_2", C2) if temperature is None and Ea != 0: if weather_df is not None: if "temperature" in weather_df: temperature = weather_df["temperature"] elif "temp_module" in weather_df: temperature = weather_df["temp_module"] print("Using temp_module from weather_df for temperature.") else: raise ValueError("Temperature data must be provided if Ea is provided.") if n != 0 and RH is None: print(n) if "RH_surface_outside" in weather_df: RH = weather_df["RH_surface_outside"] elif ( "relative_humidity" in weather_df and "temp_air" in weather_df and "temp_module" in weather_df ): RH = humidity.surface_relative( weather_df["relative_humidity"], weather_df["temp_air"], weather_df["temp_module"], ) else: raise ValueError( "Relative Humidity data must be provided if n is provided." ) if RH is not None: print("Using RH_surface_outside from weather_df for humidity.") if irradiance is None: if C2 != 0 or p != 0: if weather_df is not None: for col in weather_df.columns: if "SPECTRA" in (col[:7]).upper(): irradiance = weather_df[col].copy() irradiance = pd.DataFrame(irradiance) break if irradiance is None: if "poa_global" in weather_df: irradiance = weather_df["poa_global"] print("Using poa_global from weather_df for irradiance.") if C2 != 0: raise ValueError( "Irradiance data not provided. Please provide " "irradiance data in weather_df." ) # In the future the spectra will be created using AM1.5. else: raise ValueError( "Irradiance data not provided. Please provide it in " "irradiance or weather_df." ) else: raise ValueError( "Irradiance data must be provided when C2 or p are used." ) if elapsed_time is None: if weather_df is not None: if "elapsed_time" in weather_df: elapsed_time = weather_df["elapsed_time"] if C2 != 0: wavelengths = [ float(i) for i in irradiance.columns[0].split("[")[1].split("]")[0].split(",") ] wavelengths = np.array(wavelengths) bin_widths = ( np.append(wavelengths, [0, 0]) - np.append([0, 0], wavelengths) ) / 2 bin_widths = bin_widths[1:] bin_widths = bin_widths[:-1] # assumes the first and last bin widths are the width of that between the next # or previous bin, respectively. bin_widths[0] = bin_widths[1] bin_widths[-1] = bin_widths[-2] bin_widths = pd.Series(bin_widths) wavelengths = pd.Series(wavelengths) if isinstance(irradiance, pd.DataFrame): irradiance = irradiance.T.to_numpy().reshape( -1, ) irradiance = pd.Series(irradiance) if p == 0: if Ea != 0: if n == 0: degradation = Ro * np.exp(-(Ea / (R_GAS * (temperature + 273.15)))) else: degradation = ( Ro * np.exp(-(Ea / (R_GAS * (temperature + 273.15)))) * (RH**n) ) else: if n == 0: degradation = ( Ro * weather_df.iloc[:, 0] / weather_df.iloc[:, 0] ) # This makes sure it sums over the corect number of time # intervals. else: degradation = ( Ro * (RH**n) * weather_df.iloc[:, 0] / weather_df.iloc[:, 0] ) else: degradation = bin_widths * ((np.exp(-C2 * wavelengths) * irradiance) ** p) if Ea != 0: if n == 0: degradation = ( degradation * Ro * np.exp(-(Ea / (R_GAS * (temperature + 273.15)))) ) else: degradation = ( degradation * Ro * np.exp(-(Ea / (R_GAS * (temperature + 273.15)))) * (RH**n) ) else: if n == 0: degradation = degradation * Ro else: degradation = degradation * Ro * (RH**n) elif Ea != 0: if n == 0 and p == 0: degradation = Ro * np.exp(-(Ea / (R_GAS * (temperature + 273.15)))) elif n == 0 and p != 0: degradation = ( Ro * np.exp(-(Ea / (R_GAS * (temperature + 273.15)))) * (irradiance**p) ) elif n != 0 and p == 0: degradation = ( Ro * np.exp(-(Ea / (R_GAS * (temperature + 273.15)))) * (RH**n) ) else: degradation = ( Ro * np.exp(-(Ea / (R_GAS * (temperature + 273.15)))) * (RH**n) * (irradiance**p) ) else: if n == 0 and p == 0: degradation = Ro * weather_df.iloc[:, 0] / weather_df.iloc[:, 0] elif n == 0 and p != 0: degradation = Ro * (irradiance**p) elif n != 0 and p == 0: degradation = Ro * (RH**n) else: degradation = Ro * (RH**n) * (irradiance**p) if elapsed_time is not None: if isinstance(elapsed_time, pd.DataFrame): elapsed_time = elapsed_time.T.to_numpy().reshape( -1, ) elapsed_time = pd.Series(elapsed_time) degradation = degradation * elapsed_time return degradation.sum(axis=0, skipna=True)
[docs] def vantHoff_deg( weather_df, meta, I_chamber, temp_chamber, poa=None, temp=None, p=0.5, Tf=1.41, temp_model="sapm", conf="open_rack_glass_polymer", wind_factor=0.33, irradiance_kwarg={}, model_kwarg={}, ): """ Calculate Van't Hoff Irradiance Degradation acceleration factor. In this calculation, the rate of degradation kinetics is calculated using the Van't Hoff model. Parameters ---------- weather_df : pd.DataFrame DataFrame containing at least dni, dhi, ghi, temperature, wind_speed meta : dict Location meta-data containing at least latitude, longitude, altitude I_chamber : float Irradiance of Controlled Condition [W/m²] temp_chamber : float Reference temperature [°C] ("Chamber Temperature") poa : pd.Series or pd.DataFrame, optional Series or DataFrame containing 'poa_global', Global Plane of Array Irradiance [W/m²] temp : pd.Series, optional Solar module temperature or Cell temperature [°C]. If not provided, it will be generated using the default parameters of pvdeg.temperature.cell p : float Fit parameter Tf : float Multiplier for the increase in degradation for every 10[°C] temperature increase temp_model : (str, optional) Specify which temperature model from pvlib to use. Current options: conf : (str) The configuration of the PV module architecture and mounting configuration. Currently only used for 'sapm' and 'pvsys'. With different options for each. 'sapm' options: ``open_rack_glass_polymer`` (default), ``open_rack_glass_glass``, ``close_mount_glass_glass``, ``insulated_back_glass_polymer`` 'pvsys' options: ``freestanding``, ``insulated`` wind_factor : float, optional Wind speed correction exponent to account for different wind speed measurement heights between weather database (e.g. NSRDB) and the temperature 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. irradiance_kwarg : (dict, optional) keyword argument dictionary used for the poa irradiance calculation. options: ``sol_position``, ``tilt``, ``azimuth``, ``sky_model``. See ``pvdeg.spectral.poa_irradiance``. model_kwarg : (dict, optional) keyword argument dictionary used for the pvlib temperature model calculation. See https://pvlib-python.readthedocs.io/en/stable/reference/pv_modeling/temperature.html # noqa for more. Returns ------- accelerationFactor : float or pd.Series Degradation acceleration factor """ if poa is None: poa = spectral.poa_irradiance(weather_df, meta, **irradiance_kwarg) if isinstance(poa, pd.DataFrame): poa_global = poa["poa_global"] if temp is None: temp = temperature.temperature( cell_or_mod="cell", temp_model=temp_model, weather_df=weather_df, meta=meta, poa=poa, conf=conf, wind_factor=wind_factor, model_kwarg=model_kwarg, ) rateOfDegEnv = (poa_global**p) * (Tf ** ((temp - temp_chamber) / 10)) avgOfDegEnv = rateOfDegEnv.mean() rateOfDegChamber = I_chamber**p accelerationFactor = rateOfDegChamber / avgOfDegEnv return accelerationFactor
[docs] @decorators.geospatial_quick_shape("numeric", ["Iwa"]) def IwaVantHoff( weather_df, meta, poa=None, temp=None, Teq=None, p=0.5, Tf=1.41, temp_model="sapm", conf="open_rack_glass_polymer", wind_factor=0.33, model_kwarg={}, irradiance_kwarg={}, ): """ Calculate IWa: Environment Characterization [W/m²]. For one year of degradation, the controlled environment lamp settings will need to be set to IWa. Parameters ---------- weather_df : pd.DataFrame DataFrame containing at least dni, dhi, ghi, temperature, wind_speed meta : dict Location meta-data containing at least latitude, longitude, altitude poa : pd.Series or pd.DataFrame, optional Series or DataFrame containing 'poa_global', Global Plane of Array Irradiance [W/m²] temp : pd.Series, optional Solar module temperature or Cell temperature [°C] Teq : pd.Series, optional VantHoff equivalent temperature [°C] p : float Fit parameter Tf : float Multiplier for the increase in degradation for every 10[°C] temperature increase temp_model : (str, optional) Specify which temperature model from pvlib to use. Current options: conf : (str) The configuration of the PV module architecture and mounting configuration. Currently only used for 'sapm' and 'pvsys'. With different options for each. 'sapm' options: ``open_rack_glass_polymer`` (default), ``open_rack_glass_glass``, ``close_mount_glass_glass``, ``insulated_back_glass_polymer`` 'pvsys' options: ``freestanding``, ``insulated`` wind_factor : float, optional Wind speed correction exponent to account for different wind speed measurement heights between weather database (e.g. NSRDB) and the temperature 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. irradiance_kwarg : (dict, optional) keyword argument dictionary used for the poa irradiance calculation. options: ``sol_position``, ``tilt``, ``azimuth``, ``sky_model``. See ``pvdeg.spectral.poa_irradiance``. model_kwarg : (dict, optional) keyword argument dictionary used for the pvlib temperature model calculation. See https://pvlib-python.readthedocs.io/en/stable/reference/pv_modeling/temperature.html # noqa for more. Returns ------- Iwa : float Environment Characterization [W/m²] """ if poa is None: poa = spectral.poa_irradiance(weather_df, meta, **irradiance_kwarg) if temp is None: temp = temperature.temperature( cell_or_mod="cell", temp_model=temp_model, weather_df=weather_df, meta=meta, poa=poa, conf=conf, wind_factor=wind_factor, model_kwarg=model_kwarg, ) if Teq is None: toSum = Tf ** (temp / 10) summation = toSum.sum(axis=0, skipna=True) Teq = (10 / np.log(Tf)) * np.log(summation / len(temp)) if isinstance(poa, pd.DataFrame): poa_global = poa["poa_global"] else: poa_global = poa toSum = (poa_global**p) * (Tf ** ((temp - Teq) / 10)) summation = toSum.sum(axis=0, skipna=True) Iwa = (summation / len(poa_global)) ** (1 / p) return Iwa
[docs] def arrhenius_deg( weather_df: pd.DataFrame, meta: dict, rh_outdoor, I_chamber, rh_chamber, Ea, temp_chamber, poa=None, temp=None, p=0.5, n=1, temp_model="sapm", conf="open_rack_glass_polymer", wind_factor=0.33, model_kwarg={}, irradiance_kwarg={}, ): """ Calculate the Acceleration Factor between the rate of degradation of a modeled environment versus a modeled controlled environment. Example: If AF=25, then 1 year of Controlled Environment exposure is equal to 25 years in the field. Parameters ---------- weather_df : pd.DataFrame DataFrame containing at least dni, dhi, ghi, temperature, wind_speed meta : dict Location meta-data containing at least latitude, longitude, altitude rh_outdoor : pd.Series Relative Humidity of material of interest. Acceptable relative humiditys can be calculated from these functions: - pvdeg.humidity.backsheet() - pvdeg.humidity.back_encapsulant() - pvdeg.humidity.front_encapsulant() - pvdeg.humidity.surface_relative() I_chamber : float Irradiance of Controlled Condition [W/m²] rh_chamber : float Relative Humidity of Controlled Condition [%]. EXAMPLE: "50 = 50% NOT .5 = 50%" temp_chamber : float Reference temperature [°C] ("Chamber Temperature") Ea : float Degradation Activation Energy [kJ/mol] if Ea=0 is used there will be not dependence on temperature and degradation will proceed according to the amount of light and humidity. poa : pd.DataFrame, optional Global Plane of Array Irradiance [W/m²] temp : pd.Series, optional Solar module temperature or Cell temperature [°C]. If no cell temperature is given, it will be generated using the default parameters from pvdeg.temperature.cell p : float Fit parameter When p=0 the dependence on light will be ignored and degradation will happen both day and night. As a caution or a feature, a very small value of p (e.g. p=0.0001) will provide very little degradation dependence on irradiance, but degradation will only be accounted for during daylight. i.e. averages will be computed over half of the time only. n : float Fit parameter for relative humidity When n=0 the degradation rate will not be dependent on humidity. temp_model : (str, optional) Specify which temperature model from pvlib to use. Current options: conf : (str) The configuration of the PV module architecture and mounting configuration. Currently only used for 'sapm' and 'pvsys'. With different options for each. 'sapm' options: ``open_rack_glass_polymer`` (default), ``open_rack_glass_glass``, ``close_mount_glass_glass``, ``insulated_back_glass_polymer`` 'pvsys' options: ``freestanding``, ``insulated`` wind_factor : float, optional Wind speed correction exponent to account for different wind speed measurement heights between weather database (e.g. NSRDB) and the temperature 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. irradiance_kwarg : (dict, optional) keyword argument dictionary used for the poa irradiance calculation. options: ``sol_position``, ``tilt``, ``azimuth``, ``sky_model``. See ``pvdeg.spectral.poa_irradiance``. model_kwarg : (dict, optional) keyword argument dictionary used for the pvlib temperature model calculation. See https://pvlib-python.readthedocs.io/en/stable/reference/pv_modeling/temperature.html # noqa for more. Returns ------- accelerationFactor : float or pd.Series Degradation acceleration factor """ if poa is None: poa = spectral.poa_irradiance(weather_df, meta, **irradiance_kwarg) if temp is None: temp = temperature.temperature( cell_or_mod="cell", temp_model=temp_model, weather_df=weather_df, meta=meta, poa=poa, conf=conf, wind_factor=wind_factor, model_kwarg=model_kwarg, ) if isinstance(poa, pd.DataFrame): poa_global = poa["poa_global"] else: poa_global = poa # rate of degradation of the environment arrheniusDenominator = ( (poa_global**p) * (rh_outdoor**n) * np.exp(-Ea / (R_GAS * (temp + 273.15))) ) AvgOfDenominator = arrheniusDenominator.mean() # rate of degradation of the simulated chamber arrheniusNumerator = ( (I_chamber**p) * (rh_chamber**n) * np.exp(-Ea / (R_GAS * (temp_chamber + 273.15))) ) accelerationFactor = arrheniusNumerator / AvgOfDenominator return accelerationFactor
def _T_eq_arrhenius(temp, Ea): """ Get Temperature equivalent required for the settings of the controlled environment. Calculation is used in determining Arrhenius Environmental Characterization Parameters ----------- temp : pandas series Solar module temperature or Cell temperature [°C] Ea : float Degradation Activation Energy [kJ/mol] Returns ------- Teq : float Temperature equivalent (Celsius) required for the settings of the controlled environment """ summationFrame = np.exp(-(Ea / (R_GAS * (temp + 273.15)))) sumForTeq = summationFrame.sum(axis=0, skipna=True) Teq = -((Ea) / (R_GAS * np.log(sumForTeq / len(temp)))) # Convert to celsius Teq = Teq - 273.15 return Teq def _RH_wa_arrhenius(rh_outdoor, temp, Ea, Teq=None, n=1): """ NOTE Get the Relative Humidity Weighted Average. Calculation is used in determining Arrhenius Environmental Characterization Parameters ----------- rh_outdoor : pandas series Relative Humidity of material of interest. Acceptable relative humiditys can be calculated from these functions: - pvdeg.humidity.backsheet() - pvdeg.humidity.back_encapsulant() - pvdeg.humidity.front_encapsulant() - pvdeg.humidity.surface_relative() temp : pandas series solar module temperature or Cell temperature [°C] Ea : float Degradation Activation Energy [kJ/mol] Teq : series Equivalent Arrhenius temperature [°C] n : float Fit parameter for relative humidity Returns -------- RHwa : float Relative Humidity Weighted Average [%] """ if Teq is None: Teq = _T_eq_arrhenius(temp, Ea) summationFrame = (rh_outdoor**n) * np.exp(-(Ea / (R_GAS * (temp + 273.15)))) sumForRHwa = summationFrame.sum(axis=0, skipna=True) RHwa = ( sumForRHwa / (len(summationFrame) * np.exp(-(Ea / (R_GAS * (Teq + 273.15))))) ) ** (1 / n) return RHwa # TODO: CHECK # STANDARDIZE
[docs] def IwaArrhenius( weather_df: pd.DataFrame, meta: dict, rh_outdoor: pd.Series, Ea: float, poa: pd.DataFrame = None, temp: pd.Series = None, RHwa: float = None, Teq: float = None, p: float = 0.5, n: float = 1, temp_model="sapm", conf="open_rack_glass_polymer", wind_factor=0.33, model_kwarg={}, irradiance_kwarg={}, ) -> float: """ Function to calculate IWa, the Environment Characterization [W/m²]. For one year of degradation the controlled environment lamp settings will need to be set at IWa. Parameters ---------- weather_df : pd.DataFrame Dataframe containing at least dni, dhi, ghi, temperature, wind_speed meta : dict Location meta-data containing at least latitude, longitude, altitude rh_outdoor : pd.Series Relative Humidity of material of interest Acceptable relative humiditys can be calculated from these functions: - pvdeg.humidity.backsheet() - pvdeg.humidity.back_encapsulant() - pvdeg.humidity.front_encapsulant() - pvdeg.humidity.surface_relative() Ea : float Degradation Activation Energy [kJ/mol] poa : pd.DataFrame, optional must contain 'poa_global', Global Plane of Array irradiance [W/m²] temp : pd.Series, optional Solar module temperature or Cell temperature [°C] RHwa : float, optional Relative Humidity Weighted Average [%] Teq : float, optional Temperature equivalent (Celsius) required for the settings of the controlled environment p : float Fit parameter n : float Fit parameter for relative humidity temp_model : (str, optional) Specify which temperature model from pvlib to use. Current options: conf : (str) The configuration of the PV module architecture and mounting configuration. Currently only used for 'sapm' and 'pvsys'. With different options for each. 'sapm' options: ``open_rack_glass_polymer`` (default), ``open_rack_glass_glass``, ``close_mount_glass_glass``, ``insulated_back_glass_polymer`` 'pvsys' options: ``freestanding``, ``insulated`` wind_factor : float, optional Wind speed correction exponent to account for different wind speed measurement heights between weather database (e.g. NSRDB) and the temperature 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. irradiance_kwarg : (dict, optional) keyword argument dictionary used for the poa irradiance calculation. options: ``sol_position``, ``tilt``, ``azimuth``, ``sky_model``. See ``pvdeg.spectral.poa_irradiance``. model_kwarg : (dict, optional) keyword argument dictionary used for the pvlib temperature model calculation. See https://pvlib-python.readthedocs.io/en/stable/reference/pv_modeling/temperature.html # noqa for more. Returns -------- Iwa : float Environment Characterization [W/m²] """ if poa is None: poa = spectral.poa_irradiance(weather_df, meta, **irradiance_kwarg) if temp is None: temp = temperature.temperature( cell_or_mod="cell", temp_model=temp_model, weather_df=weather_df, meta=meta, poa=poa, conf=conf, wind_factor=wind_factor, model_kwarg=model_kwarg, ) if Teq is None: Teq = _T_eq_arrhenius(temp, Ea) if RHwa is None: RHwa = _RH_wa_arrhenius(rh_outdoor, temp, Ea) if isinstance(poa, pd.DataFrame): poa_global = poa["poa_global"] else: poa_global = poa numerator = ( poa_global ** (p) * rh_outdoor ** (n) * np.exp(-(Ea / (R_GAS * (temp + 273.15)))) ) sumOfNumerator = numerator.sum(axis=0, skipna=True) denominator = ( (len(numerator)) * ((RHwa) ** n) * (np.exp(-(Ea / (R_GAS * (Teq + 273.15))))) ) IWa = (sumOfNumerator / denominator) ** (1 / p) return IWa
[docs] def degradation_spectral( spectra: pd.Series, rh: pd.Series, temp: pd.Series, wavelengths: Union[int, np.ndarray], time: pd.Series, Ea: float = 0.0, n: float = 0.0, p: float = 0.6, C2: float = 0.07, R_0: float = 1.0, ) -> float: """ Compute degradation as double integral of Arrhenius (Activation Energy, RH, Temperature) and spectral (wavelength, irradiance) functions over wavelength and time. Parameters ---------- spectra : pd.Series type=Float front or rear irradiance at each wavelength in "wavelengths" [W/m² nm] rh : pd.Series type=Float RH, time indexed [%] temp : pd.Series type=Float temperature, time indexed [°C] wavelengths : int-array integer array (or list) of wavelengths tested w/ uniform delta in nanometers [nm] time : time indicator in [h] if not included it will assume 1 h for each dataframe entry. Ea : float [kJ/mol] Arrhenius activation energy. The default is 0 ofr no dependence n : float Power law fit paramter for RH sensitivity. The default is 0 for no dependence. p : float Power law fit parameter for irradiance sensitivity. Typically 0.6 +- 0.22. Here it is applied separately for each wavelength bin. C2 : float Exponential fit parameter for sensitivity to wavelength. Typically 0.07 [1/nm] R_0 : float Prefactor for degradation. Units can vary, but would be something like [%/h] Default 1.0 Returns ------- degradation : float Total degradation over time and wavelength. Units are determined from R_0 and time. """ # --- TO DO --- # unpack input-dataframe # spectra = df['spectra'] # temp_module = df['temp_module'] # rh_module = df['rh_module'] wav_bin = list(np.diff(wavelengths)) wav_bin.append(wav_bin[-1]) # Adding a bin for the last wavelength try: irr = pd.DataFrame(spectra.tolist(), index=spectra.index) irr.columns = wavelengths except Exception: print("Removing brackets from spectral irradiance data") irr = spectra.str.strip("[]").str.split(",", expand=True).astype(float) irr.columns = wavelengths sensitivitywavelengths = np.exp(-C2 * np.array(wavelengths)) irr = irr * sensitivitywavelengths irr *= np.array(wav_bin) irr = irr**p data = pd.DataFrame(index=spectra.index) data["G_integral"] = irr.sum(axis=1) EApR = -Ea / R_GAS C4 = np.exp(EApR / temp) RHn = rh**n data["Arr_integrand"] = C4 * RHn data["dD"] = data["G_integral"] * data["Arr_integrand"] degradation = R_0 * data["dD"].sum(axis=0) return degradation
[docs] def vecArrhenius( poa_global: np.ndarray, module_temp: np.ndarray, ea: float, x: float, lnr0: float ) -> float: """ Calculates degradation using :math:`R_D = R_0 * I^X * e^{-Ea/(kT)}` Parameters ---------- poa_global : numpy.ndarray Plane of array irradiance [W/m²] module_temp : numpy.ndarray Cell temperature [°C]. ea : float Activation energy [kJ/mol] x : float Irradiance relation [unitless] lnr0 : float prefactor [ln(%/h)] Returns ---------- degradation : float Degradation Rate [%/h] """ mask = poa_global >= 25 poa_global = poa_global[mask] module_temp = module_temp[mask] ea_scaled = ea / 8.31446261815324e-03 R0 = np.exp(lnr0) poa_global_scaled = poa_global / 1000 degradation = 0 for entry in range(len(poa_global_scaled)): degradation += ( R0 * np.exp(-ea_scaled / (273.15 + module_temp[entry])) * np.power(poa_global_scaled[entry], x) ) return degradation / len(poa_global)