Skip to content

Commit 131337a

Browse files
author
Tobin Ford
committed
degradation: time dependent integral
1 parent 0ee3550 commit 131337a

File tree

3 files changed

+79
-69
lines changed

3 files changed

+79
-69
lines changed

pvdeg/degradation.py

+13-7
Original file line numberDiff line numberDiff line change
@@ -864,6 +864,7 @@ def _gJtoMJ(gJ):
864864

865865

866866
# new version of degradation
867+
# we can make this work with varying timedelta sizes
867868
def degradation(
868869
spectra_df: pd.DataFrame,
869870
conditions_df: pd.DataFrame = None,
@@ -878,7 +879,8 @@ def degradation(
878879
"""
879880
Compute degredation as double integral of Arrhenius (Activation
880881
Energy, RH, Temperature) and spectral (wavelength, irradiance)
881-
functions over wavelength and time.
882+
functions over wavelength and time. Using dataframe input with
883+
pd.TimeIndex with constant timedelta.
882884
883885
.. math::
884886
@@ -890,7 +892,7 @@ def degradation(
890892
front or rear irradiance data in dataframe format
891893
892894
- `data`: Spectral irradiance values for each wavelength [W/m^2 nm].
893-
- `index`: pd.DateTimeIndex
895+
- `index`: pd.DateTimeIndex with constant timedelta
894896
- `columns`: Wavelengths as floats (e.g., 280, 300, etc.) [nm].
895897
896898
Example::
@@ -904,9 +906,9 @@ def degradation(
904906
conditions_df : pd.DataFrame, optional
905907
Environmental conditions including temperature and relative humidity.
906908
907-
- `index`: pd.DateTimeIndex
909+
- `index`: pd.DateTimeIndex identical to spectra_df.index
908910
- `columns`: (required)
909-
- "temperature" [°C or K]
911+
- "temperature" [°K]
910912
- "relative_humidity" [%]
911913
912914
Example::
@@ -946,7 +948,8 @@ def degradation(
946948
degradation : float
947949
Total degredation factor over time and wavelength.
948950
"""
949-
951+
if not isinstance(spectra_df.index, pd.DatetimeIndex):
952+
raise ValueError(f"spectra_df must have pd.DatetimeIndex, has {type(spectra_df.index)}")
950953

951954
if conditions_df is not None and (temp_module is not None or rh_module is not None):
952955
raise ValueError("Provide either conditions_df or temp_module and rh_module")
@@ -960,26 +963,29 @@ def degradation(
960963

961964
wavelengths = spectra_df.columns.values.astype(float)
962965
irr = spectra_df.values # irradiance as array
966+
dt = (spectra_df.index[1] - spectra_df.index[0]).total_seconds() / 3600.0
963967

964968
# call numba compiled function
965969
return deg(
966970
wavelengths=wavelengths,
967971
irr=irr,
968972
rh=rh,
969973
temps=temps,
974+
dt=dt,
970975
Ea=Ea,
971976
C2=C2,
972977
p=p,
973978
n=n,
974979
C=C
975980
)
976981

977-
@njit
982+
# @njit
978983
def deg(
979984
wavelengths: np.ndarray,
980985
irr: np.ndarray,
981986
rh: np.ndarray,
982987
temps: np.ndarray,
988+
dt: float, # timestep in hours
983989
Ea: float,
984990
C2: float,
985991
p: float,
@@ -1003,7 +1009,7 @@ def deg(
10031009
# arrhenius integral dt
10041010
time_integrand = (rh ** n) * np.exp(-Ea / (R * temps))
10051011

1006-
dD = wavelength_integral * time_integrand
1012+
dD = wavelength_integral * time_integrand * dt
10071013
degradation = C * np.sum(dD)
10081014

10091015
return degradation

tests/sandbox.ipynb

+61-60
Original file line numberDiff line numberDiff line change
@@ -813,24 +813,9 @@
813813
},
814814
{
815815
"cell_type": "code",
816-
"execution_count": 3,
817-
"metadata": {},
818-
"outputs": [
819-
{
820-
"ename": "Failed",
821-
"evalue": "Invalid regex pattern provided to 'match': incomplete escape \\U at position 46",
822-
"output_type": "error",
823-
"traceback": [
824-
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
825-
"\u001b[1;31mFailed\u001b[0m Traceback (most recent call last)",
826-
"Cell \u001b[1;32mIn[3], line 10\u001b[0m\n\u001b[0;32m 4\u001b[0m invalid_name_or_alias \u001b[38;5;241m=\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mnamenotindict\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 5\u001b[0m expected_error_message \u001b[38;5;241m=\u001b[39m (\n\u001b[0;32m 6\u001b[0m \u001b[38;5;124mrf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname_or_alias: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00minvalid_name_or_alias\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m not in JSON at \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 7\u001b[0m \u001b[38;5;124mrf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mos\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mjoin(DATA_DIR,\u001b[38;5;250m \u001b[39mpvdeg\u001b[38;5;241m.\u001b[39mutilities\u001b[38;5;241m.\u001b[39mpvdeg_datafiles[pvdeg_file])\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 8\u001b[0m )\n\u001b[1;32m---> 10\u001b[0m \u001b[38;5;28;01mwith\u001b[39;00m \u001b[43mpytest\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mraises\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;167;43;01mValueError\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmatch\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mexpected_error_message\u001b[49m\u001b[43m)\u001b[49m:\n\u001b[0;32m 11\u001b[0m pvdeg\u001b[38;5;241m.\u001b[39mutilities\u001b[38;5;241m.\u001b[39msearch_json(pvdeg_file\u001b[38;5;241m=\u001b[39mpvdeg_file, name_or_alias\u001b[38;5;241m=\u001b[39minvalid_name_or_alias)\n",
827-
" \u001b[1;31m[... skipping hidden 1 frame]\u001b[0m\n",
828-
"File \u001b[1;32mc:\\Users\\tford\\AppData\\Local\\miniconda3\\envs\\deg\\lib\\site-packages\\_pytest\\python_api.py:997\u001b[0m, in \u001b[0;36mRaisesContext.__init__\u001b[1;34m(self, expected_exception, message, match_expr)\u001b[0m\n\u001b[0;32m 995\u001b[0m re_error \u001b[38;5;241m=\u001b[39m e\n\u001b[0;32m 996\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m re_error \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[1;32m--> 997\u001b[0m \u001b[43mfail\u001b[49m\u001b[43m(\u001b[49m\u001b[38;5;124;43mf\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mInvalid regex pattern provided to \u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mmatch\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43m: \u001b[39;49m\u001b[38;5;132;43;01m{\u001b[39;49;00m\u001b[43mre_error\u001b[49m\u001b[38;5;132;43;01m}\u001b[39;49;00m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n",
829-
"File \u001b[1;32mc:\\Users\\tford\\AppData\\Local\\miniconda3\\envs\\deg\\lib\\site-packages\\_pytest\\outcomes.py:178\u001b[0m, in \u001b[0;36mfail\u001b[1;34m(reason, pytrace)\u001b[0m\n\u001b[0;32m 165\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Explicitly fail an executing test with the given message.\u001b[39;00m\n\u001b[0;32m 166\u001b[0m \n\u001b[0;32m 167\u001b[0m \u001b[38;5;124;03m:param reason:\u001b[39;00m\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 175\u001b[0m \u001b[38;5;124;03m The exception that is raised.\u001b[39;00m\n\u001b[0;32m 176\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m 177\u001b[0m __tracebackhide__ \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mTrue\u001b[39;00m\n\u001b[1;32m--> 178\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m Failed(msg\u001b[38;5;241m=\u001b[39mreason, pytrace\u001b[38;5;241m=\u001b[39mpytrace)\n",
830-
"\u001b[1;31mFailed\u001b[0m: Invalid regex pattern provided to 'match': incomplete escape \\U at position 46"
831-
]
832-
}
833-
],
816+
"execution_count": null,
817+
"metadata": {},
818+
"outputs": [],
834819
"source": [
835820
"from pvdeg import DATA_DIR\n",
836821
"\n",
@@ -847,21 +832,9 @@
847832
},
848833
{
849834
"cell_type": "code",
850-
"execution_count": 2,
835+
"execution_count": null,
851836
"metadata": {},
852-
"outputs": [
853-
{
854-
"ename": "NameError",
855-
"evalue": "name 'invalid_name_or_alias' is not defined",
856-
"output_type": "error",
857-
"traceback": [
858-
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
859-
"\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
860-
"Cell \u001b[1;32mIn[2], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m expected_error_message \u001b[38;5;241m=\u001b[39m (\n\u001b[1;32m----> 2\u001b[0m \u001b[38;5;124mrf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname_or_alias: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00minvalid_name_or_alias\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m not in JSON at \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 3\u001b[0m \u001b[38;5;124mrf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;132;01m{\u001b[39;00mos\u001b[38;5;241m.\u001b[39mpath\u001b[38;5;241m.\u001b[39mjoin(DATA_DIR,\u001b[38;5;250m \u001b[39mpvdeg\u001b[38;5;241m.\u001b[39mutilities\u001b[38;5;241m.\u001b[39mpvdeg_datafiles[pvdeg_file])\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 4\u001b[0m )\n",
861-
"\u001b[1;31mNameError\u001b[0m: name 'invalid_name_or_alias' is not defined"
862-
]
863-
}
864-
],
837+
"outputs": [],
865838
"source": [
866839
"\n",
867840
"expected_error_message = (\n",
@@ -872,46 +845,74 @@
872845
},
873846
{
874847
"cell_type": "code",
875-
"execution_count": 4,
848+
"execution_count": null,
876849
"metadata": {},
877-
"outputs": [
878-
{
879-
"data": {
880-
"text/plain": [
881-
"'name_or_alias: namenotindict not in JSON at C:\\\\Users\\\\tford\\\\dev\\\\PVDegradationTools\\\\pvdeg\\\\data\\\\H2Opermeation.json'"
882-
]
883-
},
884-
"execution_count": 4,
885-
"metadata": {},
886-
"output_type": "execute_result"
887-
}
888-
],
850+
"outputs": [],
889851
"source": [
890852
"expected_error_message"
891853
]
892854
},
893855
{
894856
"cell_type": "code",
895-
"execution_count": 5,
857+
"execution_count": null,
896858
"metadata": {},
897-
"outputs": [
898-
{
899-
"ename": "TypeError",
900-
"evalue": "'module' object is not callable",
901-
"output_type": "error",
902-
"traceback": [
903-
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
904-
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)",
905-
"Cell \u001b[1;32mIn[5], line 1\u001b[0m\n\u001b[1;32m----> 1\u001b[0m \u001b[43mpvdeg\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mutilities\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43msearch_json\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpvdeg_file\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mpvdeg_file\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mname_or_alias\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43minvalid_name_or_alias\u001b[49m\u001b[43m)\u001b[49m\n",
906-
"File \u001b[1;32m~\\dev\\PVDegradationTools\\pvdeg\\utilities.py:1437\u001b[0m, in \u001b[0;36msearch_json\u001b[1;34m(pvdeg_file, fp, name_or_alias)\u001b[0m\n\u001b[0;32m 1434\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m (subdict[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m==\u001b[39m name_or_alias \u001b[38;5;129;01mor\u001b[39;00m subdict[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124malias\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m==\u001b[39m name_or_alias):\n\u001b[0;32m 1435\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m key\n\u001b[1;32m-> 1437\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m \u001b[38;5;167;01mValueError\u001b[39;00m(\u001b[38;5;124mrf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mname_or_alias: \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mname_or_alias\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m not in JSON at \u001b[39m\u001b[38;5;132;01m{\u001b[39;00mos\u001b[38;5;241m.\u001b[39mpath(fp)\u001b[38;5;132;01m}\u001b[39;00m\u001b[38;5;124m\"\u001b[39m)\n",
907-
"\u001b[1;31mTypeError\u001b[0m: 'module' object is not callable"
908-
]
909-
}
910-
],
859+
"outputs": [],
911860
"source": [
912861
"pvdeg.utilities.search_json(pvdeg_file=pvdeg_file, name_or_alias=invalid_name_or_alias)"
913862
]
914863
},
864+
{
865+
"cell_type": "code",
866+
"execution_count": 5,
867+
"metadata": {},
868+
"outputs": [],
869+
"source": [
870+
"import os\n",
871+
"import pandas as pd\n",
872+
"import pvdeg\n",
873+
"import numpy as np\n",
874+
"from pvdeg import TEST_DATA_DIR\n",
875+
"import pytest\n",
876+
"\n",
877+
"INPUT_SPECTRA = os.path.join(TEST_DATA_DIR, r\"spectra_pytest.csv\")\n",
878+
"\n",
879+
"data = pd.read_csv(INPUT_SPECTRA)\n",
880+
"wavelengths = np.array(range(280, 420, 20))\n",
881+
"\n",
882+
"# convert to expected format\n",
883+
"spectra = data[\"Spectra\"]\n",
884+
"spectra_df = pd.DataFrame(spectra.tolist(), index=spectra.index)\n",
885+
"spectra_df = spectra.str.strip(\"[]\").str.split(\",\", expand=True).astype(float)\n",
886+
"spectra_df.columns = wavelengths\n",
887+
"\n",
888+
"# from input data, this was lost during our original conversion to the dataframe\n",
889+
"spectra_df.index = pd.date_range(\"2021-03-09 10:00:00\", freq='1h', periods=10)\n",
890+
"\n",
891+
"conditions_df = pd.DataFrame(\n",
892+
" data={\n",
893+
" \"relative_humidity\": data[\"RH\"],\n",
894+
" \"temperature\": data[\"Temperature\"],\n",
895+
" }\n",
896+
")\n",
897+
"conditions_df.index = spectra_df.index\n",
898+
"\n",
899+
"degradation = pvdeg.degradation.degradation(\n",
900+
" spectra_df=spectra_df,\n",
901+
" conditions_df=conditions_df\n",
902+
")\n",
903+
"\n",
904+
"assert degradation == pytest.approx(4.4969e-38, abs=0.02e-38)"
905+
]
906+
},
907+
{
908+
"cell_type": "code",
909+
"execution_count": null,
910+
"metadata": {},
911+
"outputs": [],
912+
"source": [
913+
"degradation"
914+
]
915+
},
915916
{
916917
"cell_type": "code",
917918
"execution_count": null,

tests/test_degradation.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ def test_degradation():
128128
# test RH, Temp, Spectral Irradiance sensitive degradation
129129
# requires TMY3-like weather data
130130
# requires spectral irradiance data
131+
INPUT_SPECTRA = os.path.join(TEST_DATA_DIR, r"spectra_pytest.csv")
131132

132133
data = pd.read_csv(INPUT_SPECTRA)
133134
wavelengths = np.array(range(280, 420, 20))
@@ -138,13 +139,16 @@ def test_degradation():
138139
spectra_df = spectra.str.strip("[]").str.split(",", expand=True).astype(float)
139140
spectra_df.columns = wavelengths
140141

142+
# from input data, this was lost during our original conversion to the dataframe
143+
spectra_df.index = pd.date_range("2021-03-09 10:00:00", freq='1h', periods=10)
144+
141145
conditions_df = pd.DataFrame(
142-
index=spectra_df.index,
143146
data={
144147
"relative_humidity": data["RH"],
145148
"temperature": data["Temperature"],
146149
}
147150
)
151+
conditions_df.index = spectra_df.index
148152

149153
degradation = pvdeg.degradation.degradation(
150154
spectra_df=spectra_df,
@@ -153,7 +157,6 @@ def test_degradation():
153157

154158
assert degradation == pytest.approx(4.4969e-38, abs=0.02e-38)
155159

156-
157160
# def test_hours_rh_above_85():
158161
# values = np.arange(0,100)
159162
# rh_linear_df = pd.DataFrame(values, columns=['rh'])

0 commit comments

Comments
 (0)