Skip to content

Commit

Permalink
degradation: time dependent integral
Browse files Browse the repository at this point in the history
  • Loading branch information
Tobin Ford committed Dec 18, 2024
1 parent 0ee3550 commit 131337a
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 69 deletions.
20 changes: 13 additions & 7 deletions pvdeg/degradation.py
Original file line number Diff line number Diff line change
Expand Up @@ -864,6 +864,7 @@ def _gJtoMJ(gJ):


# new version of degradation
# we can make this work with varying timedelta sizes
def degradation(
spectra_df: pd.DataFrame,
conditions_df: pd.DataFrame = None,
Expand All @@ -878,7 +879,8 @@ def degradation(
"""
Compute degredation as double integral of Arrhenius (Activation
Energy, RH, Temperature) and spectral (wavelength, irradiance)
functions over wavelength and time.
functions over wavelength and time. Using dataframe input with
pd.TimeIndex with constant timedelta.
.. math::
Expand All @@ -890,7 +892,7 @@ def degradation(
front or rear irradiance data in dataframe format
- `data`: Spectral irradiance values for each wavelength [W/m^2 nm].
- `index`: pd.DateTimeIndex
- `index`: pd.DateTimeIndex with constant timedelta
- `columns`: Wavelengths as floats (e.g., 280, 300, etc.) [nm].
Example::
Expand All @@ -904,9 +906,9 @@ def degradation(
conditions_df : pd.DataFrame, optional
Environmental conditions including temperature and relative humidity.
- `index`: pd.DateTimeIndex
- `index`: pd.DateTimeIndex identical to spectra_df.index
- `columns`: (required)
- "temperature" [°C or K]
- "temperature" [°K]
- "relative_humidity" [%]
Example::
Expand Down Expand Up @@ -946,7 +948,8 @@ def degradation(
degradation : float
Total degredation factor over time and wavelength.
"""

if not isinstance(spectra_df.index, pd.DatetimeIndex):
raise ValueError(f"spectra_df must have pd.DatetimeIndex, has {type(spectra_df.index)}")

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

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

# call numba compiled function
return deg(
wavelengths=wavelengths,
irr=irr,
rh=rh,
temps=temps,
dt=dt,
Ea=Ea,
C2=C2,
p=p,
n=n,
C=C
)

@njit
# @njit
def deg(
wavelengths: np.ndarray,
irr: np.ndarray,
rh: np.ndarray,
temps: np.ndarray,
dt: float, # timestep in hours
Ea: float,
C2: float,
p: float,
Expand All @@ -1003,7 +1009,7 @@ def deg(
# arrhenius integral dt
time_integrand = (rh ** n) * np.exp(-Ea / (R * temps))

dD = wavelength_integral * time_integrand
dD = wavelength_integral * time_integrand * dt
degradation = C * np.sum(dD)

return degradation
Expand Down
121 changes: 61 additions & 60 deletions tests/sandbox.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -813,24 +813,9 @@
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"ename": "Failed",
"evalue": "Invalid regex pattern provided to 'match': incomplete escape \\U at position 46",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mFailed\u001b[0m Traceback (most recent call last)",
"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",
" \u001b[1;31m[... skipping hidden 1 frame]\u001b[0m\n",
"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",
"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",
"\u001b[1;31mFailed\u001b[0m: Invalid regex pattern provided to 'match': incomplete escape \\U at position 46"
]
}
],
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from pvdeg import DATA_DIR\n",
"\n",
Expand All @@ -847,21 +832,9 @@
},
{
"cell_type": "code",
"execution_count": 2,
"execution_count": null,
"metadata": {},
"outputs": [
{
"ename": "NameError",
"evalue": "name 'invalid_name_or_alias' is not defined",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mNameError\u001b[0m Traceback (most recent call last)",
"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",
"\u001b[1;31mNameError\u001b[0m: name 'invalid_name_or_alias' is not defined"
]
}
],
"outputs": [],
"source": [
"\n",
"expected_error_message = (\n",
Expand All @@ -872,46 +845,74 @@
},
{
"cell_type": "code",
"execution_count": 4,
"execution_count": null,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"'name_or_alias: namenotindict not in JSON at C:\\\\Users\\\\tford\\\\dev\\\\PVDegradationTools\\\\pvdeg\\\\data\\\\H2Opermeation.json'"
]
},
"execution_count": 4,
"metadata": {},
"output_type": "execute_result"
}
],
"outputs": [],
"source": [
"expected_error_message"
]
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": null,
"metadata": {},
"outputs": [
{
"ename": "TypeError",
"evalue": "'module' object is not callable",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mTypeError\u001b[0m Traceback (most recent call last)",
"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",
"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",
"\u001b[1;31mTypeError\u001b[0m: 'module' object is not callable"
]
}
],
"outputs": [],
"source": [
"pvdeg.utilities.search_json(pvdeg_file=pvdeg_file, name_or_alias=invalid_name_or_alias)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {},
"outputs": [],
"source": [
"import os\n",
"import pandas as pd\n",
"import pvdeg\n",
"import numpy as np\n",
"from pvdeg import TEST_DATA_DIR\n",
"import pytest\n",
"\n",
"INPUT_SPECTRA = os.path.join(TEST_DATA_DIR, r\"spectra_pytest.csv\")\n",
"\n",
"data = pd.read_csv(INPUT_SPECTRA)\n",
"wavelengths = np.array(range(280, 420, 20))\n",
"\n",
"# convert to expected format\n",
"spectra = data[\"Spectra\"]\n",
"spectra_df = pd.DataFrame(spectra.tolist(), index=spectra.index)\n",
"spectra_df = spectra.str.strip(\"[]\").str.split(\",\", expand=True).astype(float)\n",
"spectra_df.columns = wavelengths\n",
"\n",
"# from input data, this was lost during our original conversion to the dataframe\n",
"spectra_df.index = pd.date_range(\"2021-03-09 10:00:00\", freq='1h', periods=10)\n",
"\n",
"conditions_df = pd.DataFrame(\n",
" data={\n",
" \"relative_humidity\": data[\"RH\"],\n",
" \"temperature\": data[\"Temperature\"],\n",
" }\n",
")\n",
"conditions_df.index = spectra_df.index\n",
"\n",
"degradation = pvdeg.degradation.degradation(\n",
" spectra_df=spectra_df,\n",
" conditions_df=conditions_df\n",
")\n",
"\n",
"assert degradation == pytest.approx(4.4969e-38, abs=0.02e-38)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"degradation"
]
},
{
"cell_type": "code",
"execution_count": null,
Expand Down
7 changes: 5 additions & 2 deletions tests/test_degradation.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def test_degradation():
# test RH, Temp, Spectral Irradiance sensitive degradation
# requires TMY3-like weather data
# requires spectral irradiance data
INPUT_SPECTRA = os.path.join(TEST_DATA_DIR, r"spectra_pytest.csv")

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

# from input data, this was lost during our original conversion to the dataframe
spectra_df.index = pd.date_range("2021-03-09 10:00:00", freq='1h', periods=10)

conditions_df = pd.DataFrame(
index=spectra_df.index,
data={
"relative_humidity": data["RH"],
"temperature": data["Temperature"],
}
)
conditions_df.index = spectra_df.index

degradation = pvdeg.degradation.degradation(
spectra_df=spectra_df,
Expand All @@ -153,7 +157,6 @@ def test_degradation():

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


# def test_hours_rh_above_85():
# values = np.arange(0,100)
# rh_linear_df = pd.DataFrame(values, columns=['rh'])
Expand Down

0 comments on commit 131337a

Please sign in to comment.