"""Collection of functions for degradation calculations.
"""
import numpy as np
import pandas as pd
from numba import jit, njit
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
# TODO: Clean up all those functions and add gaps functionality
def _deg_rate_env(poa_global, temp, temp_chamber, p, Tf):
"""
Helper function. Find the rate of degradation kenetics using the Fischer model.
Degradation kentics model interpolated 50 coatings with respect to
color shift, cracking, gloss loss, fluorescense loss,
retroreflectance loss, adhesive transfer, and shrinkage.
(ADD IEEE reference)
Parameters
------------
poa_global : float
(Global) Plan of Array irradiance [W/m²]
temp : float
Solar module temperature [°C]
temp_chamber : float
Reference temperature [°C] "Chamber Temperature"
p : float
Fit parameter
Tf : float
Multiplier for the increase in degradation
for every 10[°C] temperature increase
Returns
--------
degradationrate : float
rate of Degradation (NEED TO ADD METRIC)
"""
return poa_global ** (p) * Tf ** ((temp - temp_chamber) / 10)
def _deg_rate_chamber(I_chamber, p):
"""
Helper function. Find the rate of degradation kenetics of a simulated chamber. Mike Kempe's
calculation of the rate of degradation inside a accelerated degradation chamber.
(ADD IEEE reference)
Parameters
----------
I_chamber : float
Irradiance of Controlled Condition W/m²
p : float
Fit parameter
Returns
--------
chamberdegradationrate : float
Degradation rate of chamber
"""
chamberdegradationrate = I_chamber ** (p)
return chamberdegradationrate
def _acceleration_factor(numerator, denominator):
"""
Helper Function. Find the acceleration factor
(ADD IEEE reference)
Parameters
----------
numerator : float
Typically the numerator is the chamber settings
denominator : float
Typically the TMY data summation
Returns
-------
chamberAccelerationFactor : float
Acceleration Factor of chamber (NEED TO ADD METRIC)
"""
chamberAccelerationFactor = numerator / denominator
return chamberAccelerationFactor
[docs]
def vantHoff_deg(
weather_df, meta, I_chamber, temp_chamber, poa=None, temp=None, p=0.5, Tf=1.41
):
"""
Van't Hoff Irradiance Degradation
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 : series or data frame, optional
dataframe containing 'poa_global', Global Plane of Array Irradiance [W/m²]
temp : pandas series, optional
Solar module temperature or Cell temperature [°C]. If no cell temperature is given, it will
be generated using the default paramters of pvdeg.temperature.cell
p : float
fit parameter
Tf : float
Multiplier for the increase in degradation for every 10[°C] temperature increase
Returns
-------
accelerationFactor : float or series
Degradation acceleration factor
"""
if poa is None:
poa = spectral.poa_irradiance(weather_df, meta)
if isinstance(poa, pd.DataFrame):
poa_global = poa["poa_global"]
if temp is None:
temp = temperature.cell(weather_df=weather_df, meta=meta, poa=poa)
rateOfDegEnv = _deg_rate_env(
poa_global=poa_global, temp=temp, temp_chamber=temp_chamber, p=p, Tf=Tf
)
# sumOfDegEnv = rateOfDegEnv.sum(axis = 0, skipna = True)
avgOfDegEnv = rateOfDegEnv.mean()
rateOfDegChamber = _deg_rate_chamber(I_chamber, p)
accelerationFactor = _acceleration_factor(rateOfDegChamber, avgOfDegEnv)
return accelerationFactor
def _to_eq_vantHoff(temp, Tf=1.41):
"""
Function to obtain the Vant Hoff temperature equivalent [°C]
Parameters
----------
Tf : float
Multiplier for the increase in degradation for every 10[°C] temperature increase. Default value of 1.41.
temp : pandas series
Solar module surface or Cell temperature [°C]
Returns
-------
Toeq : float
Vant Hoff temperature equivalent [°C]
"""
toSum = Tf ** (temp / 10)
summation = toSum.sum(axis=0, skipna=True)
Toeq = (10 / np.log(Tf)) * np.log(summation / len(temp))
return Toeq
[docs]
def IwaVantHoff(weather_df, meta, poa=None, temp=None, Teq=None, p=0.5, Tf=1.41):
"""
IWa : Environment Characterization [W/m²]
For one year of degredation the controlled environmnet 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 : float series or dataframe
Series or dataframe containing 'poa_global', Global Plane of Array Irradiance W/m²
temp : float series
Solar module temperature or Cell temperature [°C]
Teq : series
VantHoff equivalent temperature [°C]
p : float
Fit parameter
Tf : float
Multiplier for the increase in degradation for every 10[°C] temperature increase
Returns
--------
Iwa : float
Environment Characterization [W/m²[]
"""
if poa is None:
poa = spectral.poa_irradiance(weather_df, meta)
if temp is None:
temp = temperature.cell(weather_df, meta, poa)
if Teq is None:
Teq = _to_eq_vantHoff(temp, Tf)
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
def _arrhenius_denominator(poa_global, rh_outdoor, temp, Ea, p, n):
"""
Helper function. Calculates the rate of degredation of the Environmnet
Parameters
----------
poa_global : float series
(Global) Plan of Array irradiance [W/m²]
p : float
Fit parameter
rh_outdoor : pandas series
Relative Humidity of material of interest. Acceptable relative
humiditys can be calculated from these functions: rh_backsheet(),
rh_back_encap(); rh_front_encap(); rh_surface_outside()
n : float
Fit parameter for relative humidity
temp : pandas series
Solar module temperature or Cell temperature [°C]
Ea : float
Degredation Activation Energy [kJ/mol]
Returns
-------
environmentDegradationRate : pandas series
Degradation rate of environment
"""
environmentDegradationRate = (
poa_global ** (p)
* rh_outdoor ** (n)
* np.exp(-(Ea / (0.00831446261815324 * (temp + 273.15))))
)
return environmentDegradationRate
def _arrhenius_numerator(I_chamber, rh_chamber, temp_chamber, Ea, p, n):
"""
Helper function. Find the rate of degradation of a simulated chamber.
Parameters
----------
I_chamber : float
Irradiance of Controlled Condition [W/m²]
Rhchamber : float
Relative Humidity of Controlled Condition [%]
EXAMPLE: "50 = 50% NOT .5 = 50%"
temp_chamber : float
Reference temperature [°C] "Chamber Temperature"
Ea : float
Degredation Activation Energy [kJ/mol]
p : float
Fit parameter
n : float
Fit parameter for relative humidity
Returns
--------
arrheniusNumerator : float
Degradation rate of the chamber
"""
arrheniusNumerator = (
I_chamber ** (p)
* rh_chamber ** (n)
* np.exp(-(Ea / (0.00831446261815324 * (temp_chamber + 273.15))))
)
return arrheniusNumerator
[docs]
def arrhenius_deg(
weather_df,
meta,
rh_outdoor,
I_chamber,
rh_chamber,
Ea,
temp_chamber,
poa=None,
temp=None,
p=0.5,
n=1,
):
"""
Calculate the Acceleration Factor between the rate of degredation of a
modeled environmnet versus a modeled controlled environmnet. Example: "If the 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 : float series
Relative Humidity of material of interest
Acceptable relative humiditys can be calculated
from these functions: rh_backsheet(), rh_back_encap(), rh_front_encap(),
rh_surface_outside()
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
Degredation 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 an 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.
Returns
--------
accelerationFactor : pandas series
Degradation acceleration factor
"""
if poa is None:
poa = spectral.poa_irradiance(weather_df, meta)
if temp is None:
temp = temperature.cell(weather_df, meta, poa)
if isinstance(poa, pd.DataFrame):
poa_global = poa["poa_global"]
else:
poa_global = poa
arrheniusDenominator = _arrhenius_denominator(
poa_global=poa_global, rh_outdoor=rh_outdoor, temp=temp, Ea=Ea, p=p, n=n
)
AvgOfDenominator = arrheniusDenominator.mean()
arrheniusNumerator = _arrhenius_numerator(
I_chamber=I_chamber,
rh_chamber=rh_chamber,
temp_chamber=temp_chamber,
Ea=Ea,
p=p,
n=n,
)
accelerationFactor = _acceleration_factor(arrheniusNumerator, AvgOfDenominator)
return accelerationFactor
def _T_eq_arrhenius(temp, Ea):
"""
Get the 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
Degredation Activation Energy [kJ/mol]
Returns
-------
Teq : float
Temperature equivalent (Celsius) required
for the settings of the controlled environment
"""
summationFrame = np.exp(-(Ea / (0.00831446261815324 * (temp + 273.15))))
sumForTeq = summationFrame.sum(axis=0, skipna=True)
Teq = -((Ea) / (0.00831446261815324 * 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 the below functions:
rh_backsheet(), rh_back_encap(), rh_front_encap(), rh_surface_outside()
temp : pandas series
solar module temperature or Cell temperature [°C]
Ea : float
Degredation 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 / (0.00831446261815324 * (temp + 273.15)))
)
sumForRHwa = summationFrame.sum(axis=0, skipna=True)
RHwa = (
sumForRHwa
/ (len(summationFrame) * np.exp(-(Ea / (0.00831446261815324 * (Teq + 273.15)))))
) ** (1 / n)
return RHwa
# TODO: CHECK
# STANDARDIZE
[docs]
def IwaArrhenius(
weather_df,
meta,
rh_outdoor,
Ea,
poa=None,
temp=None,
RHwa=None,
Teq=None,
p=0.5,
n=1,
):
"""
Function to calculate IWa, the Environment Characterization [W/m²].
For one year of degredation the controlled environmnet 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 include: rh_backsheet(), rh_back_encap(), rh_front_encap(),
rh_surface_outside()
Ea : float
Degradation Activation Energy [kJ/mol]
poa : pd.dataframe, optional
must contain 'poa_global', Global Plan 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
Returns
--------
Iwa : float
Environment Characterization [W/m²]
"""
if poa is None:
poa = spectral.poa_irradiance(weather_df, meta)
if temp is None:
temp = temperature.cell(weather_df, meta, poa)
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 / (0.00831446261815324 * (temp + 273.15))))
)
sumOfNumerator = numerator.sum(axis=0, skipna=True)
denominator = (
(len(numerator))
* ((RHwa) ** n)
* (np.exp(-(Ea / (0.00831446261815324 * (Teq + 273.15)))))
)
IWa = (sumOfNumerator / denominator) ** (1 / p)
return IWa
############
# Misc. Functions for Energy Calcs
############
def _rh_Above85(rh):
"""
Helper function. Determines if the relative humidity is above 85%.
Parameters
----------
rh : float
Relative Humidity %
Returns
--------
rhabove85 : boolean
True if the relative humidity is above 85% or False if the relative
humidity is below 85%
"""
if rh > 85:
rhabove85 = True
else:
rhabove85 = False
return rhabove85
def _hoursRH_Above85(df):
"""
Helper Function. Count the number of hours relative humidity is above 85%.
Parameters
----------
df : dataframe
DataFrame, dataframe containing Relative Humidity %
Returns
-------
numhoursabove85 : int
Number of hours relative humidity is above 85%
"""
booleanDf = df.apply(lambda x: _rh_Above85(x))
numhoursabove85 = booleanDf.sum()
return numhoursabove85
def _whToGJ(wh):
"""
NOTE: unused, remove?
Helper Function to convert Wh/m² to GJ/m²
Parameters
-----------
wh : float
Input Value in Wh/m²
Returns
-------
gj : float
Value in GJ/m²
"""
gj = 0.0000036 * wh
return gj
def _gJtoMJ(gJ):
"""
NOTE: unused, remove?
Helper Function to convert GJ/m² to MJ/y
Parameters
-----------
gJ : float
Value in GJ/m^-2
Returns
-------
MJ : float
Value in MJ/m^-2
"""
MJ = gJ * 1000
return MJ
[docs]
def degradation(
spectra, rh_module, temp_module, wavelengths, Ea=40.0, n=1.0, p=0.5, C2=0.07, C=1.0
):
"""
Compute degredation 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^2 nm]
rh_module : pd.Series type=Float
module RH, time indexed [%]
temp_module : pd.Series type=Float
module temperature, time indexed [C]
wavelengths : int-array
integer array (or list) of wavelengths tested w/ uniform delta
in nanometers [nm]
Ea : float
Arrhenius activation energy. The default is 40. [kJ/mol]
n : float
Fit paramter for RH sensitivity. The default is 1.
p : float
Fit parameter for irradiance sensitivity. Typically
0.6 +- 0.22
C2 : float
Fit parameter for sensitivity to wavelength exponential.
Typically 0.07
C : float
Fit parameter for the Degradation equaiton
Typically 1.0
Returns
-------
degradation : float
Total degredation factor over time and wavelength.
"""
# --- TO DO ---
# unpack input-dataframe
# spectra = df['spectra']
# temp_module = df['temp_module']
# rh_module = df['rh_module']
# Constants
R = 0.0083145 # Gas Constant in [kJ/mol*K]
wav_bin = list(np.diff(wavelengths))
wav_bin.append(wav_bin[-1]) # Adding a bin for the last wavelength
# Integral over Wavelength
try:
irr = pd.DataFrame(spectra.tolist(), index=spectra.index)
irr.columns = wavelengths
except:
# TODO: Fix this except it works on some cases, veto it by cases
print("Removing brackets from spectral irradiance data")
# irr = data['spectra'].str.strip('[]').str.split(',', expand=True).astype(float)
irr = spectra.str.strip("[]").str.split(",", expand=True).astype(float)
irr.columns = wavelengths
sensitivitywavelengths = np.exp(-C2 * 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
C4 = np.exp(EApR / temp_module)
RHn = rh_module**n
data["Arr_integrand"] = C4 * RHn
data["dD"] = data["G_integral"] * data["Arr_integrand"]
degradation = C * data["dD"].sum(axis=0)
return degradation
# change it to take pd.DataFrame? instead of np.ndarray
[docs]
@njit
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^{\\frac{-Ea}{kT}}`
Parameters
----------
poa_global : numpy.ndarray
Plane of array irradiance [W/m^2]
module_temp : numpy.ndarray
Cell temperature [C].
ea : float
Activation energy [kJ/mol]
x : float
Irradiance relation [unitless]
lnR0 : float
prefactor [ln(%/h)]
Returns
----------
degredation : 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
degredation = 0
# refactor to list comprehension approach
for entry in range(len(poa_global_scaled)):
degredation += (
R0
* np.exp(-ea_scaled / (273.15 + module_temp[entry]))
* np.power(poa_global_scaled[entry], x)
)
return degredation / len(poa_global)