From 131337aee8dd2d1df1e7651362f878ce078ef2f9 Mon Sep 17 00:00:00 2001 From: Tobin Ford Date: Wed, 18 Dec 2024 07:41:05 -0700 Subject: [PATCH] degradation: time dependent integral --- pvdeg/degradation.py | 20 ++++--- tests/sandbox.ipynb | 121 +++++++++++++++++++------------------- tests/test_degradation.py | 7 ++- 3 files changed, 79 insertions(+), 69 deletions(-) diff --git a/pvdeg/degradation.py b/pvdeg/degradation.py index d89f5c0..3a122de 100644 --- a/pvdeg/degradation.py +++ b/pvdeg/degradation.py @@ -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, @@ -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:: @@ -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:: @@ -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:: @@ -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") @@ -960,6 +963,7 @@ 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( @@ -967,6 +971,7 @@ def degradation( irr=irr, rh=rh, temps=temps, + dt=dt, Ea=Ea, C2=C2, p=p, @@ -974,12 +979,13 @@ def degradation( 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, @@ -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 diff --git a/tests/sandbox.ipynb b/tests/sandbox.ipynb index 8a9ed66..9fc0f70 100644 --- a/tests/sandbox.ipynb +++ b/tests/sandbox.ipynb @@ -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", @@ -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", @@ -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, diff --git a/tests/test_degradation.py b/tests/test_degradation.py index 0454a01..c85e3a6 100644 --- a/tests/test_degradation.py +++ b/tests/test_degradation.py @@ -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)) @@ -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, @@ -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'])