13
13
from . import spectral
14
14
from . import weather
15
15
16
- from pvdeg .decorators import geospatial_quick_shape
16
+ from typing import Optional
17
+ from pvdeg .decorators import geospatial_quick_shape , deprecated
17
18
18
19
# TODO: Clean up all those functions and add gaps functionality
19
20
@@ -862,33 +863,70 @@ def _gJtoMJ(gJ):
862
863
return MJ
863
864
864
865
866
+ # new version of degradation
865
867
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 ,
870
872
Ea : float = 40.0 ,
871
873
n : float = 1.0 ,
872
874
p : float = 0.5 ,
873
875
C2 : float = 0.07 ,
874
876
C : float = 1.0 ,
875
- ) -> float :
877
+ )-> float :
876
878
"""
877
879
Compute degredation as double integral of Arrhenius (Activation
878
880
Energy, RH, Temperature) and spectral (wavelength, irradiance)
879
881
functions over wavelength and time.
880
882
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
+
881
887
Parameters
882
888
----------
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
+
892
930
Ea : float
893
931
Arrhenius activation energy. The default is 40. [kJ/mol]
894
932
n : float
@@ -907,58 +945,183 @@ def degradation(
907
945
-------
908
946
degradation : float
909
947
Total degredation factor over time and wavelength.
910
-
911
948
"""
912
- # --- TO DO ---
913
- # unpack input-dataframe
914
- # spectra = df['spectra']
915
- # temp_module = df['temp_module']
916
- # rh_module = df['rh_module']
917
949
918
- # Constants
919
- R = 0.0083145 # Gas Constant in [kJ/mol*K]
920
950
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" )
923
953
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
+ )
934
976
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 :
941
989
942
- EApR = - Ea / R
943
- C4 = np .exp (EApR / temp_module )
990
+ R = 0.0083145 # Gas Constant in [kJ/mol*K]
944
991
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
947
1001
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 ))
949
1005
950
- degradation = C * data ["dD" ].sum (axis = 0 )
1006
+ dD = wavelength_integral * time_integrand
1007
+ degradation = C * np .sum (dD )
951
1008
952
1009
return degradation
953
1010
954
1011
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
+
955
1114
# change it to take pd.DataFrame? instead of np.ndarray
956
1115
@njit
957
1116
def vecArrhenius (
958
1117
poa_global : np .ndarray , module_temp : np .ndarray , ea : float , x : float , lnr0 : float
959
1118
) -> float :
960
1119
"""
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}}
962
1125
963
1126
Parameters
964
1127
----------
@@ -972,7 +1135,7 @@ def vecArrhenius(
972
1135
Activation energy [kJ/mol]
973
1136
974
1137
x : float
975
- Irradiance relation [unitless]
1138
+ Irradiance sensitivity [unitless]
976
1139
977
1140
lnR0 : float
978
1141
prefactor [ln(%/h)]
0 commit comments