diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 8a782f96..43e78ead 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -20,6 +20,7 @@ Please delete options that are not relevant. - [ ] I have performed a self-review of my code - [ ] Code changes are covered by tests. - [ ] Code changes have been evaluated for compatibility/integration with Scenario analysis (for future PRs) +- [ ] Code changes have been evaluated for compatibility/integration with geospatial autotemplating (for future PRs) - [ ] New functions added to __init__.py - [ ] API.rst is up to date, along with other sphinx docs pages - [ ] Example notebooks are rerun and differences in results scrutinized diff --git a/docs/source/_autosummary/pvdeg.geospatial.analysis.rst b/docs/source/_autosummary/pvdeg.geospatial.analysis.rst new file mode 100644 index 00000000..8de2ff62 --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.analysis.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.analysis +========================= + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: analysis \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.auto_template.rst b/docs/source/_autosummary/pvdeg.geospatial.auto_template.rst new file mode 100644 index 00000000..b4df1a2b --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.auto_template.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.auto\_template +=============================== + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: auto_template \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.calc_block.rst b/docs/source/_autosummary/pvdeg.geospatial.calc_block.rst new file mode 100644 index 00000000..c78b81de --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.calc_block.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.calc\_block +============================ + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: calc_block \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.calc_gid.rst b/docs/source/_autosummary/pvdeg.geospatial.calc_gid.rst new file mode 100644 index 00000000..4398fd14 --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.calc_gid.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.calc\_gid +========================== + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: calc_gid \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.output_template.rst b/docs/source/_autosummary/pvdeg.geospatial.output_template.rst new file mode 100644 index 00000000..6d49f0b8 --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.output_template.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.output\_template +================================= + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: output_template \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.plot_Europe.rst b/docs/source/_autosummary/pvdeg.geospatial.plot_Europe.rst new file mode 100644 index 00000000..f6b3cb48 --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.plot_Europe.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.plot\_Europe +============================= + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: plot_Europe \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.plot_USA.rst b/docs/source/_autosummary/pvdeg.geospatial.plot_USA.rst new file mode 100644 index 00000000..b1b86a6b --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.plot_USA.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.plot\_USA +========================== + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: plot_USA \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.rst b/docs/source/_autosummary/pvdeg.geospatial.rst new file mode 100644 index 00000000..c50d18ee --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.rst @@ -0,0 +1,125 @@ +.. Please when editing this file make sure to keep it matching the + docs in ../configuration.rst:reference_to_examples + +pvdeg.geospatial +================ + +.. automodule:: pvdeg.geospatial + + .. this is crazy + + + + + Function Overview + ----------------- + + .. autosummary:: + :toctree: + :nosignatures: + + + pvdeg.geospatial.analysis + pvdeg.geospatial.auto_template + pvdeg.geospatial.calc_block + pvdeg.geospatial.calc_gid + pvdeg.geospatial.output_template + pvdeg.geospatial.plot_Europe + pvdeg.geospatial.plot_USA + pvdeg.geospatial.start_dask + pvdeg.geospatial.template_parameters + pvdeg.geospatial.zero_template + + + + + .. this is crazy + + + + +.. + Functions + --------- + + + + .. autofunction:: analysis + + .. _sphx_glr_backref_pvdeg.geospatial.analysis: + + .. minigallery:: pvdeg.geospatial.analysis + :add-heading: + + .. autofunction:: auto_template + + .. _sphx_glr_backref_pvdeg.geospatial.auto_template: + + .. minigallery:: pvdeg.geospatial.auto_template + :add-heading: + + .. autofunction:: calc_block + + .. _sphx_glr_backref_pvdeg.geospatial.calc_block: + + .. minigallery:: pvdeg.geospatial.calc_block + :add-heading: + + .. autofunction:: calc_gid + + .. _sphx_glr_backref_pvdeg.geospatial.calc_gid: + + .. minigallery:: pvdeg.geospatial.calc_gid + :add-heading: + + .. autofunction:: output_template + + .. _sphx_glr_backref_pvdeg.geospatial.output_template: + + .. minigallery:: pvdeg.geospatial.output_template + :add-heading: + + .. autofunction:: plot_Europe + + .. _sphx_glr_backref_pvdeg.geospatial.plot_Europe: + + .. minigallery:: pvdeg.geospatial.plot_Europe + :add-heading: + + .. autofunction:: plot_USA + + .. _sphx_glr_backref_pvdeg.geospatial.plot_USA: + + .. minigallery:: pvdeg.geospatial.plot_USA + :add-heading: + + .. autofunction:: start_dask + + .. _sphx_glr_backref_pvdeg.geospatial.start_dask: + + .. minigallery:: pvdeg.geospatial.start_dask + :add-heading: + + .. autofunction:: template_parameters + + .. _sphx_glr_backref_pvdeg.geospatial.template_parameters: + + .. minigallery:: pvdeg.geospatial.template_parameters + :add-heading: + + .. autofunction:: zero_template + + .. _sphx_glr_backref_pvdeg.geospatial.zero_template: + + .. minigallery:: pvdeg.geospatial.zero_template + :add-heading: + + + + + + + + + + \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.start_dask.rst b/docs/source/_autosummary/pvdeg.geospatial.start_dask.rst new file mode 100644 index 00000000..6f7684f9 --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.start_dask.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.start\_dask +============================ + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: start_dask \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.template_parameters.rst b/docs/source/_autosummary/pvdeg.geospatial.template_parameters.rst new file mode 100644 index 00000000..dbc2d634 --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.template_parameters.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.template\_parameters +===================================== + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: template_parameters \ No newline at end of file diff --git a/docs/source/_autosummary/pvdeg.geospatial.zero_template.rst b/docs/source/_autosummary/pvdeg.geospatial.zero_template.rst new file mode 100644 index 00000000..ecae12db --- /dev/null +++ b/docs/source/_autosummary/pvdeg.geospatial.zero_template.rst @@ -0,0 +1,6 @@ +pvdeg.geospatial.zero\_template +=============================== + +.. currentmodule:: pvdeg.geospatial + +.. autofunction:: zero_template \ No newline at end of file diff --git a/docs/source/api.rst b/docs/source/api.rst index c59c6b08..5520f757 100644 --- a/docs/source/api.rst +++ b/docs/source/api.rst @@ -22,6 +22,7 @@ Modules, methods, classes and attributes are explained here. humidity letid montecarlo + geospatial spectral standards temperature diff --git a/docs/source/user_guide/geospatial-templates.rst b/docs/source/user_guide/geospatial-templates.rst new file mode 100644 index 00000000..d45044dc --- /dev/null +++ b/docs/source/user_guide/geospatial-templates.rst @@ -0,0 +1,40 @@ +.. _geospatial-templates: + +Geospatial Analysis +=================== +Using 3 dimensional labeled arrays (`Xarray`) we are able to run calculations using meteorological data across many points at once. This process has been parallelized using `dask` and `xarray`. Both of these packages can be run locally or on cloud HPC environments. + +This presents a new issue, our models produce outputs in many different shapes and sizes. We can have single numerical results, multiple numeric results or a timeseries of numeric results at each location. To parallelize this process, we cannot wait until runtime to know what shape to store the outputs in. This is where the need for `templates` arises. + +Previously, ``pvdeg.geospatial`` provided minimal templates and forced users to create their own for each function they wanted to use in a geospatial calculation. + +Auto-templating: allows users to skip creating templates for most functions within pvdeg by using ``pvdeg.geospatial.autotemplate`` to generate templates on the spot, instead of figuring out the output shape. For any given function within the source code decorated with `geospatial_result_type`, we can use `pvdeg.geospatial.autotemplate` + + +Example +-------- + +Here we are providing a function to autotemplate along with an ``Xarray.Dataset`` of weather data. Combining these two will give us enough information to produce an output template. + +Autotemplate approach to creating a template + +.. code-block:: Python + + edge_seal_template = pvdeg.geospatial.auto_template( + func=pvdeg.design.edge_seal_width, + ds_gids=geo_weather + ) + +Manual Approach to Creating the Sample Template + +.. code-block:: Python + + shapes = { + "width" : ("gid",) # one return value at each datapoint, only dependent on datapoint, not time + } + + template = pvdeg.geospatial.output_template( + ds_gids=geo_weather, # xarray dataset + shapes=shapes, # output shapes + ) + diff --git a/docs/source/user_guide/index.rst b/docs/source/user_guide/index.rst index 3f89f770..d4a4357b 100644 --- a/docs/source/user_guide/index.rst +++ b/docs/source/user_guide/index.rst @@ -10,6 +10,7 @@ User Guide package_overview NSRDB_API_Key montecarlo + geospatial-templates pv-variables-terms contributing diff --git a/docs/source/user_guide/montecarlo.rst b/docs/source/user_guide/montecarlo.rst index cb116f68..a2fce79e 100644 --- a/docs/source/user_guide/montecarlo.rst +++ b/docs/source/user_guide/montecarlo.rst @@ -21,12 +21,12 @@ If your variables are correlated form a list of correlations using pvdeg.monteca Defining Mean and Standard Deviation ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Each variable passed to a Corr object in string form must have its own dictionary entry containing its mean and standard deviation in the following form. -``` -my_dict = { - : {'mean' : , 'stdev' : } -} -``` -*why are there extra backticks this is upsetting* + +.. code-block:: Python + + my_dict = { + : {'mean' : , 'stdev' : } + } Generating Correlated Samples ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -39,13 +39,13 @@ Generating Uncorrelated Samples ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ To create uncorrelated samples the ``corr`` parameter of ``pvdeg.montecarlo.generateCorrelatedSamples()`` provided with an empty list, while still providing the other arguments. like the following. -``` +.. code-block:: Python + pvdeg.montecarlo.generateCorrelatedSamples( corr = [], ... ... ) -``` 3rd Party Samples/Data ^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/source/whatsnew/index.rst b/docs/source/whatsnew/index.rst index e9e23881..37fe565d 100644 --- a/docs/source/whatsnew/index.rst +++ b/docs/source/whatsnew/index.rst @@ -4,7 +4,10 @@ What's New ========== PVDegradationTools (pvdeg) change log: + +.. include:: releases/v0.4.0.rst .. include:: releases/v0.3.5.rst +.. include:: releases/v0.3.4.rst .. include:: releases/v0.3.3.rst .. include:: releases/v0.3.2.rst .. include:: releases/v0.3.1.rst diff --git a/docs/source/whatsnew/releases/v0.3.3.rst b/docs/source/whatsnew/releases/v0.3.3.rst index 1b6bd777..e50391dd 100644 --- a/docs/source/whatsnew/releases/v0.3.3.rst +++ b/docs/source/whatsnew/releases/v0.3.3.rst @@ -1,4 +1,4 @@ -v0.3.1 (2024-05-06) +v0.3.3 (2024-05-06) ======================= Bug Fixes diff --git a/docs/source/whatsnew/releases/v0.3.4.rst b/docs/source/whatsnew/releases/v0.3.4.rst new file mode 100644 index 00000000..664dd329 --- /dev/null +++ b/docs/source/whatsnew/releases/v0.3.4.rst @@ -0,0 +1,11 @@ +v0.3.4 (2024-07-26) +======================= + +Bug Fixes +--------- +* Fix incorrect keyword arguments in `pvdeg.standards.T98_estimate` + +Contributors +~~~~~~~~~~~~ +* Tobin Ford (:ghuser:`tobin-ford`) + diff --git a/docs/source/whatsnew/releases/v0.3.5.rst b/docs/source/whatsnew/releases/v0.3.5.rst index e0a0a614..f307ec05 100644 --- a/docs/source/whatsnew/releases/v0.3.5.rst +++ b/docs/source/whatsnew/releases/v0.3.5.rst @@ -7,5 +7,5 @@ Enhancements Contributors ~~~~~~~~~~~~ -* Martin Springer (:ghuser:`tobin-ford`) +* Tobin Ford (:ghuser:`tobin-ford`) diff --git a/docs/source/whatsnew/releases/v0.4.0.rst b/docs/source/whatsnew/releases/v0.4.0.rst new file mode 100644 index 00000000..fc0df308 --- /dev/null +++ b/docs/source/whatsnew/releases/v0.4.0.rst @@ -0,0 +1,21 @@ +v0.4.0 (2024-07-29) +======================= + +Enhancements +--------- +* Autotemplating system for geospatial analysis using `pvdeg.geospatial.autotemplate` +* New module `pvdeg.decorators` that contains `pvdeg` specific decorator functions. +* Implemented `geospatial_result_type` decorator to update functions and preform runtime introspection to determine if a function is autotemplate-able. +* `Geospatial Templates.ipynb` notebook to showcase new and old templating functionality for users. + +Bug Fixes +--------- +* Added type hinting to many `pvdeg` functions +* Replaced deprecated numba `jit(nopython=True)` calls with `njit` +* Fixed whatsnew `v0.3.5` author + +Contributors +~~~~~~~~~~~~ +* Tobin Ford (:ghuser:`tobin-ford`) + + diff --git a/pvdeg/decorators.py b/pvdeg/decorators.py new file mode 100644 index 00000000..b5927399 --- /dev/null +++ b/pvdeg/decorators.py @@ -0,0 +1,59 @@ +""" +Utility Decorators for PVDeg. +Private API, should only be used in PVDeg implemenation files. +""" + + +def geospatial_quick_shape(numeric_or_timeseries: bool, shape_names: list[str]) -> None: + """ + Add an attribute to the functions that can be run with geospatial analysis. + Strict typing is not enough for this purpose so we can view this attribute + at runtime to create a template for the function. + + For single numeric results, includes tabular numeric data + >>> value = False (0) + + Example if a function returns a dataframe with 1 row of numerics (not timeseries) + `pvdeg.standards.standoff` does this. + + For timeseries results + >>> value = True (1) + + Example, `pvdeg.temperature.temperature` + + For both numeric and timeseries results, we care about the output names of the funtion. + When a function returns a dataframe, the names will simply be the dataframe column names. + >>> return df # function returns dataframe + >>> df.columns = ["rh", "dry_bulb", "irradiance"] # dataframe column names + >>> func.shape_names = ["rh", "dry_bulb", "irradiance"] # function attribute names + + When a function returns a numeric, or tuple of numerics, the names will correspond to the meanings of each unpacked variable. + >>> return (T98, x_eff) # function return tuple of numerics + >>> func.shape_names = ["T98", "x_eff"] # function attribute names + + * Note: we cannot autotemplate functions with ambiguous return types that depend on runtime input, + the function will need strictly return a timeseries or numeric but not one or the other. + + Parameters: + ----------- + numeric_or_timeseries: bool + indicate whether the function returns a single numeric/tuple of numerics + or a timeseries/tuple of timeseries. False when numeric, True when timeseries + + shape_names: list[str] + list of return value names. These will become the xarray datavariable names in the output. + + Modifies: + --------- + func.numeric_or_timeseries + sets to numeric_or_timeseries argument + func.shape_names + sets to shape_names argument + """ + + def decorator(func): + setattr(func, "numeric_or_timeseries", numeric_or_timeseries) + setattr(func, "shape_names", shape_names) + return func + + return decorator diff --git a/pvdeg/degradation.py b/pvdeg/degradation.py index 2660f239..59670e51 100644 --- a/pvdeg/degradation.py +++ b/pvdeg/degradation.py @@ -1,5 +1,4 @@ -"""Collection of functions for degradation calculations. -""" +"""Collection of functions for degradation calculations.""" import numpy as np import pandas as pd @@ -8,11 +7,14 @@ from rex import Outputs from pathlib import Path from concurrent.futures import ProcessPoolExecutor, as_completed +from typing import Union from . import temperature from . import spectral from . import weather +from pvdeg.decorators import geospatial_quick_shape + # TODO: Clean up all those functions and add gaps functionality @@ -97,7 +99,7 @@ def _acceleration_factor(numerator, denominator): def vantHoff_deg( - weather_df, meta, I_chamber, temp_chamber, poa=None, temp=None, p=0.5, Tf=1.41 + weather_df: pd.DataFrame, meta: dict, I_chamber, temp_chamber, poa=None, temp=None, p=0.5, Tf=1.41 ): """ Van't Hoff Irradiance Degradation @@ -177,7 +179,16 @@ def _to_eq_vantHoff(temp, Tf=1.41): return Toeq -def IwaVantHoff(weather_df, meta, poa=None, temp=None, Teq=None, p=0.5, Tf=1.41): +@geospatial_quick_shape(0,["Iwa"]) +def IwaVantHoff( + weather_df: pd.DataFrame, + meta: dict, + poa: Union[pd.Series, pd.DataFrame] = None, + temp=None, + Teq=None, + p=0.5, + Tf=1.41, +): """ IWa : Environment Characterization [W/m²] For one year of degredation the controlled environmnet lamp settings will @@ -299,8 +310,8 @@ def _arrhenius_numerator(I_chamber, rh_chamber, temp_chamber, Ea, p, n): def arrhenius_deg( - weather_df, - meta, + weather_df: pd.DataFrame, + meta: dict, rh_outdoor, I_chamber, rh_chamber, @@ -463,17 +474,17 @@ def _RH_wa_arrhenius(rh_outdoor, temp, Ea, Teq=None, n=1): # TODO: CHECK # STANDARDIZE def IwaArrhenius( - weather_df, - meta, - rh_outdoor, - Ea, - poa=None, - temp=None, - RHwa=None, - Teq=None, - p=0.5, - n=1, -): + weather_df: pd.DataFrame, + meta: dict, + rh_outdoor: pd.Series, + Ea: float, + poa: pd.DataFrame = None, + temp: pd.Series = None, + RHwa: float = None, + Teq: float = None, + p: float = 0.5, + n: float = 1, +) -> float: """ Function to calculate IWa, the Environment Characterization [W/m²]. For one year of degredation the controlled environmnet lamp settings will @@ -643,8 +654,16 @@ def _gJtoMJ(gJ): def degradation( - spectra, rh_module, temp_module, wavelengths, Ea=40.0, n=1.0, p=0.5, C2=0.07, C=1.0 -): + spectra: pd.Series, + rh_module: pd.Series, + temp_module: pd.Series, + wavelengths: Union[int, np.ndarray[float]], + Ea: float = 40.0, + n: float = 1.0, + p: float = 0.5, + C2: float = 0.07, + C: float = 1.0, +) -> float: """ Compute degredation as double integral of Arrhenius (Activation Energy, RH, Temperature) and spectral (wavelength, irradiance) diff --git a/pvdeg/design.py b/pvdeg/design.py index 893d32a3..25c5b076 100644 --- a/pvdeg/design.py +++ b/pvdeg/design.py @@ -1,7 +1,8 @@ -"""Collection of functions for PV module design considertations. -""" +"""Collection of functions for PV module design considertations.""" from . import humidity +from pvdeg.decorators import geospatial_quick_shape +import pandas as pd def edge_seal_ingress_rate(avg_psat): @@ -41,7 +42,14 @@ def edge_seal_ingress_rate(avg_psat): return k -def edge_seal_width(weather_df, meta, k=None, years=25, from_dew_point=False): +@geospatial_quick_shape(0, ["width"]) +def edge_seal_width( + weather_df: pd.DataFrame, + meta: dict, + k: float = None, + years: int = 25, + from_dew_point: bool = False, +): """ Determine the width of edge seal required for given number of years water ingress. diff --git a/pvdeg/fatigue.py b/pvdeg/fatigue.py index 82fbc22e..ed0f88f8 100644 --- a/pvdeg/fatigue.py +++ b/pvdeg/fatigue.py @@ -1,7 +1,8 @@ import numpy as np import pandas as pd from scipy.constants import convert_temperature -from . import temperature +from pvdeg import temperature +from pvdeg.decorators import geospatial_quick_shape def _avg_daily_temp_change(time_range, temp_cell): @@ -97,18 +98,19 @@ def _times_over_reversal_number(temp_cell, reversal_temp): return num_changes_temp_hist +@geospatial_quick_shape(0, ['damage']) def solder_fatigue( - weather_df, - meta, - time_range=None, - temp_cell=None, - reversal_temp=54.8, - n=1.9, - b=0.33, - C1=405.6, - Q=0.12, - wind_factor=None, -): + weather_df: pd.DataFrame, + meta: dict, + time_range: pd.Series = None, + temp_cell: pd.Series = None, + reversal_temp: float = 54.8, + n: float = 1.9, + b: float = 0.33, + C1: float = 405.6, + Q: float = 0.12, + wind_factor: float = 0.33, +) -> float: """ Get the Thermomechanical Fatigue of flat plate photovoltaic module solder joints. Damage will be returned as the rate of solder fatigue for one year. Based on: diff --git a/pvdeg/geospatial.py b/pvdeg/geospatial.py index 793ef66e..1286e7e7 100644 --- a/pvdeg/geospatial.py +++ b/pvdeg/geospatial.py @@ -2,9 +2,11 @@ Collection of classes and functions for geospatial analysis. """ -from . import standards -from . import humidity -from . import letid +from . import ( + standards, + humidity, + letid, +) import xarray as xr import dask.array as da @@ -16,6 +18,8 @@ import cartopy.crs as ccrs import cartopy.io.shapereader as shpreader +from collections.abc import Callable + def start_dask(hpc=None): """ @@ -79,6 +83,28 @@ def start_dask(hpc=None): return client +def _df_from_arbitrary(res, func): + numerics = (int, float, np.number) + arrays = (np.ndarray, pd.Series) + + if isinstance(res, pd.DataFrame): + return res + elif isinstance(res, pd.Series): + return pd.DataFrame(res, columns=[func.__name__]) + elif isinstance(res, (int, float)): + return pd.DataFrame([res], columns=[func.__name__]) + elif isinstance(res, tuple) and all(isinstance(item, numerics) for item in res): + return pd.DataFrame([res]) + elif isinstance(res, tuple) and all(isinstance(item, arrays) for item in res): + return pd.concat( + res, axis=1 + ) # they must all be the same length here or this will error out + else: + raise NotImplementedError( + f"function return type: {type(res)} not available for geospatial analysis yet." + ) + + def calc_gid(ds_gid, meta_gid, func, **kwargs): """ Calculates a single gid for a given function. @@ -101,7 +127,8 @@ def calc_gid(ds_gid, meta_gid, func, **kwargs): """ df_weather = ds_gid.to_dataframe() - df_res = func(weather_df=df_weather, meta=meta_gid, **kwargs) + res = func(weather_df=df_weather, meta=meta_gid, **kwargs) + df_res = _df_from_arbitrary(res, func) # convert all return types to dataframe ds_res = xr.Dataset.from_dataframe(df_res) if not df_res.index.name: @@ -229,11 +256,12 @@ def output_template( }, coords={dim: ds_gids[dim] for dim in dims}, attrs=global_attrs, - )#.chunk({dim: ds_gids.chunks[dim] for dim in dims}) + ) # .chunk({dim: ds_gids.chunks[dim] for dim in dims}) return output_template +# we should be able to get rid of this with the new autotemplating function and decorator def template_parameters(func): """ Output parameters for xarray template. @@ -312,7 +340,7 @@ def template_parameters(func): add_dims = {} else: - raise ValueError(f"No preset output template for function {func}.") + raise NotImplementedError(f"No preset output template for function {func}.") parameters = { "shapes": shapes, @@ -352,6 +380,48 @@ def zero_template( return res +def auto_template(func: Callable, ds_gids: xr.Dataset) -> xr.Dataset: + """ + Automatically create a template for a target function: `func`. + Only works on functions that have the `numeric_or_timeseries` and `shape_names` attributes. + These attributes are assigned at function definition with the `@geospatial_quick_shape` decorator. + + Otherwise you will have to create your own template. + Don't worry, this is easy. See the Geospatial Templates Notebook + for more information. + + examples: + --------- + + the function returns a numeric value + >>> pvdeg.design.edge_seal_width + + the function returns a timeseries result + >>> pvdeg.module.humidity + + counter example: + ---------------- + the function could either return a single numeric or a series based on changed in + the input. Because it does not have a known result shape we cannot determine the + attributes required for autotemplating ahead of time. + """ + + if not (hasattr(func, "numeric_or_timeseries") and hasattr(func, "shape_names")): + raise ValueError( + f"{func.__name__} cannot be autotemplated. create a template manually" + ) + + if func.numeric_or_timeseries == 0: + shapes = {datavar: ("gid",) for datavar in func.shape_names} + elif func.numeric_or_timeseries == 1: + shapes = {datavar: ("gid", "time") for datavar in func.shape_names} + + template = output_template( # zeros_template? + ds_gids=ds_gids, shapes=shapes + ) + + return template + def plot_USA( xr_res, cmap="viridis", vmin=None, vmax=None, title=None, cb_title=None, fp=None ): diff --git a/pvdeg/humidity.py b/pvdeg/humidity.py index bf421bc2..2ef26905 100644 --- a/pvdeg/humidity.py +++ b/pvdeg/humidity.py @@ -4,15 +4,19 @@ import numpy as np import pandas as pd import pvlib -from numba import jit +from numba import njit from rex import NSRDBX from rex import Outputs from pathlib import Path from concurrent.futures import ProcessPoolExecutor, as_completed -from . import temperature -from . import spectral -from . import weather +from . import ( + temperature, + spectral, + weather +) + +from pvdeg.decorators import geospatial_quick_shape def _ambient(weather_df): @@ -52,7 +56,7 @@ def _ambient(weather_df): # TODO: When is dew_yield used? -@jit(nopython=True, error_model="python") +@njit def dew_yield(elevation, dew_point, dry_bulb, wind_speed, n): """ Estimates the dew yield in [mm/day]. Calculation taken from: @@ -389,7 +393,7 @@ def _ceq(Csat, rh_SurfaceOutside): return Ceq -@jit(nopython=True) +@njit def Ce_numba( start, temp_module, @@ -650,6 +654,7 @@ def backsheet( return backsheet +@geospatial_quick_shape(1, ["RH_surface_outside", "RH_front_encap", "RH_back_encap", "RH_backsheet"]) def module( weather_df, meta, diff --git a/pvdeg/letid.py b/pvdeg/letid.py index 9b805730..2a0c3114 100644 --- a/pvdeg/letid.py +++ b/pvdeg/letid.py @@ -11,6 +11,7 @@ from pvdeg import collection, utilities, standards, DATA_DIR +from pvdeg.decorators import geospatial_quick_shape def tau_now(tau_0, tau_deg, n_b): @@ -870,6 +871,7 @@ def calc_injection_outdoors(results): return injection +@geospatial_quick_shape(1, ["Temperature", "Injection", "NA", "NB", "NC", "tau", "Jsc", "Voc", "Isc", "FF", "Pmp", "Pmp_norm"]) def calc_letid_outdoors( tau_0, tau_deg, diff --git a/pvdeg/spectral.py b/pvdeg/spectral.py index 53b73ea1..cdf273ba 100644 --- a/pvdeg/spectral.py +++ b/pvdeg/spectral.py @@ -3,9 +3,22 @@ """ import pvlib - - -def solar_position(weather_df, meta): +import pandas as pd +from pvdeg.decorators import geospatial_quick_shape + + +@geospatial_quick_shape( + 1, + [ + "apparent_zenith", + "zenith", + "apparent_elevation", + "elevation", + "azimuth", + "equation_of_time", + ], +) +def solar_position(weather_df: pd.DataFrame, meta: dict) -> pd.DataFrame: """ Calculate solar position using pvlib based on weather data from the National Solar Radiation Database (NSRDB) for a given location (gid). @@ -14,7 +27,7 @@ def solar_position(weather_df, meta): ---------- weather_df : pandas.DataFrame Weather data for given location. - meta : pandas.Series + meta : dict Meta data of location. Returns @@ -41,9 +54,24 @@ def solar_position(weather_df, meta): return solar_position +@geospatial_quick_shape( + 1, + [ + "poa_global", + "poa_direct", + "poa_diffuse", + "poa_sky_diffuse", + "poa_ground_diffuse", + ], +) def poa_irradiance( - weather_df, meta, sol_position=None, tilt=None, azimuth=None, sky_model="isotropic" -): + weather_df: pd.DataFrame, + meta: dict, + sol_position=None, + tilt=None, + azimuth=None, + sky_model="isotropic", +) -> pd.DataFrame: """ Calculate plane-of-array (POA) irradiance using pvlib based on weather data from the National Solar Radiation Database (NSRDB) for a given location (gid). diff --git a/pvdeg/standards.py b/pvdeg/standards.py index 895896b6..ea1e7554 100644 --- a/pvdeg/standards.py +++ b/pvdeg/standards.py @@ -11,12 +11,15 @@ from pathlib import Path from random import random from concurrent.futures import ProcessPoolExecutor, as_completed +from typing import Union, Tuple # from gaps import ProjectPoints from pvdeg import temperature, spectral, utilities, weather +from pvdeg.decorators import geospatial_quick_shape +@geospatial_quick_shape(1, ["T_0", "T_inf", "poa"]) def eff_gap_parameters( weather_df=None, meta=None, @@ -187,20 +190,21 @@ def eff_gap(T_0, T_inf, T_measured, T_ambient, poa, x_0=6.5, poa_min=400, t_amb_ return x_eff +@geospatial_quick_shape(0, ["x","T98_0", "T98_inf"]) # numeric result, with corresponding datavariable names def standoff( - weather_df=None, - meta=None, - weather_kwarg=None, - tilt=None, - azimuth=None, - sky_model="isotropic", - temp_model="sapm", - conf_0="insulated_back_glass_polymer", - conf_inf="open_rack_glass_polymer", - T98=70, # [°C] - x_0=6.5, # [cm] - wind_factor=0.33, -): + weather_df: pd.DataFrame = None, + meta: dict = None, + weather_kwarg: dict = None, + tilt: Union[float, int] = None, + azimuth: Union[float, int] = None, + sky_model: str = "isotropic", + temp_model: str = "sapm", + conf_0: str = "insulated_back_glass_polymer", + conf_inf: str = "open_rack_glass_polymer", + T98: float = 70, # [°C] + x_0: float = 6.5, # [cm] + wind_factor: float = 0.33, +) -> pd.DataFrame: """ Calculate a minimum standoff distance for roof mounded PV systems. Will default to horizontal tilt. If the azimuth is not provided, it @@ -414,6 +418,7 @@ def interpret_standoff(standoff_1=None, standoff_2=None): return Output +@geospatial_quick_shape(0, ["T98"]) def T98_estimate( weather_df=None, meta=None, @@ -504,7 +509,7 @@ def T98_estimate( meta=meta, poa=poa, temp_model=temp_model, - conf_inf=conf_inf, + conf=conf_inf, wind_factor=wind_factor, ) T98_inf = T_inf.quantile(q=0.98, interpolation="linear") @@ -517,7 +522,7 @@ def T98_estimate( meta=meta, poa=poa, temp_model=temp_model, - conf_0=conf_0, + conf=conf_0, wind_factor=wind_factor, ) T98_0 = T_0.quantile(q=0.98, interpolation="linear") diff --git a/pvdeg/temperature.py b/pvdeg/temperature.py index d49bf2a1..73561cf0 100644 --- a/pvdeg/temperature.py +++ b/pvdeg/temperature.py @@ -4,8 +4,12 @@ import pvlib import pvdeg +from pvdeg.decorators import geospatial_quick_shape +import pandas as pd +from typing import Union +@geospatial_quick_shape(1, ['module_temperature']) def module( weather_df, meta, @@ -96,14 +100,15 @@ def module( return module_temperature +@geospatial_quick_shape(1, ["cell_temperature"]) def cell( - weather_df, - meta, - poa=None, - temp_model="sapm", - conf="open_rack_glass_polymer", - wind_factor=0.33, -): + weather_df: pd.DataFrame, + meta: dict, + poa: Union[pd.DataFrame, pd.Series] = None, + temp_model: str = "sapm", + conf: str = "open_rack_glass_polymer", + wind_factor: float = 0.33, +) -> pd.DataFrame: """ Calculate the PV cell temperature using PVLIB Currently this only supports the SAPM temperature model. diff --git a/tests/test_geospatial.py b/tests/test_geospatial.py index 6a8ccec0..14c3aacd 100644 --- a/tests/test_geospatial.py +++ b/tests/test_geospatial.py @@ -11,7 +11,6 @@ with open(os.path.join(TEST_DATA_DIR, "summit-weather.pkl"), 'rb') as f: GEO_WEATHER = pickle.load(f) -# refactor def test_analysis_standoff(): res_ds = pvdeg.geospatial.analysis( weather_ds=GEO_WEATHER, diff --git a/tutorials_and_tools/tutorials_and_tools/Geospatial Templates.ipynb b/tutorials_and_tools/tutorials_and_tools/Geospatial Templates.ipynb new file mode 100644 index 00000000..b9b47c09 --- /dev/null +++ b/tutorials_and_tools/tutorials_and_tools/Geospatial Templates.ipynb @@ -0,0 +1,5569 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "metadata": {}, + "outputs": [], + "source": [ + "import pvdeg\n", + "from pvdeg import TEST_DATA_DIR\n", + "import pickle\n", + "import xarray as xr\n", + "import pandas as pd\n", + "import numpy as np\n", + "import os" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Geospatial Templates\n", + "\n", + "When running a geospatial analysis using `pvdeg.geospatial.analysis` on arbitary `pvdeg` functions you will need to specify a template for the shape of the output data. This is because the input data comes with dimensions of gid and time while the output will have data in a different shape usually corresonding to coordinates.\n", + "- gid, identification number corresponding to an NSRDB datapoint's location \n", + "- time, timeseries corresponding to the hourly time indicies of NSRDB datapoint's yearly meteorological data.\n", + "\n", + "Follow the steps below to see how we generate templates before running the analysis.\n", + "\n", + "The only functions where this is not required are currently `pvdeg.standards.standoff`, `pvdeg.humidity.moduke` and, `letid.calc_letid_outdoors` as they are predefined within the package." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Loading Geospatial Data\n", + "\n", + "This step skips over making the `pvdeg.weather.get` call with `geospatial == True`. See the [Duramat Demo](./DuraMAT%20Live%20Demo.ipynb) for information on how to do this traditionally.\n", + "\n", + "We can also use a `GeospatialScenario` object. See the [Geospatial Scenario Tutorial](./Scenario%20-%20Geospatial.ipynb) for more information on how to use this approach.\n", + "\n", + "*The cell below loads a pickled xarray object, this is not the best way to do this. xarray datasets should be stored as `.nc` - netcdf files*" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 9MB\n",
+       "Dimensions:            (time: 17520, gid: 11)\n",
+       "Coordinates:\n",
+       "  * gid                (gid) int64 88B 449211 452064 453020 ... 460613 462498\n",
+       "  * time               (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31T...\n",
+       "Data variables:\n",
+       "    temp_air           (time, gid) float64 2MB -12.0 -8.1 -14.2 ... -4.3 -6.2\n",
+       "    wind_speed         (time, gid) float64 2MB 0.6 0.6 0.3 0.6 ... 0.9 1.0 1.1\n",
+       "    dhi                (time, gid) float64 2MB 0.0 0.0 0.0 ... 13.0 18.0 19.0\n",
+       "    ghi                (time, gid) float64 2MB 0.0 0.0 0.0 ... 13.0 24.0 19.0\n",
+       "    dni                (time, gid) float64 2MB 0.0 0.0 0.0 0.0 ... 0.0 126.0 1.0\n",
+       "    relative_humidity  (time, gid) float64 2MB 99.93 79.41 100.0 ... 95.93 100.0\n",
+       "Attributes:\n",
+       "    full_version_record:  {"rex": "0.2.80", "pandas": "2.0.0", "numpy": "1.23...\n",
+       "    package:              rex\n",
+       "    version:              4.0.0
" + ], + "text/plain": [ + " Size: 9MB\n", + "Dimensions: (time: 17520, gid: 11)\n", + "Coordinates:\n", + " * gid (gid) int64 88B 449211 452064 453020 ... 460613 462498\n", + " * time (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31T...\n", + "Data variables:\n", + " temp_air (time, gid) float64 2MB -12.0 -8.1 -14.2 ... -4.3 -6.2\n", + " wind_speed (time, gid) float64 2MB 0.6 0.6 0.3 0.6 ... 0.9 1.0 1.1\n", + " dhi (time, gid) float64 2MB 0.0 0.0 0.0 ... 13.0 18.0 19.0\n", + " ghi (time, gid) float64 2MB 0.0 0.0 0.0 ... 13.0 24.0 19.0\n", + " dni (time, gid) float64 2MB 0.0 0.0 0.0 0.0 ... 0.0 126.0 1.0\n", + " relative_humidity (time, gid) float64 2MB 99.93 79.41 100.0 ... 95.93 100.0\n", + "Attributes:\n", + " full_version_record: {\"rex\": \"0.2.80\", \"pandas\": \"2.0.0\", \"numpy\": \"1.23...\n", + " package: rex\n", + " version: 4.0.0" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "geo_meta = pd.read_csv(os.path.join(TEST_DATA_DIR, 'summit-meta.csv'), index_col=0)\n", + "\n", + "with open(os.path.join(TEST_DATA_DIR, 'summit-weather.pkl'), 'rb') as f:\n", + " geo_weather = pickle.load(f)\n", + "\n", + "geo_weather" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating Templates Manually\n", + "\n", + "`pvdeg.geospatial.ouput_template` we can produce a template for our result data. \n", + "\n", + "We need to do this because different functions return different types of values, some return multiple values as tuples, some return only single numerics, others return timeseries results. We need to specify the shape of our data to create an output xarray dataset. " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examples\n", + "\n", + "## 98ᵗʰ module percential temperature at Standoff Height\n", + "\n", + "Say we want to estimate the 98ᵗʰ percential temperature for the module at the given tilt, azimuth, and x_eff. `PVDeg` has a function to do this, `pvdeg.standards.T98_estimate` BUT it doesn't have a preset geospatial template. We will need to make one.\n", + "\n", + "- look at the function return values. \n", + "From the docstring we can see that `T98_estimate` only has one return value. IMPORTANT, this value is a single float, NOT a timeseries. This means our output shape will only be dependent on the input identifier and NOT time. \n", + "\n", + "Therefore we will map the output variable `T98` to the location identifier `gid` using a dictionary with `str: tuple` mappings.\n", + "\n", + " *IMPORTANT: you must use the syntax below where the variable maps to a tuple of the coordinates. in this case there needs to be a trailing comma in the tuple or python will iterate over the characters in the tuple instead of the elements. See further examples to alleviate confusion.*" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": {}, + "outputs": [], + "source": [ + "# define output shape\n", + "shapes = {\n", + " \"T98\" : (\"gid\",) # one return value at each datapoint, only dependent on datapoint, not time\n", + "}\n", + "\n", + "# create xarray template for output to be populated when analysis is run\n", + "template = pvdeg.geospatial.output_template(\n", + " ds_gids=geo_weather, \n", + " shapes=shapes,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.7 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n" + ] + } + ], + "source": [ + "geo_estimate_temp = pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.standards.T98_estimate,\n", + " template=template\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Glass Glass Estimated Module Temperature\n", + "\n", + "Now we want to calculate geospatial timeseries temperature values for a module using `pvdeg.temperature.module`. This is not super practical because all `pvdeg` functions that need to use tempeature for their calculations preform the temperature calculation internally, this is just for show. \n", + "\n", + "This calculation differs from the above because the temperature functions return the module temperature in a timeseries format. So we care about 2 dimensions, location identifier and TIME." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": {}, + "outputs": [], + "source": [ + "# define output shape\n", + "shapes = {\n", + " \"module_temperature\" : (\"gid\", \"time\") # one return value at each datapoint, only dependent on datapoint, not time \n", + "}\n", + "\n", + "# create xarray template for output to be populated when analysis is run\n", + "temperature_template = pvdeg.geospatial.output_template(\n", + " ds_gids=geo_weather, \n", + " shapes=shapes,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.7 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n" + ] + } + ], + "source": [ + "geo_temperature_res = pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.temperature.module,\n", + " template=temperature_template, # use the template we created\n", + "\n", + " conf='open_rack_glass_glass' # provide kwargs for function here\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": {}, + "outputs": [ + { + "data": { + "text/plain": [ + "[]" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# plot the temperature at ONE of the geospatial analysis result locations but we have calculated all of these.\n", + "import matplotlib.pyplot as plt\n", + "module_temps = geo_temperature_res['module'].sel(latitude=39.89, longitude='-106.42').values\n", + "\n", + "plt.plot(module_temps)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Self Explaining Code\n", + "\n", + "If we are looking at adding templates for other functions, we can also look at the 3 presaved templates for existing `pvdeg` functions. Visit [pvdeg.geospatial.template_parameters](../../pvdeg/geospatial.py) and inspect this function to see how these different target functions utilize templates and shapes." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Creating Templates Programatically\n", + "\n", + "We can use `pvdeg.geospatial.autotemplate` to generate a template for a given function. This can return a bad result which will fail or work improperly when running `pvdeg.geospatial.analysis` with the generated template. Results should be scrutinized to make sure they are the right format." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Examples Below\n", + "Steps\n", + "- Create template using autotemplating function. Pulls in information about function to determine shape of output. Not usable on functions with ambigious return types.\n", + "- Call geospatial analysis function using template" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geospatial Cell Temperature Calculation" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.7 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 11MB\n",
+       "Dimensions:    (latitude: 8, longitude: 10, time: 17520)\n",
+       "Coordinates:\n",
+       "  * latitude   (latitude) float64 64B 39.41 39.45 39.53 ... 39.69 39.81 39.89\n",
+       "  * longitude  (longitude) float64 80B -106.4 -106.3 -106.3 ... -105.9 -105.9\n",
+       "  * time       (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31T23:30:00\n",
+       "Data variables:\n",
+       "    cell       (time, latitude, longitude) float64 11MB nan nan nan ... nan nan
" + ], + "text/plain": [ + " Size: 11MB\n", + "Dimensions: (latitude: 8, longitude: 10, time: 17520)\n", + "Coordinates:\n", + " * latitude (latitude) float64 64B 39.41 39.45 39.53 ... 39.69 39.81 39.89\n", + " * longitude (longitude) float64 80B -106.4 -106.3 -106.3 ... -105.9 -105.9\n", + " * time (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31T23:30:00\n", + "Data variables:\n", + " cell (time, latitude, longitude) float64 11MB nan nan nan ... nan nan" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# create a template using auto_template for the desired function\n", + "cell_temp_template = pvdeg.geospatial.auto_template(\n", + " func=pvdeg.temperature.cell,\n", + " ds_gids=geo_weather\n", + ")\n", + "\n", + "# run the geospatial analysis with the template\n", + "pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.temperature.cell,\n", + " template=cell_temp_template\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geospatial Module Temperature Calculation" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.7 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 11MB\n",
+       "Dimensions:    (latitude: 8, longitude: 10, time: 17520)\n",
+       "Coordinates:\n",
+       "  * latitude   (latitude) float64 64B 39.41 39.45 39.53 ... 39.69 39.81 39.89\n",
+       "  * longitude  (longitude) float64 80B -106.4 -106.3 -106.3 ... -105.9 -105.9\n",
+       "  * time       (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31T23:30:00\n",
+       "Data variables:\n",
+       "    module     (time, latitude, longitude) float64 11MB nan nan nan ... nan nan
" + ], + "text/plain": [ + " Size: 11MB\n", + "Dimensions: (latitude: 8, longitude: 10, time: 17520)\n", + "Coordinates:\n", + " * latitude (latitude) float64 64B 39.41 39.45 39.53 ... 39.69 39.81 39.89\n", + " * longitude (longitude) float64 80B -106.4 -106.3 -106.3 ... -105.9 -105.9\n", + " * time (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31T23:30:00\n", + "Data variables:\n", + " module (time, latitude, longitude) float64 11MB nan nan nan ... nan nan" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "module_temp_template = pvdeg.geospatial.auto_template(\n", + " func=pvdeg.temperature.module,\n", + " ds_gids=geo_weather\n", + ")\n", + "\n", + "pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.temperature.module,\n", + " template=module_temp_template\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geospatial Solar Position Calculation" + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 67MB\n",
+       "Dimensions:             (latitude: 8, longitude: 10, time: 17520)\n",
+       "Coordinates:\n",
+       "  * latitude            (latitude) float64 64B 39.41 39.45 39.53 ... 39.81 39.89\n",
+       "  * longitude           (longitude) float64 80B -106.4 -106.3 ... -105.9 -105.9\n",
+       "  * time                (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31...\n",
+       "Data variables:\n",
+       "    apparent_zenith     (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    zenith              (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    apparent_elevation  (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    elevation           (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    azimuth             (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    equation_of_time    (time, latitude, longitude) float64 11MB nan nan ... nan
" + ], + "text/plain": [ + " Size: 67MB\n", + "Dimensions: (latitude: 8, longitude: 10, time: 17520)\n", + "Coordinates:\n", + " * latitude (latitude) float64 64B 39.41 39.45 39.53 ... 39.81 39.89\n", + " * longitude (longitude) float64 80B -106.4 -106.3 ... -105.9 -105.9\n", + " * time (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31...\n", + "Data variables:\n", + " apparent_zenith (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " zenith (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " apparent_elevation (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " elevation (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " azimuth (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " equation_of_time (time, latitude, longitude) float64 11MB nan nan ... nan" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "solar_position_template = pvdeg.geospatial.auto_template(\n", + " func=pvdeg.spectral.solar_position,\n", + " ds_gids=geo_weather\n", + ")\n", + "\n", + "pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.spectral.solar_position,\n", + " template=solar_position_template\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geospatial POA Irradiance Calculation" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.7 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 56MB\n",
+       "Dimensions:             (latitude: 8, longitude: 10, time: 17520)\n",
+       "Coordinates:\n",
+       "  * latitude            (latitude) float64 64B 39.41 39.45 39.53 ... 39.81 39.89\n",
+       "  * longitude           (longitude) float64 80B -106.4 -106.3 ... -105.9 -105.9\n",
+       "  * time                (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31...\n",
+       "Data variables:\n",
+       "    poa_global          (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    poa_direct          (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    poa_diffuse         (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    poa_sky_diffuse     (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    poa_ground_diffuse  (time, latitude, longitude) float64 11MB nan nan ... nan
" + ], + "text/plain": [ + " Size: 56MB\n", + "Dimensions: (latitude: 8, longitude: 10, time: 17520)\n", + "Coordinates:\n", + " * latitude (latitude) float64 64B 39.41 39.45 39.53 ... 39.81 39.89\n", + " * longitude (longitude) float64 80B -106.4 -106.3 ... -105.9 -105.9\n", + " * time (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31...\n", + "Data variables:\n", + " poa_global (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " poa_direct (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " poa_diffuse (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " poa_sky_diffuse (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " poa_ground_diffuse (time, latitude, longitude) float64 11MB nan nan ... nan" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "poa_irradiance_template = pvdeg.geospatial.auto_template(\n", + " func=pvdeg.spectral.poa_irradiance,\n", + " ds_gids=geo_weather\n", + ")\n", + "\n", + "pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.spectral.poa_irradiance,\n", + " template=poa_irradiance_template\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geospatial 98th Percentile Operating Temperature Calculation" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.7 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 784B\n",
+       "Dimensions:       (latitude: 8, longitude: 10)\n",
+       "Coordinates:\n",
+       "  * latitude      (latitude) float64 64B 39.41 39.45 39.53 ... 39.69 39.81 39.89\n",
+       "  * longitude     (longitude) float64 80B -106.4 -106.3 -106.3 ... -105.9 -105.9\n",
+       "Data variables:\n",
+       "    T98_estimate  (latitude, longitude) float64 640B nan nan nan ... nan nan nan
" + ], + "text/plain": [ + " Size: 784B\n", + "Dimensions: (latitude: 8, longitude: 10)\n", + "Coordinates:\n", + " * latitude (latitude) float64 64B 39.41 39.45 39.53 ... 39.69 39.81 39.89\n", + " * longitude (longitude) float64 80B -106.4 -106.3 -106.3 ... -105.9 -105.9\n", + "Data variables:\n", + " T98_estimate (latitude, longitude) float64 640B nan nan nan ... nan nan nan" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "standoff_template = pvdeg.geospatial.auto_template(\n", + " func=pvdeg.standards.T98_estimate, \n", + " ds_gids=geo_weather\n", + " )\n", + "\n", + "pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.standards.T98_estimate,\n", + " template=standoff_template\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geospatial Module Humidity Calculation" + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.7 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 45MB\n",
+       "Dimensions:             (latitude: 8, longitude: 10, time: 17520)\n",
+       "Coordinates:\n",
+       "  * latitude            (latitude) float64 64B 39.41 39.45 39.53 ... 39.81 39.89\n",
+       "  * longitude           (longitude) float64 80B -106.4 -106.3 ... -105.9 -105.9\n",
+       "  * time                (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31...\n",
+       "Data variables:\n",
+       "    RH_surface_outside  (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    RH_front_encap      (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    RH_back_encap       (time, latitude, longitude) float64 11MB nan nan ... nan\n",
+       "    RH_backsheet        (time, latitude, longitude) float64 11MB nan nan ... nan
" + ], + "text/plain": [ + " Size: 45MB\n", + "Dimensions: (latitude: 8, longitude: 10, time: 17520)\n", + "Coordinates:\n", + " * latitude (latitude) float64 64B 39.41 39.45 39.53 ... 39.81 39.89\n", + " * longitude (longitude) float64 80B -106.4 -106.3 ... -105.9 -105.9\n", + " * time (time) datetime64[ns] 140kB 2022-01-01 ... 2022-12-31...\n", + "Data variables:\n", + " RH_surface_outside (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " RH_front_encap (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " RH_back_encap (time, latitude, longitude) float64 11MB nan nan ... nan\n", + " RH_backsheet (time, latitude, longitude) float64 11MB nan nan ... nan" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "humidity_template = pvdeg.geospatial.auto_template(\n", + " func=pvdeg.humidity.module,\n", + " ds_gids=geo_weather\n", + ")\n", + "\n", + "pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.humidity.module,\n", + " template=humidity_template\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geospatial IwaVantHoff Environment Characterization Calculation" + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.9 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.7 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.8 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.4 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.5 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n", + "The array tilt angle was not provided, therefore the latitude tilt of 39.6 was used.\n", + "The array azimuth was not provided, therefore an azimuth of 180.0 was used.\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 784B\n",
+       "Dimensions:      (latitude: 8, longitude: 10)\n",
+       "Coordinates:\n",
+       "  * latitude     (latitude) float64 64B 39.41 39.45 39.53 ... 39.69 39.81 39.89\n",
+       "  * longitude    (longitude) float64 80B -106.4 -106.3 -106.3 ... -105.9 -105.9\n",
+       "Data variables:\n",
+       "    IwaVantHoff  (latitude, longitude) float64 640B nan nan nan ... nan nan nan
" + ], + "text/plain": [ + " Size: 784B\n", + "Dimensions: (latitude: 8, longitude: 10)\n", + "Coordinates:\n", + " * latitude (latitude) float64 64B 39.41 39.45 39.53 ... 39.69 39.81 39.89\n", + " * longitude (longitude) float64 80B -106.4 -106.3 -106.3 ... -105.9 -105.9\n", + "Data variables:\n", + " IwaVantHoff (latitude, longitude) float64 640B nan nan nan ... nan nan nan" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "iwa_vant_hoff_template = pvdeg.geospatial.auto_template(\n", + " func=pvdeg.degradation.IwaVantHoff,\n", + " ds_gids=geo_weather\n", + ")\n", + "\n", + "pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.degradation.IwaVantHoff,\n", + " template=iwa_vant_hoff_template\n", + ")" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Geospatial Edge Seal Width Calculation" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 784B\n",
+       "Dimensions:          (latitude: 8, longitude: 10)\n",
+       "Coordinates:\n",
+       "  * latitude         (latitude) float64 64B 39.41 39.45 39.53 ... 39.81 39.89\n",
+       "  * longitude        (longitude) float64 80B -106.4 -106.3 ... -105.9 -105.9\n",
+       "Data variables:\n",
+       "    edge_seal_width  (latitude, longitude) float64 640B nan nan nan ... nan nan
" + ], + "text/plain": [ + " Size: 784B\n", + "Dimensions: (latitude: 8, longitude: 10)\n", + "Coordinates:\n", + " * latitude (latitude) float64 64B 39.41 39.45 39.53 ... 39.81 39.89\n", + " * longitude (longitude) float64 80B -106.4 -106.3 ... -105.9 -105.9\n", + "Data variables:\n", + " edge_seal_width (latitude, longitude) float64 640B nan nan nan ... nan nan" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "edge_seal_template = pvdeg.geospatial.auto_template(\n", + " func=pvdeg.design.edge_seal_width,\n", + " ds_gids=geo_weather\n", + ")\n", + "\n", + "pvdeg.geospatial.analysis(\n", + " weather_ds=geo_weather,\n", + " meta_df=geo_meta,\n", + " func=pvdeg.design.edge_seal_width,\n", + " template=edge_seal_template\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "\n", + "
<xarray.Dataset> Size: 176B\n",
+       "Dimensions:  (gid: 11)\n",
+       "Coordinates:\n",
+       "  * gid      (gid) int64 88B 449211 452064 453020 ... 459670 460613 462498\n",
+       "Data variables:\n",
+       "    width    (gid) float64 88B dask.array<chunksize=(11,), meta=np.ndarray>
" + ], + "text/plain": [ + " Size: 176B\n", + "Dimensions: (gid: 11)\n", + "Coordinates:\n", + " * gid (gid) int64 88B 449211 452064 453020 ... 459670 460613 462498\n", + "Data variables:\n", + " width (gid) float64 88B dask.array" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "edge_seal_template" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "fem_diff", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.14" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +}