Skip to content

Commit 7139e29

Browse files
author
Tobin Ford
committed
changed degradation.degradation signature, @njit
1 parent f7469ec commit 7139e29

File tree

3 files changed

+532
-83
lines changed

3 files changed

+532
-83
lines changed

pvdeg/degradation.py

+212-49
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
from . import spectral
1414
from . import weather
1515

16-
from pvdeg.decorators import geospatial_quick_shape
16+
from typing import Optional
17+
from pvdeg.decorators import geospatial_quick_shape, deprecated
1718

1819
# TODO: Clean up all those functions and add gaps functionality
1920

@@ -862,33 +863,70 @@ def _gJtoMJ(gJ):
862863
return MJ
863864

864865

866+
# new version of degradation
865867
def degradation(
866-
spectra: pd.Series,
867-
rh_module: pd.Series,
868-
temp_module: pd.Series,
869-
wavelengths: Union[int, np.ndarray[float]],
868+
spectra_df: pd.DataFrame,
869+
conditions_df: pd.DataFrame = None,
870+
temp_module: pd.Series = None,
871+
rh_module: pd.Series = None,
870872
Ea: float = 40.0,
871873
n: float = 1.0,
872874
p: float = 0.5,
873875
C2: float = 0.07,
874876
C: float = 1.0,
875-
) -> float:
877+
)-> float:
876878
"""
877879
Compute degredation as double integral of Arrhenius (Activation
878880
Energy, RH, Temperature) and spectral (wavelength, irradiance)
879881
functions over wavelength and time.
880882
883+
.. math::
884+
885+
D = C \\int_{0}^{t} RH(t)^n \\cdot e^{\\frac{-E_a}{RT(t)}} \\int_{\\lambda} [e^{-C_2 \\lambda} \\cdot G(\\lambda, t)]^p d\\lambda dt
886+
881887
Parameters
882888
----------
883-
spectra : pd.Series type=Float
884-
front or rear irradiance at each wavelength in "wavelengths" [W/m^2 nm]
885-
rh_module : pd.Series type=Float
886-
module RH, time indexed [%]
887-
temp_module : pd.Series type=Float
888-
module temperature, time indexed [C]
889-
wavelengths : int-array
890-
integer array (or list) of wavelengths tested w/ uniform delta
891-
in nanometers [nm]
889+
spectra_df : pd.DataFrame
890+
front or rear irradiance data in dataframe format
891+
892+
- `data`: Spectral irradiance values for each wavelength [W/m^2 nm].
893+
- `index`: pd.DateTimeIndex
894+
- `columns`: Wavelengths as floats (e.g., 280, 300, etc.).
895+
896+
Example::
897+
898+
timestamp 280 300 320 340 360 380 400
899+
2021-03-09 10:00:00 0.6892 0.4022 0.6726 0.0268 0.3398 0.9432 0.7411
900+
2021-03-09 11:00:00 0.1558 0.5464 0.6896 0.7828 0.5050 0.9336 0.4652
901+
2021-03-09 12:00:00 0.2278 0.9057 0.2639 0.0572 0.9906 0.9370 0.1800
902+
2021-03-09 13:00:00 0.3742 0.0358 0.4052 0.9578 0.1044 0.8917 0.4876
903+
904+
conditions_df : pd.DataFrame, optional
905+
Environmental conditions including temperature and relative humidity.
906+
907+
- `index`: pd.DateTimeIndex
908+
- `columns`: (required)
909+
- "temperature" [°C or K]
910+
- "relative_humidity" [%]
911+
912+
Example::
913+
914+
timestamp temperature relative_humidity
915+
2021-03-09 10:00:00 298.0 45.0
916+
2021-03-09 11:00:00 303.0 50.0
917+
2021-03-09 12:00:00 310.0 55.0
918+
2021-03-09 13:00:00 315.0 60.0
919+
920+
temp_module : pd.Series, optional
921+
Module temperatures [°C]. Required if `conditions_df` is not provided. Time indexed same as spectra_df
922+
923+
rh_module : pd.Series, optional
924+
Relative humidity values [%]. Required if `conditions_df` is not provided. Time indexed same as spectra_df
925+
926+
Example::
927+
928+
30 = 30%
929+
892930
Ea : float
893931
Arrhenius activation energy. The default is 40. [kJ/mol]
894932
n : float
@@ -907,58 +945,183 @@ def degradation(
907945
-------
908946
degradation : float
909947
Total degredation factor over time and wavelength.
910-
911948
"""
912-
# --- TO DO ---
913-
# unpack input-dataframe
914-
# spectra = df['spectra']
915-
# temp_module = df['temp_module']
916-
# rh_module = df['rh_module']
917949

918-
# Constants
919-
R = 0.0083145 # Gas Constant in [kJ/mol*K]
920950

921-
wav_bin = list(np.diff(wavelengths))
922-
wav_bin.append(wav_bin[-1]) # Adding a bin for the last wavelength
951+
if conditions_df is not None and (temp_module is not None or rh_module is not None):
952+
raise ValueError("Provide either conditions_df or temp_module and rh_module")
923953

924-
# Integral over Wavelength
925-
try:
926-
irr = pd.DataFrame(spectra.tolist(), index=spectra.index)
927-
irr.columns = wavelengths
928-
except:
929-
# TODO: Fix this except it works on some cases, veto it by cases
930-
print("Removing brackets from spectral irradiance data")
931-
# irr = data['spectra'].str.strip('[]').str.split(',', expand=True).astype(float)
932-
irr = spectra.str.strip("[]").str.split(",", expand=True).astype(float)
933-
irr.columns = wavelengths
954+
if conditions_df is not None:
955+
rh = conditions_df["relative_humidity"].values
956+
temps = conditions_df["temperature"].values
957+
else:
958+
rh = rh_module.values
959+
temps = temp_module.values
960+
961+
wavelengths = spectra_df.columns.values.astype(float)
962+
irr = spectra_df.values # irradiance as array
963+
964+
# call numba compiled function
965+
return deg(
966+
wavelengths=wavelengths,
967+
irr=irr,
968+
rh=rh,
969+
temps=temps,
970+
Ea=Ea,
971+
C2=C2,
972+
p=p,
973+
n=n,
974+
C=C
975+
)
934976

935-
sensitivitywavelengths = np.exp(-C2 * wavelengths)
936-
irr = irr * sensitivitywavelengths
937-
irr *= np.array(wav_bin)
938-
irr = irr**p
939-
data = pd.DataFrame(index=spectra.index)
940-
data["G_integral"] = irr.sum(axis=1)
977+
@njit
978+
def deg(
979+
wavelengths: np.ndarray,
980+
irr: np.ndarray,
981+
rh: np.ndarray,
982+
temps: np.ndarray,
983+
Ea: float,
984+
C2: float,
985+
p: float,
986+
n: float,
987+
C: float
988+
) -> float:
941989

942-
EApR = -Ea / R
943-
C4 = np.exp(EApR / temp_module)
990+
R = 0.0083145 # Gas Constant in [kJ/mol*K]
944991

945-
RHn = rh_module**n
946-
data["Arr_integrand"] = C4 * RHn
992+
wav_bin = np.diff(wavelengths)
993+
wav_bin = np.append(wav_bin, wav_bin[-1]) # Extend last bin
994+
995+
# inner integral
996+
# wavelength d lambda
997+
irr_weighted = irr * np.exp(-C2 * wavelengths) # weight irradiances
998+
irr_weighted *= wav_bin
999+
irr_pow = irr_weighted ** p
1000+
wavelength_integral = np.sum(irr_pow, axis=1) # sum over wavelengths
9471001

948-
data["dD"] = data["G_integral"] * data["Arr_integrand"]
1002+
# outer integral
1003+
# arrhenius integral dt
1004+
time_integrand = (rh ** n) * np.exp(-Ea / (R * temps))
9491005

950-
degradation = C * data["dD"].sum(axis=0)
1006+
dD = wavelength_integral * time_integrand
1007+
degradation = C * np.sum(dD)
9511008

9521009
return degradation
9531010

9541011

1012+
# @deprecated("old double integral degradation function will be replaced 'pvdegradation' in an updated version of pvdeg")
1013+
# def degradation(
1014+
# spectra: pd.Series,
1015+
# rh_module: pd.Series,
1016+
# temp_module: pd.Series,
1017+
# wavelengths: Union[int, np.ndarray[float]],
1018+
# Ea: float = 40.0,
1019+
# n: float = 1.0,
1020+
# p: float = 0.5,
1021+
# C2: float = 0.07,
1022+
# C: float = 1.0,
1023+
# ) -> float:
1024+
# """
1025+
# Compute degredation as double integral of Arrhenius (Activation
1026+
# Energy, RH, Temperature) and spectral (wavelength, irradiance)
1027+
# functions over wavelength and time.
1028+
1029+
# .. math::
1030+
1031+
# D = C \\int_{0}^{t} RH(t)^n \\cdot e^{\\frac{-E_a}{RT(t)}} \\int_{\\lambda} [e^{-C_2 \\lambda} \\cdot G(\\lambda, t)]^p d\\lambda dt
1032+
1033+
# Parameters
1034+
# ----------
1035+
# spectra : pd.Series type=Float
1036+
# front or rear irradiance at each wavelength in "wavelengths" [W/m^2 nm]
1037+
# rh_module : pd.Series type=Float
1038+
# module RH, time indexed [%]
1039+
# temp_module : pd.Series type=Float
1040+
# module temperature, time indexed [C]
1041+
# wavelengths : int-array
1042+
# integer array (or list) of wavelengths tested w/ uniform delta
1043+
# in nanometers [nm]
1044+
# Ea : float
1045+
# Arrhenius activation energy. The default is 40. [kJ/mol]
1046+
# n : float
1047+
# Fit paramter for RH sensitivity. The default is 1.
1048+
# p : float
1049+
# Fit parameter for irradiance sensitivity. Typically
1050+
# 0.6 +- 0.22
1051+
# C2 : float
1052+
# Fit parameter for sensitivity to wavelength exponential.
1053+
# Typically 0.07
1054+
# C : float
1055+
# Fit parameter for the Degradation equaiton
1056+
# Typically 1.0
1057+
1058+
# Returns
1059+
# -------
1060+
# degradation : float
1061+
# Total degredation factor over time and wavelength.
1062+
# """
1063+
# # --- TO DO ---
1064+
# # unpack input-dataframe
1065+
# # spectra = df['spectra']
1066+
# # temp_module = df['temp_module']
1067+
# # rh_module = df['rh_module']
1068+
1069+
# # Constants
1070+
# R = 0.0083145 # Gas Constant in [kJ/mol*K]
1071+
1072+
# wav_bin = list(np.diff(wavelengths))
1073+
# wav_bin.append(wav_bin[-1]) # Adding a bin for the last wavelength
1074+
1075+
# # Integral over Wavelength
1076+
# try:
1077+
# irr = pd.DataFrame(spectra.tolist(), index=spectra.index)
1078+
# irr.columns = wavelengths
1079+
# except:
1080+
# # TODO: Fix this except it works on some cases, veto it by cases
1081+
# print("Removing brackets from spectral irradiance data")
1082+
# # irr = data['spectra'].str.strip('[]').str.split(',', expand=True).astype(float)
1083+
# irr = spectra.str.strip("[]").str.split(",", expand=True).astype(float)
1084+
# irr.columns = wavelengths
1085+
1086+
1087+
# # double integral calculation
1088+
# sensitivitywavelengths = np.exp(-C2 * wavelengths)
1089+
# irr = irr * sensitivitywavelengths
1090+
# irr *= np.array(wav_bin)
1091+
# irr = irr**p
1092+
# data = pd.DataFrame(index=spectra.index)
1093+
# data["G_integral"] = irr.sum(axis=1)
1094+
1095+
# EApR = -Ea / R
1096+
# C4 = np.exp(EApR / temp_module)
1097+
1098+
# RHn = rh_module**n
1099+
1100+
# data["Arr_integrand"] = C4 * RHn
1101+
1102+
# print("arr integral", data["Arr_integrand"])
1103+
# print("wavelength integral", data["G_integral"] )
1104+
1105+
# data["dD"] = data["G_integral"] * data["Arr_integrand"]
1106+
1107+
# print(f"delta degradation ", data["dD"])
1108+
1109+
# degradation = C * data["dD"].sum(axis=0)
1110+
1111+
# return degradation
1112+
1113+
9551114
# change it to take pd.DataFrame? instead of np.ndarray
9561115
@njit
9571116
def vecArrhenius(
9581117
poa_global: np.ndarray, module_temp: np.ndarray, ea: float, x: float, lnr0: float
9591118
) -> float:
9601119
"""
961-
Calculates degradation using :math:`R_D = R_0 * I^X * e^{\\frac{-Ea}{kT}}`
1120+
Calculate arrhenius degradation using vectorized operations. To eliminate the irradiance term set the irradiance sensitivity to 0.
1121+
1122+
.. math::
1123+
1124+
R_D = R_0 \\cdot I^X \\cdot e^{\\frac{-E_a}{kT}}
9621125
9631126
Parameters
9641127
----------
@@ -972,7 +1135,7 @@ def vecArrhenius(
9721135
Activation energy [kJ/mol]
9731136
9741137
x : float
975-
Irradiance relation [unitless]
1138+
Irradiance sensitivity [unitless]
9761139
9771140
lnR0 : float
9781141
prefactor [ln(%/h)]

tests/test_degradation.py

+18-4
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,26 @@ def test_degradation():
131131

132132
data = pd.read_csv(INPUT_SPECTRA)
133133
wavelengths = np.array(range(280, 420, 20))
134+
135+
# convert to expected format
136+
spectra = data["Spectra"]
137+
spectra_df = pd.DataFrame(spectra.tolist(), index=spectra.index)
138+
spectra_df = spectra.str.strip("[]").str.split(",", expand=True).astype(float)
139+
spectra_df.columns = wavelengths
140+
141+
conditions_df = pd.DataFrame(
142+
index=spectra_df.index,
143+
data={
144+
"relative_humidity": data["RH"],
145+
"temperature": data["Temperature"],
146+
}
147+
)
148+
134149
degradation = pvdeg.degradation.degradation(
135-
spectra=data["Spectra"],
136-
rh_module=data["RH"],
137-
temp_module=data["Temperature"],
138-
wavelengths=wavelengths,
150+
spectra_df=spectra_df,
151+
conditions_df=conditions_df
139152
)
153+
140154
assert degradation == pytest.approx(4.4969e-38, abs=0.02e-38)
141155

142156

0 commit comments

Comments
 (0)