diff --git a/.gitignore b/.gitignore index a896770..c271652 100644 --- a/.gitignore +++ b/.gitignore @@ -107,4 +107,7 @@ TEMP/ slurm-*.out # Run information -tests\data\h5_pytest.h5 \ No newline at end of file +tests\data\h5_pytest.h5 + +# ruff linter and formatter +.ruff_cache \ No newline at end of file diff --git a/pvdeg/chamber.py b/pvdeg/chamber.py index bf02397..7533816 100644 --- a/pvdeg/chamber.py +++ b/pvdeg/chamber.py @@ -11,7 +11,10 @@ import json import os import matplotlib.pyplot as plt -from IPython.display import display +from IPython.display import display, HTML + +import io +import base64 from pvdeg import ( humidity, @@ -68,9 +71,10 @@ def start_times(df: pd.DataFrame) -> pd.DataFrame: # return df + def add_previous_setpoints(df: pd.DataFrame) -> pd.DataFrame: """ - Create setpoints columns with values shifted by 1. + Create setpoints columns with values shifted by 1. At row k, the shifted value will come from row k - 1. """ @@ -81,7 +85,7 @@ def add_previous_setpoints(df: pd.DataFrame) -> pd.DataFrame: # only care about setpoints, not ramp rates setpoints = set(df.columns).difference(ignore_columns) - setpoint_names = {name.rstrip("_ramp") for name in setpoints} + setpoint_names = {name.rstrip("_ramp") for name in setpoints} for name in setpoint_names: df[f"previous_{name}"] = df[name].shift(1) @@ -150,9 +154,9 @@ def fill_linear_region( if rate != 0: ramp_time = linear_ramp_time(y_0=set_0, y_f=set_f, rate=rate) - if ramp_time > step_time: # adding a tolerance - # not np.isclose(ramp_time, step_time, rtol=0.01): - # if ramp_time > step_time: + if ramp_time > step_time: # adding a tolerance + # not np.isclose(ramp_time, step_time, rtol=0.01): + # if ramp_time > step_time: raise ValueError( f""" Ramp speed is too slow, will not finish ramping up before next set point. @@ -348,7 +352,6 @@ def _temp_calc_no_irradiance( # the temperature increase from irradiance at a timestep has a constant factor of # K = surface area * absorptance so we can bring this out of the loop if we refactor the temperature irradiance - # @njit def _temp_calc_irradiance( temps: np.ndarray, @@ -409,10 +412,10 @@ def sample_temperature( if not isinstance(air_temperature, np.ndarray): raise ValueError("Air_temperature must be a numpy array or pandas series") - if "irradiance_full" in setpoints_df.columns: + if "setpoint_irradiance_full" in setpoints_df.columns: sample_temp = _temp_calc_irradiance( temps=air_temperature, - irradiances=setpoints_df["irradiance_full"].to_numpy(), + irradiances=setpoints_df["setpoint_irradiance_full"].to_numpy(), times=setpoints_df.index.to_numpy(), tau=tau_s, temp_0=sample_temp_0, @@ -422,7 +425,7 @@ def sample_temperature( else: print(f""" - "irradiance_full" not in setpoints_df.columns + "setpoint_irradiance_full" not in setpoints_df.columns Current column names {setpoints_df.columns}. calculating sample temperature without irradiance" """) @@ -475,8 +478,9 @@ def __init__( length=None, width=None, ): - f = open(os.path.join(DATA_DIR, "materials.json")) - self.materials = json.load(f) + # f = utilities.read_material() + # f = open(os.path.join(DATA_DIR, "materials.json")) + # self.materials = json.load(f) self.absorptance = absorptance self.length = length @@ -488,7 +492,7 @@ def __init__( if encapsulant: self.setEncapsulant(encapsulant_thickness) - def setEncapsulant(self, id: str, thickness: float) -> None: + def setEncapsulant(self, pvdeg_file: str, key: str, thickness: float, fp: str = None) -> None: """ Set encapsulant diffusivity activation energy, prefactor and solubility activation energy, prefactor. @@ -496,18 +500,35 @@ def setEncapsulant(self, id: str, thickness: float) -> None: Parameters: ----------- - id: str - name of material from `PVDegradationTools/data/materials.json` + pvdeg_file: str + keyword for material json file in `pvdeg/data`. Options: + >>> "AApermeation", "H2Opermeation", "O2permeation" + fp: str + file path to material parameters json with same schema as material parameters json files in `pvdeg/data`. `pvdeg_file` will override `fp` if both are provided. + key: str + key corresponding to specific material in the file. In the pvdeg files these have arbitrary names. Inspect the files or use `display_json` or `search_json` to identify the key for desired material. thickness: float thickness of encapsulant [mm] """ - self.diffusivity_encap_ea = (self.materials)[id]["Ead"] - self.diffusivity_encap_pre = (self.materials)[id]["Do"] - self.solubility_encap_ea = (self.materials)[id]["Eas"] - self.solubility_encap_pre = (self.materials)[id]["So"] + + material_dict = utilities.read_material( + pvdeg_file=pvdeg_file, + fp=fp, + key=key, + ) + + self.diffusivity_encap_ea = material_dict["Ead"] + self.diffusivity_encap_pre = material_dict["Do"] + self.solubility_encap_ea = material_dict["Eas"] + self.solubility_encap_pre = material_dict["So"] + + # self.diffusivity_encap_ea = (self.materials)[key]["Ead"] + # self.diffusivity_encap_pre = (self.materials)[key]["Do"] + # self.solubility_encap_ea = (self.materials)[key]["Eas"] + # self.solubility_encap_pre = (self.materials)[key]["So"] self.encap_thickness = thickness - def setBacksheet(self, id: str, thickness: float) -> None: + def setBacksheet(self, pvdeg_file: str, key: str, thickness: float, fp: str = None ) -> None: """ Set backsheet permiability activation energy and prefactor. @@ -519,9 +540,31 @@ def setBacksheet(self, id: str, thickness: float) -> None: name of material from `PVDegradationTools/data/materials.json` thickness: float thickness of backsheet [mm] + + Parameters: + ----------- + pvdeg_file: str + keyword for material json file in `pvdeg/data`. Options: + >>> "AApermeation", "H2Opermeation", "O2permeation" + fp: str + file path to material parameters json with same schema as material parameters json files in `pvdeg/data`. `pvdeg_file` will override `fp` if both are provided. + key: str + key corresponding to specific material in the file. In the pvdeg files these have arbitrary names. Inspect the files or use `display_json` or `search_json` to identify the key for desired material. + thickness: float + thickness of backsheet [mm] """ - self.permiability_back_ea = (self.materials)[id]["Eap"] - self.permiability_back_pre = (self.materials)[id]["Po"] + + material_dict = utilities.read_material( + pvdeg_file=pvdeg_file, + fp=fp, + key=key, + ) + + self.permiability_back_ea = material_dict["Eap"] + self.permiability_back_pre = material_dict["Po"] + + # self.permiability_back_ea = (self.materials)[id]["Eap"] + # self.permiability_back_pre = (self.materials)[id]["Po"] self.back_thickness = thickness def setDimensions(self, length: float = None, width: float = None) -> None: @@ -621,15 +664,16 @@ def calc_temperatures( ) if ( - "irradiance_340" in self.setpoints.columns - and "irradiance_full" not in self.setpoints.columns + "setpoint_irradiance_340" in self.setpoints.columns + and "setpoint_irradiance_full" not in self.setpoints.columns ): # gti calculation is very slow because of integration print("Calculating GTI...") - self.setpoints["irradiance_full"] = spectral.get_GTI_from_irradiance_340( - self.setpoints["irradiance_340"] + # should this be setpoint irradiance full, it is not a setpoint + self.setpoints["setpoint_irradiance_full"] = spectral.get_GTI_from_irradiance_340( + self.setpoints["setpoint_irradiance_340"] ) # this may be misleading - print('Saved in self.setpoints as "irradiance_full') + print('Saved in self.setpoints as "setpoints_irradiance_full') self.sample_temperature = sample_temperature( self.setpoints, @@ -683,14 +727,19 @@ def calc_equilibrium_ecapsulant_water(self): def calc_back_encapsulant_moisture(self, n_steps: int = 20): """Calculate the moisture on the backside of the encapsulant""" + + # kJ/mol -> eV + permiability = utilities.kj_mol_to_ev(self.permiability_back_ea) + res, _ = humidity.moisture_eva_back( eva_moisture_0=self.equilibrium_encapsulant_water.iloc[0], sample_temp=self.sample_temperature, rh_at_sample_temp=self.sample_relative_humidity, equilibrium_eva_water=self.equilibrium_encapsulant_water, - pet_permiability=utilities.kj_mol_to_ev( - self.permiability_back_ea - ), # kJ/mol -> eV + # pet_permiability=utilities.kj_mol_to_ev( + # self.permiability_back_ea + # ), # kJ/mol -> eV + pet_permiability=permiability, pet_prefactor=self.permiability_back_pre, thickness_eva=self.encap_thickness, # mm thickness_pet=self.back_thickness, # mm @@ -698,7 +747,10 @@ def calc_back_encapsulant_moisture(self, n_steps: int = 20): ) self.back_encapsulant_moisture = pd.Series( - res, index=self.setpoints.index, name="Back Encapsulant Moisture" + # res, index=self.setpoints.index, name="Back Encapsulant Moisture" + res, + index=self.setpoints.index, + name="Back Encapsulant Moisture" ) def calc_relative_humidity_internal_on_back_of_cells(self): @@ -710,8 +762,11 @@ def calc_relative_humidity_internal_on_back_of_cells(self): ), rh_at_sample_temp=self.sample_relative_humidity.to_numpy(dtype=np.float64), ) + self.relative_humidity_internal_on_back_of_cells = pd.Series( - res, self.setpoints.index, name="Relative Humidity Internal Cells Backside" + res, + self.setpoints.index, + name="Relative Humidity Internal Cells Backside" ) def calc_dew_point(self): @@ -808,14 +863,72 @@ def gti_from_irradiance_340(self) -> pd.Series: gti: pd.Series full spectrum irradiance using ASTM G173-03 AM1.5 spectrum. """ - self.setpoints["irradiance_full"] = spectral.get_GTI_from_irradiance_340( + self.setpoints["setpoint_irradiance_full"] = spectral.get_GTI_from_irradiance_340( self.setpoints["setpoint_irradiance_340"] ) - return self.setpoints["irradiance_full"] + return self.setpoints["setpoint_irradiance_full"] def _ipython_display_(self): """ Display the setpoints of the chamber instance. """ - display(self.setpoints) + + # Create the plot + fig, ax = plt.subplots(figsize=(8, 4)) + self.setpoints.plot(ax=ax) + ax.set_title("Setpoints Plot (Units Not Demonstrated on Y Axis)") + ax.set_xlabel("Index") + ax.set_ylabel("Value") + plt.tight_layout() + + # Save the plot to a BytesIO object + img_buffer = io.BytesIO() + plt.savefig(img_buffer, format='png', bbox_inches='tight') + plt.close(fig) + img_buffer.seek(0) + + # Encode the image as base64 + img_base64 = base64.b64encode(img_buffer.read()).decode('utf-8') + + html_content = f""" +
+

Chamber Simulation

+ +
+

+ + Setpoints Dataframe +

+
+ + +
+

+ + Setpoints Plot +

+
+ + +
+ + """ + display(HTML(html_content)) diff --git a/pvdeg/collection.py b/pvdeg/collection.py index 352e1bf..a2f4322 100644 --- a/pvdeg/collection.py +++ b/pvdeg/collection.py @@ -8,31 +8,27 @@ import photovoltaic as pv -def collection_probability(x, thickness, s, l, d): +def collection_probability(x, thickness: float, s: float, l: float, d: float): """ Returns the collection probability (unit 0 to 1) at a distance x (cm) from the junction. See [1]_. Parameters ---------- - x : array-like + x: array-like array of x positions from a junction to a surface (typically [cm]). - - thickness : numeric + thickness: numeric Layer thickness [cm]. - - s : numeric + s: numeric Surface recombination velocity [cm/s]. - - l : numeric + l: numeric Minority carrier diffusion length [cm]. - - d : numeric + d: numeric Minority carrier diffusivity [cm^2/Vs]. Returns ------- - cp : array-like + cp: array-like Collection probability along x. References diff --git a/pvdeg/diffusion.py b/pvdeg/diffusion.py index 9f8af27..5f55a22 100644 --- a/pvdeg/diffusion.py +++ b/pvdeg/diffusion.py @@ -1,11 +1,15 @@ """ -<<<<<<< HEAD -Collection of functions to calculate diffusion of diffusants/solutes into a host material +Collection of functions to calculate diffusion of permeants/diffsants into PV modules. """ - import numpy as np import pandas as pd +import os +import json +import pandas as pd +from pvdeg import DATA_DIR +import numpy as np + def _calc_diff_substeps( water_new, water_old, n_steps, t, delta_t, dis, delta_dis, Fo ) -> None: # inplace @@ -146,17 +150,6 @@ def module_front( return results -======= -Collection of classes and functions to calculate diffusion of permeants into a PV module. -""" - -import os -import json -import pandas as pd -from pvdeg import DATA_DIR -from numba import jit -import numpy as np -from typing import Callable def esdiffusion( temperature, @@ -439,4 +432,3 @@ def esdiffusion( perm = np.vstack([positions, perm]) return pd.DataFrame(perm[1:, 1:], index=perm[1:, 0], columns=perm[0, 1:]) ->>>>>>> development diff --git a/pvdeg/humidity.py b/pvdeg/humidity.py index 761d17e..ed26697 100644 --- a/pvdeg/humidity.py +++ b/pvdeg/humidity.py @@ -2,28 +2,25 @@ import numpy as np import pandas as pd -<<<<<<< HEAD from numba import jit, njit, vectorize, guvectorize, float64 from typing import Union from . import temperature from . import spectral -======= -import pvlib +# import pvlib from numba import njit -from rex import NSRDBX -from rex import Outputs -from pathlib import Path -from concurrent.futures import ProcessPoolExecutor, as_completed +# from rex import NSRDBX +# from rex import Outputs +# from pathlib import Path +# from concurrent.futures import ProcessPoolExecutor, as_completed from . import ( temperature, spectral, - weather + # weather ) from pvdeg.decorators import geospatial_quick_shape ->>>>>>> development def _ambient(weather_df): @@ -400,10 +397,6 @@ def _ceq(Csat, rh_SurfaceOutside): return Ceq -<<<<<<< HEAD -# @jit(nopython=True) -======= ->>>>>>> development @njit def Ce_numba( start, diff --git a/pvdeg/spectral.py b/pvdeg/spectral.py index efe317d..9eb9ab1 100644 --- a/pvdeg/spectral.py +++ b/pvdeg/spectral.py @@ -3,15 +3,10 @@ """ import pvlib -<<<<<<< HEAD -from numba import njit, prange, cuda +from numba import njit, prange import numpy as np import pandas as pd -======= -import pandas as pd from pvdeg.decorators import geospatial_quick_shape ->>>>>>> development - @geospatial_quick_shape( 1, diff --git a/pvdeg/temperature.py b/pvdeg/temperature.py index ad8e383..1fbd7a0 100644 --- a/pvdeg/temperature.py +++ b/pvdeg/temperature.py @@ -287,7 +287,6 @@ def cell( return temp_cell -<<<<<<< HEAD # @njit def chamber_sample_temperature( irradiance_340: float, @@ -408,7 +407,6 @@ def fdm_temperature_irradiance( -======= # test not providing poa # what if we dont need the cell or mod param, only matters for sapm @@ -535,4 +533,3 @@ def temperature( temperature = func(**model_args) return temperature ->>>>>>> development diff --git a/pvdeg/utilities.py b/pvdeg/utilities.py index 2c56718..9386698 100644 --- a/pvdeg/utilities.py +++ b/pvdeg/utilities.py @@ -5,10 +5,8 @@ from rex import NSRDBX, Outputs from pvdeg import DATA_DIR from typing import Callable -<<<<<<< HEAD import math from numba import njit -======= import inspect from random import choices from string import ascii_uppercase @@ -19,12 +17,11 @@ # A mapping to simplify access to files stored in `pvdeg/data` -pvdeg_datafiles = { +PVDEG_DATAFILES = { "AApermeation": os.path.join(DATA_DIR, "AApermeation.json"), "H2Opermeation": os.path.join(DATA_DIR, "H2Opermeation.json"), "O2permeation": os.path.join(DATA_DIR, "O2permeation.json"), } ->>>>>>> development def gid_downsampling(meta, n): @@ -506,7 +503,6 @@ def convert_tmy(file_in, file_out="h5_from_tmy.h5"): ) -<<<<<<< HEAD def _shift(arr, num, fill_value=np.timedelta64(0, "m")): """ Fast numpy shift. @@ -524,12 +520,8 @@ def _shift(arr, num, fill_value=np.timedelta64(0, "m")): result[:] = arr return result - -def _read_material(name, fname="materials.json"): -======= ### DEPRECATE ### def _read_material(name, fname="O2permeation.json"): ->>>>>>> development """ read a material from materials.json and return the parameter dictionary @@ -746,7 +738,6 @@ def tilt_azimuth_scan( return tilt_azimuth_series -<<<<<<< HEAD def plot_water_2d(water: np.ndarray[float]): """ Plot a heatmap of water module using a 2d numpy array. @@ -777,7 +768,6 @@ def kj_mol_to_ev(energy: float) -> float: """ return energy / 96.485 -======= def _meta_df_from_csv(file_paths: list[str]): """ Helper Function: Create csv dataframe from list of files in string form [Or Directory (not functional yet)] @@ -1425,9 +1415,9 @@ def display_json( if pvdeg_file: try: - fp = pvdeg_datafiles[pvdeg_file] + fp = PVDEG_DATAFILES[pvdeg_file] except KeyError: - raise KeyError(f"{pvdeg_file} does not exist in pvdeg/data. Options are {pvdeg_datafiles.keys()}") + raise KeyError(f"{pvdeg_file} does not exist in pvdeg/data. Options are {PVDEG_DATAFILES.keys()}") with open(fp, 'r') as file: data = json.load(file) @@ -1481,9 +1471,9 @@ def search_json( if pvdeg_file: try: - fp = pvdeg_datafiles[pvdeg_file] + fp = PVDEG_DATAFILES[pvdeg_file] except KeyError: - raise KeyError(rf"{pvdeg_file} does not exist in pvdeg/data. Options are {pvdeg_datafiles.keys()}") + raise KeyError(rf"{pvdeg_file} does not exist in pvdeg/data. Options are {PVDEG_DATAFILES.keys()}") with open(fp, "r") as file: data = json.load(file) @@ -1525,9 +1515,9 @@ def read_material( # these live in the `pvdeg/data` folder if pvdeg_file: try: - fp = pvdeg_datafiles[pvdeg_file] + fp = PVDEG_DATAFILES[pvdeg_file] except KeyError: - raise KeyError(f"{pvdeg_file} does not exist in pvdeg/data. Options are {pvdeg_datafiles.keys()}") + raise KeyError(f"{pvdeg_file} does not exist in pvdeg/data. Options are {PVDEG_DATAFILES.keys()}") with open(fp, "r") as file: data = json.load(file) @@ -1539,4 +1529,3 @@ def read_material( material_dict = {k: material_dict.get(k, None) for k in parameters} return material_dict ->>>>>>> development diff --git a/tests/data/h5_pytest.h5 b/tests/data/h5_pytest.h5 index 188e78e..12175c8 100644 Binary files a/tests/data/h5_pytest.h5 and b/tests/data/h5_pytest.h5 differ diff --git a/tests/sandbox.ipynb b/tests/sandbox.ipynb index 5792067..0530f01 100644 --- a/tests/sandbox.ipynb +++ b/tests/sandbox.ipynb @@ -13,7 +13,6 @@ { "cell_type": "code", "execution_count": null, - "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -117,7 +116,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -130,7 +129,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -139,7 +138,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -157,7 +156,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -168,7 +167,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -187,7 +186,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -198,7 +197,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -207,7 +206,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -219,27 +218,16 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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Qf/+/eHq6go3Nzf06NEDFy9evOcxVwrFtSD98ssvWL58OSZMmIB//vOfSEpKwrhx46DT6RAVFYXU1FQAgJeXl9XnvLy8pHmlzZ07F7GxsWWmmzkOEtEjLbfQjCbTd9fItk/NioSDrnL/C09JScHAgQMxf/589OrVC1lZWfj2228hhMD69esxffp0vP/++2jZsiVOnDiBESNGwNHREVFRUTh69Chat26NvXv3omnTplatCvHx8bC3t8e+fftw8eJFDBs2DO7u7pgzZw4AYPLkyfj888/x8ccfIyAgAPPnz0dkZCTOnTsHNze3cmvNycnBggULsG7dOqjVagwZMgQTJ07E+vXrMXHiRJw+fRqZmZnSVYKK1mNRUf3322+LGTNmYNGiRfD398fw4cMxaNAgODs747333oODgwP69++P6dOnY/ny5ZU+LnPnzsUnn3yCFStWoGHDhjhw4ACGDBkCDw8PdOjQQVrPlClTsGDBAtSrVw+1atXClStX8Pe//x1z5syBXq/H2rVr0b17d5w5cwb+/v7YsmULWrRogZEjR2LEiBGVOjdKio+Ph9FoxJ49ewDcGUcoMjISJpMJ3377LbRaLd5880107doV33//veJbmBQXkIqLi9GqVSu89dZbAO781fDjjz9ixYoVVid9VcTExGDChAnS+8zMTPj5+d29i42X2IjoIZaSkoKioiL07t0bAQEBAIDQ0FAAdwLAwoUL0bt3bwB3WlxOnTqFlStXIioqCh4eHgAAd3f3MpfDdDodPvroIzg4OKBp06aYNWsWJk2ahNmzZyM3NxfLly/HmjVr0K1bNwDAhx9+iD179mD16tWYNGlSubUWFhZixYoVqF+/PgBg7NixmDVrFgDAyckJBoMB+fn5lb40V1H999tvi4kTJyIyMhIA8Oqrr2LgwIGIj49Hu3btAADR0dFYs2ZNpY9LYWEh3nrrLezduxcmkwkAUK9ePRw8eBArV660CkizZs3C008/Lb13c3NDixYtpPezZ8/GF198gf/85z8YO3Ys3NzcoNFo4OzsXK1Ll46Ojli1apUUfD755BMUFxdj1apVUmtUXFwcXF1dsW/fPnTp0qXK2/grUVxA8vHxQZMmTaymhYSE4PPPPwcA6aRJS0uzek5LWlpamSZIC71eD71eX2Z6Me9iI3qkGew0ODUrssa2XVktWrRA586dERoa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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "test_chamber.plot_setpoints()" ] }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -251,13 +239,98 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "SAMPLE_CONDITIONS" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "bad_df = pd.DataFrame(np.nan, index=pd.RangeIndex(5), columns=[\"Temperature\"])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pvdeg.chamber.setpoint_series(df=bad_df, setpoint_name=\"Temperature\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pytest\n", + "with pytest.raises(ValueError) as excinfo:\n", + " pvdeg.chamber.setpoint_series(df=bad_df, setpoint_name=\"Temperature\")\n", + "\n", + "# Check that the exception message is correct\n", + "assert str(excinfo.value) == \"column: Temperature contains NaN values. Remove from setpoint list or remove NaN's in input.\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import pandas as pd\n", + "import os \n", + "import numpy as np\n", + "import pvdeg\n", + "from pvdeg import TEST_DATA_DIR\n", + "\n", + "CHAMBER_CONDITIONS = pd.read_csv(\n", + " os.path.join(TEST_DATA_DIR, \"chamber_conditions_result.csv\"), index_col=0\n", + ")\n", + "CHAMBER_CONDITIONS.index = CHAMBER_CONDITIONS.index.astype(\"timedelta64[s]\")\n", + "\n", + "SAMPLE_CONDITIONS = pd.read_csv(\n", + " os.path.join(TEST_DATA_DIR, \"sample_conditions_results.csv\"), index_col=0\n", + ")\n", + "SAMPLE_CONDITIONS.index = SAMPLE_CONDITIONS.index.astype(\"timedelta64[s]\")\n", + "\n", + "test_chamber = pvdeg.chamber.Chamber(\n", + " os.path.join(TEST_DATA_DIR, \"chamber-setpoints.csv\"),\n", + " setpoint_names=[\"temperature\", \"relative_humidity\"],\n", + " skiprows=[1],\n", + ")\n", + "\n", + "test_chamber.setDimensions()\n", + "\n", + "test_chamber.setpoints = test_chamber.setpoints.iloc[:100] # we only care about the first 100 setpoints" + ] + }, + { + "cell_type": "code", + "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ - "
\n", + "\n", + "
\n", + "

Chamber Simulation

\n", + "\n", + "
\n", + "

\n", + " \n", + " Setpoints Dataframe\n", + "

\n", + "
\n", + "
\n", + "
\n", "