diff --git a/.github/workflows/dev_test.yml b/.github/workflows/dev_test.yml index cdbcf0cf..bdae34fb 100644 --- a/.github/workflows/dev_test.yml +++ b/.github/workflows/dev_test.yml @@ -45,5 +45,5 @@ jobs: run: | pip install pytest pip install setuptools - pip install -e ".[download]" + pip install -e ".[download, pvlib]" pytest tests/dev/ diff --git a/.github/workflows/pr_test.yml b/.github/workflows/pr_test.yml index 9637c60d..67498f8a 100644 --- a/.github/workflows/pr_test.yml +++ b/.github/workflows/pr_test.yml @@ -45,5 +45,5 @@ jobs: run: | pip install pytest pip install setuptools - pip install -e ".[download]" + pip install -e ".[download, pvlib]" pytest tests/pr/ diff --git a/README.md b/README.md index 2b1b912a..f12f83ba 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ GEODATA -- +- [![DOI](https://zenodo.org/badge/218690319.svg)](https://zenodo.org/badge/latestdoi/218690319) diff --git a/docs/source/datasets/era5/era5_outputs.md b/docs/source/datasets/era5/era5_outputs.md index 5bf321e4..c260ce93 100644 --- a/docs/source/datasets/era5/era5_outputs.md +++ b/docs/source/datasets/era5/era5_outputs.md @@ -7,6 +7,9 @@ * Wind generation time-series (`wind`) * Wind speed time-series (`windspd`) * Solar photovoltaic generation time-series (`pv`) +* Solar photovoltaic generation time-series using the `pvlib` library (`pvlib`). For information on using `pvlib` with `geodata`, see: [era5_pvlib.py](https://github.com/GeodataTools/geodata/blob/master/docs/source/datasets/era5/era5_pvlib.md). + + ## Supported ERA5 Outputs diff --git a/docs/source/datasets/era5/era5_pvlib.md b/docs/source/datasets/era5/era5_pvlib.md new file mode 100644 index 00000000..d4e0b120 --- /dev/null +++ b/docs/source/datasets/era5/era5_pvlib.md @@ -0,0 +1,89 @@ +# Generating PV Outputs with `pvlib` + +**geodata** also supports solar photovoltaic (PV) generation modeling using the `pvlib` library, integrating solar position, irradiance, and PV system modeling with flexible configuration options. + +For more information on `pvlib`, see: [pvlib-python.readthedocs.io](https://pvlib-python.readthedocs.io/en/stable/) + +## Supported PV Output + + +### Solar Photovoltaic Generation Time-series (`pvlib_model`) + +Convert ERA5 data into PV generation estimates using `pvlib` functionality. + +```python +pvlib_model( + cutout, + system, + model_chain_config, + vars = [ + 'influx_diffuse', + 'influx_direct', + 'dewpoint_temperature', + 'temperature', + 'wnd100m' + ] + ) -> xarray.Dataset +``` + +#### Parameters + +* `cutout` - `xr.Dataset` - An input ERA5 cutout with the following variables: + - **influx_diffuse** (*float*) - Diffuse horizontal irradiance. + - **influx_direct** (*float*) - Direct normal irradiance. + - **dewpoint_temperature** (*float*) - Dewpoint temperature in Celsius. + - **temperature** (*float*) - Air temperature in Celsius. + - **wnd100m** (*float*) - Wind speed at 100m. +* `system` - A `PVSystem` class defined by `pv_system()`, describing the collection and interactions of PV system components to be used in modeling. +* `model_chain_config` - A `ModelChainConfig` class that defines pvlib ModelChain parameters that can be passed to one or more instances of pvlib_model(). Allows reuse of a common set of ModelChain parameters across multiple PVSystems or even multiple cutouts. +*Note*: For full information on input parameters for the `PVSystem` and `ModelChainConfig` classes, [see the docstrings here.](https://github.com/GeodataTools/geodata/blob/master/src/geodata/pvlib.py) + + +#### Output + +* Outputs an `xarray.Dataset` containing: + - **ac** (*float*) - AC photovoltaic output (W). + - **pv** (*float*) - Photovoltaic capacity. + +#### Example Code and Result + +```python +n_mods = 50 +n_strings = 1 +cec_modules = geodata.pvlib.retrieve_sam('CECMod') +module = cec_modules['Kaneka_U_SA105'] +inv = geodata.pvlib.retrieve_sam("CECInverter")['Fronius_USA__CL_33_3_Delta__208V_'] + +system = geodata.pvlib.pv_system( + arrays = None, + surface_tilt=35, + surface_azimuth=180, + racking_model = 'open_rack', + module_parameters=module, + modules_per_string = n_mods, + module_type = 'glass_polymer', + module = 'Kaneka_U_SA105', + strings_per_inverter = n_strings, + inverter_parameters=inv +) + +model_config = geodata.pvlib.ModelChainConfig( + clearsky_model= 'haurwitz', + transposition_model='perez', + solar_position_method= 'nrel_numpy', + airmass_model= 'kastenyoung1989', + dc_model='cec', + ac_model='sandia', + aoi_model="physical", + spectral_model='first_solar', + dc_ohmic_model='no_loss' +) + +model = geodata.pvlib.pvlib_model( + cutout, + system, + model_config +) +model + +``` diff --git a/example_notebooks/geodata_pvlib.ipynb b/example_notebooks/geodata_pvlib.ipynb new file mode 100644 index 00000000..bd0a945b --- /dev/null +++ b/example_notebooks/geodata_pvlib.ipynb @@ -0,0 +1,264 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "a6947bae-0da5-4758-8816-e30e8de35b11", + "metadata": {}, + "source": [ + "# Pvlib Generation Process\n", + "\n", + "This Jupyter notebook provides a brief overview of how to use the embedded **pvlib** functionality \n", + "within the **geodata** package to create photovoltaic energy series using **geodata** cutouts.\n", + "\n", + "*The following guide assumes you have installed and configured **geodata** and all required dependencies, \n", + "and have also installed the **pvlib** module.*\n", + "\n", + "For a guide on how to install **pvlib**, see the [pvlib documentation](https://pvlib-python.readthedocs.io/en/stable/user_guide/installation.html#editablelibrary).\n" + ] + }, + { + "cell_type": "markdown", + "id": "4bdfea85-41ce-48a3-b19b-c11556ad49fa", + "metadata": {}, + "source": [ + "## Step 1 - Download and Create an ERA5 Cutout\n", + "\n", + "Assuming you have previously created a CDS account and set up the CDS API credentials, you can download ERA5 data from the CDS API as follows.\n", + "\n", + "First, define a dataset object for the data you wish to download:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e1c36475-ea3e-47ca-8f18-6cd0b28378ba", + "metadata": {}, + "outputs": [], + "source": [ + "import geodata\n", + "\n", + "DS = geodata.Dataset(\n", + " module=\"era5\",\n", + " weather_data_config=\"wind_solar_hourly\",\n", + " years=slice(2009, 2009),\n", + " months=slice(5, 6),\n", + " bounds=[50, -3, 45, 3]\n", + ")\n", + "\n", + "if DS.prepared == False:\n", + " DS.get_data()\n", + "\n", + "cutout = geodata.Cutout(\n", + " name=\"era5-europe-test-2009-56\",\n", + " module=\"era5\",\n", + " weather_data_config=\"wind_solar_hourly\",\n", + " ys=slice(1, 2), \n", + " xs=slice(47, 48), \n", + " years=slice(2009, 2009),\n", + " months=slice(5, 6),\n", + ")\n", + "cutout.prepare(overwrite=True)" + ] + }, + { + "cell_type": "markdown", + "id": "35e9bc2a-e224-4a3b-81f9-f84a821f05a6", + "metadata": {}, + "source": [ + "* Specify \"era5\" as the model, with a `weather_data_config` of \"wind_solar_hourly\" (this is currently the only configuration supported by **pvlib** functionality in **geodata**.\n", + "* PV data will be generated at the hourly level for each set of coordinates included in the specified bounds, over the specified time period." + ] + }, + { + "cell_type": "markdown", + "id": "c1ff4b5f-a652-47a7-a404-733f3a152b62", + "metadata": {}, + "source": [ + "## Step 2 - Define PV system and model parameters\n", + "\n", + "In order to generate PV data, you must first define parameters for both the PV system and the PV model.\n", + "First, define system parameters as per the following example:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "574aecf4-3c32-4260-a190-9d0980fe4c68", + "metadata": {}, + "outputs": [], + "source": [ + "n_mods = 50\n", + "n_strings = 1\n", + "cec_modules = geodata.pvlib.retrieve_sam('CECMod')\n", + "module = cec_modules['Kaneka_U_SA105']\n", + "inv = geodata.pvlib.retrieve_sam(\"CECInverter\")['Fronius_USA__CL_33_3_Delta__208V_']\n", + "\n", + "system = geodata.pvlib.pv_system(\n", + " arrays = None,\n", + " surface_tilt=35,\n", + " surface_azimuth=180,\n", + " racking_model = 'open_rack',\n", + " module_parameters=module,\n", + " modules_per_string = n_mods,\n", + " module_type = 'glass_polymer',\n", + " module = 'Kaneka_U_SA105',\n", + " strings_per_inverter = n_strings, \n", + " inverter_parameters=inv\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "915ab2d1-f5e6-4ade-8422-44e21663e27a", + "metadata": {}, + "source": [ + "For a full list of module and inverter info available for use, see: [pvlib.pvsystem.retrieve_sam()](https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.pvsystem.retrieve_sam.html#pvlib.pvsystem.retrieve_sam)\n", + "\n", + "For comprehensive documentation on parameters used to define a PV system with **pvlib**, see: [pvlib.pvsystem.PVSystem()](https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.pvsystem.PVSystem.html)" + ] + }, + { + "cell_type": "markdown", + "id": "333249a8-8efd-4996-a7b9-55c74a798e11", + "metadata": {}, + "source": [ + "Next, define the PV model parameters as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "b1d78e83-c1fc-4b29-b155-56678f300def", + "metadata": {}, + "outputs": [], + "source": [ + "model_config = geodata.pvlib.ModelChainConfig(\n", + " clearsky_model= 'haurwitz',\n", + " transposition_model='perez', \n", + " solar_position_method= 'nrel_numpy',\n", + " airmass_model= 'kastenyoung1989',\n", + " dc_model='cec',\n", + " ac_model='sandia', \n", + " aoi_model=\"physical\",\n", + " spectral_model='first_solar',\n", + " dc_ohmic_model='no_loss'\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "86fa0a15-11a2-4347-9df0-4d3ba2bb0e33", + "metadata": {}, + "source": [ + "For a comprehensive documentation on parameters used to define a PV model with **pvlib**, see: [pvlib.modelchain.ModelChain](https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.modelchain.ModelChain.html#pvlib.modelchain.ModelChain)" + ] + }, + { + "cell_type": "markdown", + "id": "9a0c7170-1ead-4978-8fd7-0675ecd7ea91", + "metadata": {}, + "source": [ + "## Step 3 - Run PV model using cutout\n", + "\n", + "The model can then be run as follows:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f839dd15-d6f5-420f-985c-dc45829fb8d5", + "metadata": {}, + "outputs": [], + "source": [ + "model = geodata.pvlib.pvlib_model(\n", + " cutout,\n", + " system,\n", + " model_config\n", + ")\n", + "model" + ] + }, + { + "cell_type": "markdown", + "id": "47d457ed-9b55-4213-ad21-dec44b8cdee5", + "metadata": {}, + "source": [ + "If needed, the results can be converted to a Pandas dataframe, and/or graphed:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "9b739845-9e54-42b1-936e-e75e16c5f03a", + "metadata": { + "collapsed": true, + "jupyter": { + "outputs_hidden": true + } + }, + "outputs": [], + "source": [ + "model_df = model.to_dataframe()\n", + "print(model_df)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ae2cc54a-de32-4d39-b6df-99b99ce733ee", + "metadata": {}, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n", + "import pandas as pd\n", + "x_coord = 47.0\n", + "y_coord = 1.25\n", + "date = '2009-05-02'\n", + "plot_df = model_df.reset_index()\n", + "plot_df = plot_df[\n", + " (plot_df['x'] == x_coord) \n", + " & (plot_df['y'] == y_coord) \n", + " & (plot_df['time'].dt.date == pd.to_datetime(date).date())]\n", + "\n", + "plot_df = plot_df.sort_values(by='time')\n", + "plt.figure(figsize=(10, 5))\n", + "plt.plot(plot_df['time'], plot_df['ac'], marker='o', linestyle='-')\n", + "plt.xlabel('Time')\n", + "plt.ylabel('AC Power Output')\n", + "plt.title(f'AC power output for ({x_coord}, {y_coord} on {date})')\n", + "plt.xticks(rotation=45)\n", + "plt.grid()\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a3e81e9c-061c-4f2c-b423-63ed2a1d806e", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "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.6" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/pyproject.toml b/pyproject.toml index 9173310d..5f866b4f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -46,6 +46,10 @@ docs = [ "sphinx-book-theme>=1.1.3", "sphinx-autoapi==3.3.2" ] +pvlib = [ + "pvlib>=0.11.2", + "timezonefinder>=6.5.7", +] [tool.uv] dev-dependencies = [ diff --git a/src/geodata/__init__.py b/src/geodata/__init__.py index a9ee1652..393ce153 100644 --- a/src/geodata/__init__.py +++ b/src/geodata/__init__.py @@ -19,6 +19,7 @@ from .mask import Mask from .plot import * # noqa: F403 from .model import * # noqa: F403 +from .pvlib import * # noqa: F403 __author__ = "Michael Davidson (UCSD), William Honaker" __copyright__ = "GNU GPL 3 license" diff --git a/src/geodata/datasets/era5.py b/src/geodata/datasets/era5.py index 6dd23b42..8131c4e9 100644 --- a/src/geodata/datasets/era5.py +++ b/src/geodata/datasets/era5.py @@ -391,7 +391,7 @@ def prepare_meta_era5(xs, ys, year, month, template, module, **kwargs): def prepare_month_era5(fn, year, month, xs, ys): # Reference of the quantities - # https://confluence.ecmwf.int/display/CKB/ERA5+data+documentation + # https://confluence.ecmwf.int/display/CKB/ERA5%3A+data+documentation # (shortName) | (name) | (paramId) # tisr | TOA incident solar radiation | 212 # ssrd | Surface Solar Rad Downwards | 169 @@ -399,6 +399,7 @@ def prepare_month_era5(fn, year, month, xs, ys): # fdir | Total sky direct solar radiation at surface | 228021 # ro | Runoff | 205 # 2t | 2 metre temperature | 167 + # 2d | 2 metre dewpoint temperature | 168 # sp | Surface pressure | 134 # stl4 | Soil temperature level 4 | 236 # fsr | Forecast surface roughnes | 244 @@ -436,10 +437,11 @@ def prepare_month_era5(fn, year, month, xs, ys): ds = ds.rename( { "ro": "runoff", + "d2m": "dewpoint_temperature", "t2m": "temperature", "sp": "pressure", "stl4": "soil temperature", - "fsr": "roughness", + "fsr": "roughness" } ) @@ -500,14 +502,17 @@ def prepare_3d_era5(fn, year, month, xs, ys): tasks_func=tasks_monthly_era5, meta_prepare_func=prepare_meta_era5, prepare_func=prepare_month_era5, - template=os.path.join(era5_dir, "{year}/{month:0>2}/wind_solar_hourly.nc"), - fn=os.path.join(era5_dir, "{year}/{month:0>2}/wind_solar_hourly.nc"), + #template=os.path.join(era5_dir, "{year}/{month:0>2}/wind_solar_hourly.nc"), + template=os.path.join(era5_dir, "wind_solar_hourly/{year}/{month:0>2}.nc"), + #fn=os.path.join(era5_dir, "{year}/{month:0>2}/wind_solar_hourly.nc"), + fn=os.path.join(era5_dir, "wind_solar_hourly/{year}/{month:0>2}.nc"), product="reanalysis-era5-single-levels", product_type="reanalysis", keywords=[ "100m_u_component_of_wind", "100m_v_component_of_wind", "2m_temperature", + "2m_dewpoint_temperature", "runoff", "soil_temperature_level_4", "surface_net_solar_radiation", @@ -522,6 +527,7 @@ def prepare_3d_era5(fn, year, month, xs, ys): "u100", "v100", "t2m", + "d2m", "ro", "stl4", "ssr", @@ -539,8 +545,10 @@ def prepare_3d_era5(fn, year, month, xs, ys): tasks_func=tasks_monthly_era5, meta_prepare_func=prepare_meta_era5, prepare_func=prepare_3d_era5, - template=os.path.join(era5_dir, "{year}/{month:0>2}/wind_3d_hourly.nc"), - fn=os.path.join(era5_dir, "{year}/{month:0>2}/wind_3d_hourly.nc"), + #template=os.path.join(era5_dir, "{year}/{month:0>2}/wind_3d_hourly.nc"), + template=os.path.join(era5_dir, "wind_3d_hourly/{year}/{month:0>2}.nc"), + #fn=os.path.join(era5_dir, "{year}/{month:0>2}/wind_3d_hourly.nc"), + fn=os.path.join(era5_dir, "wind_3d_hourly/{year}/{month:0>2}.nc"), product="reanalysis-era5-complete", product_type="reanalysis", keywords=[131, 132], @@ -552,8 +560,10 @@ def prepare_3d_era5(fn, year, month, xs, ys): tasks_func=tasks_monthly_era5, meta_prepare_func=prepare_meta_era5, prepare_func=prepare_month_era5, - template=os.path.join(era5_dir, "{year}/{month:0>2}/wind_solar_monthly.nc"), - fn=os.path.join(era5_dir, "{year}/{month:0>2}/wind_solar_monthly.nc"), + #template=os.path.join(era5_dir, "{year}/{month:0>2}/wind_solar_monthly.nc"), + template=os.path.join(era5_dir, "wind_solar_monthly/{year}/{month:0>2}.nc"), + #fn=os.path.join(era5_dir, "{year}/{month:0>2}/wind_solar_monthly.nc"), + fn=os.path.join(era5_dir, "wind_solar_monthly/{year}/{month:0>2}.nc"), product="reanalysis-era5-single-levels-monthly-means", product_type="monthly_averaged_reanalysis", keywords=[ diff --git a/src/geodata/pvlib.py b/src/geodata/pvlib.py new file mode 100644 index 00000000..2853d2b1 --- /dev/null +++ b/src/geodata/pvlib.py @@ -0,0 +1,561 @@ +# Copyright 2016-2017 Gorm Andresen (Aarhus University), Jonas Hoersch (FIAS), Tom Brown (FIAS) +# Copyright 2020 Michael Davidson (UCSD), William Honaker, Jiahe Feng (UCSD), Yuanbo Shi +# Copyright 2023-2024 Xiqiang Liu + +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; either version 3 of the +# License, or (at your option) any later version. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +""" +GEODATA + +Geospatial Data Collection and "Pre-Analysis" Tools +""" + +import logging +import pandas as pd +import numpy as np +import xarray as xr +from .convert import (get_var) +from timezonefinder import TimezoneFinder +from pvlib import pvsystem +from pvlib.location import Location +from pvlib.modelchain import ModelChain +from pvlib.atmosphere import gueymard94_pw +from pvlib.solarposition import get_solarposition +logger = logging.getLogger(__name__) + +__all__ = ["_prepare_pvlib_ds"] + + +class ModelChainConfig: + """ + Defines pvlib ModelChain parameters as a class that + can be passed to one or more instances of pvlib_model(). + Allows user to reuse a common set of ModelChain parameters across multiple + PVSystems or even multiple cutouts. + + Parameters + ---------- + clearsky_model : string, default 'ineichen' + Specifies the clear-sky model. Passed to location.get_clearsky. + Only used when DNI is not found in the weather inputs. + transposition_model : string, default 'haydavies' + Specifies the transposition model. Passed to system.get_irradiance. + solar_position_method : string, default 'nrel_numpy' + Specifies the method for calculating solar positions. Passed to location.get_solarposition. + airmass_model : string, default 'kastenyoung1989' + Specifies the airmass model. Passed to location.get_airmass. + dc_model : string or function, optional + Specifies the DC model. Valid strings are 'sapm', 'desoto', 'cec', 'pvsyst', 'pvwatts'. + If not specified, the model will be inferred from the parameters of system.arrays[i].module_parameters. + A user-defined function may also be provided, with the ModelChain instance passed as the first argument. + ac_model : string or function, optional + Specifies the AC model. Valid strings are 'sandia', 'adr', 'pvwatts'. + If not specified, the model will be inferred from the parameters of system.inverter_parameters. + A user-defined function may also be provided, with the ModelChain instance passed as the first argument. + aoi_model : string or function, optional + Specifies the angle of incidence (AOI) model. Valid strings are 'physical', 'ashrae', 'sapm', 'martin_ruiz', + 'interp', 'no_loss'. If not specified, the model will be inferred from the parameters of + system.arrays[i].module_parameters. A user-defined function may also be provided, + with the ModelChain instance passed as the first argument. + spectral_model : string or function, optional + Specifies the spectral model. Valid strings are 'sapm', 'first_solar', 'no_loss'. + If not specified, the model will be inferred from the parameters of system.arrays[i].module_parameters. + A user-defined function may also be provided, with the ModelChain instance passed as the first argument. + temperature_model : string or function, optional + Specifies the temperature model. Valid strings are 'sapm', 'pvsyst', 'faiman', 'fuentes', 'noct_sam'. + A user-defined function may also be provided, with the ModelChain instance passed as the first argument. + dc_ohmic_model : string or function, default 'no_loss' + Specifies the DC ohmic loss model. Valid strings are 'dc_ohms_from_percent', 'no_loss'. + A user-defined function may also be provided, with the ModelChain instance passed as the first argument. + losses_model : string or function, default 'no_loss' + Specifies the losses model. Valid strings are 'pvwatts', 'no_loss'. + A user-defined function may also be provided, with the ModelChain instance passed as the first argument. + name : string, optional + Specifies the name of the ModelChain instance. + + For full documentation, see: + - pvlib.modelchain.ModelChain(): + https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.modelchain.ModelChain.html + + """ + def __init__( + self, + clearsky_model='ineichen', + transposition_model='haydavies', + solar_position_method='nrel_numpy', + airmass_model='kastenyoung1989', + dc_model=None, + ac_model=None, + aoi_model=None, + spectral_model=None, + temperature_model=None, + dc_ohmic_model='no_loss', + losses_model='no_loss', + name=None + ): + self.clearsky_model = clearsky_model + self.transposition_model = transposition_model + self.solar_position_method = solar_position_method + self.airmass_model = airmass_model + self.dc_model = dc_model + self.ac_model = ac_model + self.aoi_model = aoi_model + self.spectral_model = spectral_model + self.temperature_model = temperature_model + self.dc_ohmic_model = dc_ohmic_model + self.losses_model = losses_model + self.name = name + + def model_chain_to_kwargs(self): + return self.__dict__ + +def retrieve_sam(samfile, path=None): + """ + Wrapper for pvlib.pvsystem.retrieve_sam(). Retrieves latest module + and inverter info from a file bundled with pvlib, a path or a + URL (like SAM’s website), and returns it as a Pandas DataFrame. + + Supported databases: + - CEC module database + - Sandia Module database + - CEC Inverter database + - Anton Driesse Inverter database + + Parameters + ---------- + name : string + Use one of the following strings to retrieve a database bundled with pvlib: + - ’CECMod’ - returns the CEC module database + - ’CECInverter’ - returns the CEC Inverter database + - ’SandiaInverter’ - returns the CEC Inverter database + (CEC is only current inverter db available; tag kept for backwards compatibility) + - ’SandiaMod’ - returns the Sandia Module database + - ’ADRInverter’ - returns the ADR Inverter database + + Optional Parameters + ---------- + path : string + Path to a CSV file or a URL. + + Returns: DataFrame + + See also: + - pvlib.pvsystem.retrieve_sam(): + https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.pvsystem.retrieve_sam.html + + """ + return pvsystem.retrieve_sam(name=samfile, path=path) + +def pv_system(*args, **kwargs): + """ + Wrapper for pvlib.pvsystem.PVSystem(). + The PVSystem class defines a standard set of PV system attributes + and modeling functions. This class describes the collection and + interactions of PV system components rather than an installed system + on the ground. It is typically used in combination with Location + and ModelChain objects. + + The class supports basic system topologies consisting of: + - N total modules arranged in series (modules_per_string=N, strings_per_inverter=1). + - M total modules arranged in parallel (modules_per_string=1, strings_per_inverter=M). + - NxM total modules arranged in M strings of N modules each + (modules_per_string=N, strings_per_inverter=M). + + For full documentation, see: https://pvlib-python.readthedocs.io/en/stable/reference/generated/pvlib.pvsystem.PVSystem.html + + Parameters + ---------- + arrays : array (optional) + An Array or list of arrays that are part of the system. + See pvlib documentation for full description. + surface_tilt : float + Surface tilt angles in decimal degrees. The tilt angle is + defined as degrees from horizontal (e.g. surface facing up = 0, + surface facing horizon = 90). + surface_azimuth : float + Azimuth angle of the module surface. North=0, East=90, South=180, West=270. + albedo : float + Ground surface albedo. If not supplied, then surface_type is used to look up + a value in pvlib.albedo.SURFACE_ALBEDOS. If surface_type is also not supplied + then a ground surface albedo of 0.25 is used. + surface_type : string + The ground surface type. See pvlib.albedo.SURFACE_ALBEDOS for valid values. + module : string + The model name of the modules. May be used to look up the module_parameters dictionary via some other method. + module_type : string + Describes the module’s construction. Valid strings are ‘glass_polymer’ and ‘glass_glass’. + Used for cell and module temperature calculations. + module_parameters : dict + Module parameters as defined by the SAPM, CEC, or other. + temperature_model_parameters : dict + Temperature model parameters as required by one of the models in pvlib.temperature (excluding poa_global, temp_air and wind_speed). + modules_per_string : int, float + See system topology discussion above. + strings_per_inverter : int, float + See system topology discussion above. + inverter : string + The model name of the inverters. May be used to look up the inverter_parameters dictionary via some other method. + inverter_parameters : dict + Inverter parameters as defined by the SAPM, CEC, or other. + racking_model : string + Valid strings are ‘open_rack’, ‘close_mount’, and ‘insulated_back’. + Used to identify a parameter set for the SAPM cell temperature model. + losses_parameters : dict + Losses parameters as defined by PVWatts or other. + name : string (optional) + + """ + return pvsystem.PVSystem(*args, **kwargs) + +def pvlib_model( + cutout, + system, + model_chain_config, + vars = [ + 'influx_diffuse', + 'influx_direct', + #'dewpoint_temperature', + 'd2m', + 'temperature', + 'wnd100m' + ] + ): + + """ + Applies a `pvlib` model using :code:`pvlib.modelchain.ModelChain()` across all unique coordinates + represented in a `geodata` cutout. This function prepares input weather data, initializes the + `pvlib` model, and runs simulations for each set of coordinates, outputting an xarray dataset + containing all simulation results. + + Requires a cutout with the following variables: + + - **influx_diffuse** (*float*) - Diffuse horizontal irradiance. + - **influx_direct** (*float*) - Direct normal irradiance. + - **dewpoint_temperature** (*float*) - Dewpoint temperature in Celsius. + - **temperature** (*float*) - Air temperature in Celsius. + - **wnd100m** (*float*) - Wind speed at 100m. + + Outputs an `xarray.Dataset` containing: + + - **ac** (*float*) - AC photovoltaic output (W). + - **pv** (*float*) - Photovoltaic capacity. + + Parameters + ---------- + cutout : geodata **cutout** class + Cutout generated by the `geodata` library, based on the ERA5 dataset. + Must contain the required meteorological variables. + system : pvlib **PVSystem** class + The photovoltaic system to be simulated. Generated by :code:`geodata.pvlib.pv_system()` + model_chain_config : `ModelChainConfig` + Configuration object for :code:`pvlib.modelchain.ModelChain()` with model parameters. + vars : list of str, optional + List of variable names required for simulation. Defaults to: + ['influx_diffuse', 'influx_direct', 'dewpoint_temperature', 'temperature', 'wnd100m']. + + Returns + ------- + xr.Dataset + Dataset containing ac power output and pv capacity across all coordinates in the cutout. + + """ + ptc = system.arrays[0].module_parameters['PTC'] + n_mods = system.arrays[0].modules_per_string + + weather_data = _prepare_pvlib_ds(cutout, *vars).to_dataframe() + unique_coords = weather_data.index.droplevel('time').drop_duplicates() + coord_subsets = [] + for y, x in unique_coords: + subset = weather_data.loc[(slice(None), y, x), :].reset_index(['x', 'y']) + tz_str = TimezoneFinder().timezone_at(lat=y, lng=x) + location = Location(latitude=y, longitude=x, tz = tz_str) + + mc = ModelChain( + system, + location, + **model_chain_config.model_chain_to_kwargs() + ) + mc.run_model(subset) + + subset['ac'] = mc.results.ac + subset.loc[subset['ac'] < 0, 'ac'] = 0 + subset['pv'] = subset['ac'] / (ptc * n_mods) + + coord_subsets.append(subset) + + weather_data_final = pd.concat(coord_subsets) + + return xr.Dataset.from_dataframe(weather_data_final) + + + +## solar PV - pvlib +def _calculate_pvlib_solarposition(ds): + """ + Wrapper for :code:`pvlib.solarposition.get_solarposition()`. + Allows for vectorized calculation of solar position across an xarray dataset. + The solar zenith angle is a required input for :code:`_calculate_ghi()`. + + For full documentation on how :code:`pvlib.solarposition.get_solarposition()` calculates precipitable water, + see: `the pvlib API reference for pvlib.solarposition.get_solarposition() `. + + Parameters + ---------- + ds : xarray dataset + An xarray dataset containing series for both influx diffuse (dhi) and influx direct (dni). + zenith : numeric + Zenith angle of the sun in degrees, as calculated by :code:`_calculate_pvlib_solarposition()`. + + Returns + ------- + solarposition : dataframe + Dataframe containing solar zenith angle for a given time and set of coordinates. + """ + nt, ny, nx = ds.sizes['time'], ds.sizes['y'], ds.sizes['x'] + time_expanded = np.broadcast_to(ds.time.values[:, None, None], (nt, ny, nx)).ravel() + yy, xx = np.meshgrid(ds.y, ds.x, indexing="ij") + x_expanded = np.tile(xx.ravel(), nt) + y_expanded = np.tile(yy.ravel(), nt) + solarposition = get_solarposition(time_expanded, y_expanded, x_expanded) + solarposition.index = pd.MultiIndex.from_arrays([time_expanded, y_expanded, x_expanded], names=['time', 'y', 'x']) + return solarposition + +def _calculate_ghi(ds, zenith): + """ + Calculates global horizontal irradiance (ghi) from data arrays representing influx diffuse (dhi) and influx direct (dni) + Negative values are clipped. Calculated using the formula: + + .. math:: + + GHI = DHI + DNI * cos(Z) + + where Z representst the solar zenith as calculated by :code:`_calculate_pvlib_solarposition()`. + + Parameters + ---------- + ds : xarray dataset + An xarray dataset containing series for both influx diffuse (dhi) and influx direct (dni). + zenith : numeric + Zenith angle of the sun in degrees, as calculated by :code:`_calculate_pvlib_solarposition()`. + + Returns + ------- + ghi : numeric + Global horizontal irradiance (ghi) [W m**-2]. + + """ + dhi = ds.influx_diffuse.values.ravel() + dni = ds.influx_direct.values.ravel() + ghi = np.clip( + dhi + dni * np.cos(zenith), + 0, + #np.Inf + np.inf # `np.Inf` was removed in the NumPy 2.0 release. + ) + + reshaped_ghi = ghi.values.reshape( + ds.sizes['time'], + ds.sizes['y'], + ds.sizes['x'] + ) + + ghi = xr.DataArray( + reshaped_ghi, + dims=("time", "y", "x"), + coords={ + "time": ds['time'].values, + "y": ds['y'].values, + "x": ds['x'].values + }, + name="ghi" + ) + + ghi.name = "ghi" + ghi.attrs["units"] = "W m**-2" + ghi.attrs["description"] = "Ghi calculated from influx diffuse (dhi) and influx direct (dni)." + return ghi + +def _calculate_relative_humidity(temperature, dewpoint_temperature): + """ + Calculates relative humidity based on air temperature and dewpoint temperature. + Needed in order to calculate precipitable water using pvlib's :code:`gueymard94_pw()` function. + + Relative humidity is calculated using a version of the + August-Roche-Magnus equation as follows: + + .. math:: + + RH = 100 \cdot \frac{{\exp\left(\frac{{17.625 \cdot TD}}{{243.04 + TD}}\right)}}{{\exp\left(\frac{{17.625 \cdot T}}{{243.04 + T}}\right)}} + + where, RH is % relative humidity, TD is dew-point temperature (celsius), and T is air temperature (celsius).[#1]_ [#2]_ + + Parameters + ---------- + temperature : numeric + Ambient air temperature at the surface. [C] + dewpoint_temperature : numeric + Dewpoint temperature at the surface. [C] + + Returns + ------- + relative_humidity : numeric + Percent relative humidity. [%] + + References + ---------- + .. [#1] `United States Environmental Protection Agency. Hydrologic Micro Services. Meteorology - Humidity. `_ + + .. [#2] `University of Miami. Calculate Temperature, Dewpoint, or Relative Humidity. ` + + """ + relative_humidity = 100 * ( + np.exp((17.625 * dewpoint_temperature) / (243.04 + dewpoint_temperature)) / + np.exp((17.625 * temperature) / (243.04 + temperature)) + ) + + relative_humidity.name = "relative_humidity" + relative_humidity.attrs["units"] = "%" + relative_humidity.attrs["description"] = "Relative humidity, calculated using temperature and dewpoint temperature." + + return relative_humidity + +def _calculate_precipitable_water(temperature, relative_humidity): + """ + Calculates precipitable water (cm) from ambient air temperature (C) and relative humidity (%) using + :code:`pvlib.atmosphere.gueymard94_pw()`. + + Precipitable water (cm) is a required input for models using CEC modules from :code:`pvlib`. + For full documentation on how :code:`pvlib.atmosphere.gueymard94_pw()` calculates precipitable water, + see: `the pvlib API reference for pvlib.atmosphere.gueymard94_pw() `. + + Parameters + ---------- + temperature : numeric + Ambient air temperature at the surface. [C] + relative_humidity : numeric + Percent relative humidity. [%] + + Returns + ------- + precipitable_water : numeric + Precipitable water (cm) calculated from ambient air temperature (C) and relative humidity (%). [cm] + + """ + precipitable_water = gueymard94_pw(temperature, relative_humidity) + precipitable_water.name = "precipitable_water" + precipitable_water.attrs["units"] = "cm" + precipitable_water.attrs["description"] = "Precipitable water (cm) calculated from ambient air temperature (C) and relative humidity (%)." + + return precipitable_water + +def _convert_celsius(ds): + """ + Converts a temperature in Kelvin to a temperate in Celsius. + + Parameters + ---------- + temperature : numeric + A temperature in Celsius [C]. + + Returns + ------- + temperature : numeric + A temperature in Kelvin [K]. + """ + return ds - 273.15 + +def _prepare_pvlib_ds(cutout, *varnames): + """ + Prepares an `xarray.Dataset` from a geodata `cutout` class for use in model simulations using `pvlib`. + This function extracts specified variables from the `cutout` dataset, calculates additional parameters + like global horizontal irradiance (GHI), precipitable water, and solar position, and renames fields to + align with expected inputs. + + Requires a cutout with the following variables: + + - **influx_diffuse** (*float*) - Diffuse horizontal irradiance. + - **influx_direct** (*float*) - Direct normal irradiance. + - **dewpoint_temperature** (*float*) - Dewpoint temperature in Celsius. + - **temperature** (*float*) - Air temperature in Celsius. + - **wnd100m** (*float*) - Wind speed at 100m. + + Outputs an `xarray.Dataset` with the following variables: + + - **dhi** (*float*) - Diffuse horizontal irradiance. + - **dni** (*float*) - Direct normal irradiance. + - **ghi** (*float*) - Global horizontal irradiance (calculated via :code:`_calculate_ghi()`). + - **temp_air** (*float*) - Air temperature in Celsius. + - **wind_speed** (*float*) - Wind speed at 100m. + - **precipitable_water** (*float*) - Precipitable water (calculated via :code:`_calculate_precipitable_water()`). + + Parameters + ---------- + cutout : geodata **Cutout** class + Cutout generated by `geodata` library. Must contain following variables: influx_diffuse, influx_direct, + dewpoint_temperature, temperature, wnd100m. + varnames : string + String values representing names of required variables. + + Returns + ------- + weather_data : `xarray.Dataset` + Dataset containing necessary variables to run `pvlib` model simulations. + + """ + ds = xr.Dataset({ + name: get_var(cutout, name) + for name in varnames + }) + + temperature_celsius = _convert_celsius(ds.temperature) + + relative_humidity = _calculate_relative_humidity( + temperature_celsius, + #_convert_celsius(ds.dewpoint_temperature), + _convert_celsius(ds.d2m), + ) + + precipitable_water = _calculate_precipitable_water( + temperature_celsius, + relative_humidity + ) + + sp = _calculate_pvlib_solarposition(ds) + ghi = _calculate_ghi(ds, sp['zenith']) + + ds = ( + ds + .assign( + ghi=ghi, + temperature=temperature_celsius, + precipitable_water=precipitable_water + ) + .rename({ + 'influx_diffuse': 'dhi', + 'influx_direct': 'dni', + 'temperature': 'temp_air', + 'wnd100m': 'wind_speed' + }) + ) + + return ds[[ + "dhi", + "dni", + "ghi", + "temp_air", + "wind_speed", + "precipitable_water" + ]] + diff --git a/uv.lock b/uv.lock index 7e5888c6..03960459 100644 --- a/uv.lock +++ b/uv.lock @@ -458,7 +458,7 @@ name = "click" version = "8.1.7" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/96/d3/f04c7bfcf5c1862a2a5b845c6b2b360488cf47af55dfa79c98f6a6bf98b5/click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de", size = 336121 } wheels = [ @@ -781,6 +781,10 @@ download = [ notebook = [ { name = "notebook" }, ] +pvlib = [ + { name = "pvlib" }, + { name = "timezonefinder" }, +] [package.dev-dependencies] dev = [ @@ -805,6 +809,7 @@ requires-dist = [ { name = "numpy", specifier = "<2" }, { name = "pandas", specifier = ">=2.2.3" }, { name = "progressbar2", specifier = ">=4.5.0" }, + { name = "pvlib", marker = "extra == 'pvlib'", specifier = ">=0.11.2" }, { name = "pyproj", specifier = "==3.6.1" }, { name = "pyyaml", specifier = ">=6.0.2" }, { name = "rasterio", specifier = "==1.4.0" }, @@ -815,6 +820,7 @@ requires-dist = [ { name = "sphinx", marker = "extra == 'docs'", specifier = ">=8.0.0" }, { name = "sphinx-autoapi", marker = "extra == 'docs'", specifier = "==3.3.2" }, { name = "sphinx-book-theme", marker = "extra == 'docs'", specifier = ">=1.1.3" }, + { name = "timezonefinder", marker = "extra == 'pvlib'", specifier = ">=6.5.7" }, { name = "toolz", specifier = ">=0.12.1" }, { name = "tqdm", specifier = ">=4.66.5" }, { name = "xarray", specifier = ">=2024.9.0" }, @@ -905,6 +911,69 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 }, ] +[[package]] +name = "h3" +version = "4.1.2" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/01/15/2af9544bfdd1a0a419769d8f74c5da5ea1860629ff4d5148017a2b1bccf6/h3-4.1.2.tar.gz", hash = "sha256:505344f080b2fee96c1124fc0f9d9ba56c74cb46d17692b8ab5de6ce241b3ad0", size = 148572 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c3/10/ff44521a789c92b934b3ef549609719758a3affa0baaa1ea2656b1737cc4/h3-4.1.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:5a3249ed73b6abd58d117ddd77c164111a7de82001fe0418b8bddad0ca002dde", size = 852178 }, + { url = "https://files.pythonhosted.org/packages/c7/f4/298997c4c415cd34239e4f99ba2f74d3d51f911d3219de18be03ff99431a/h3-4.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:aae33b789cd279754434dbfe85cbc5613c7154d8833032224a2517544f99949f", size = 797468 }, + { url = "https://files.pythonhosted.org/packages/99/37/2fa94de9fae463677d9ee7be70a2031d73ce710d79a6828019a4ce698676/h3-4.1.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:03da6f6bbaeb6225e1adccd7033f0f6615348306bbcabac687c6296bcc43b8ae", size = 992277 }, + { url = "https://files.pythonhosted.org/packages/ae/fa/787c5222330fb2605744590fab609abb0dd324b5166605369926ab19fed4/h3-4.1.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afda9065e45b49b2d8c008ff668c7dd22adf72b8077e3e8e1062a16fc5321b4e", size = 993530 }, + { url = "https://files.pythonhosted.org/packages/79/d5/ae5321f2f57e801fc269e8301154d1d32ad695ecb23e79ed8cce7a7191c7/h3-4.1.2-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:d2f0cd18c99016f157529dbee7d9dc1c10d4f68a1a23c8034f0a85ee266b712a", size = 1041712 }, + { url = "https://files.pythonhosted.org/packages/f2/33/3a85f3f8a4637cf0414b3cb38c27bf3b19ee9e67967c964095831b4b74b3/h3-4.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:9499cd0328767d5782fa9743ba541b16d6c9ac23b77633525609f814f994b2c2", size = 775652 }, + { url = "https://files.pythonhosted.org/packages/bc/86/0480d067082ad9938ac9ff2e625d089180a9445a216188c9ea9b1c4bd979/h3-4.1.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:bf4d5c4d80765ad35a2b095356d925ffdabd4862f222551c662fa5d67601719e", size = 852536 }, + { url = "https://files.pythonhosted.org/packages/05/3f/4938dd97fbb90a4dc0bd5de36f9631d42153dd8cf9519fdae5090297c7bf/h3-4.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:2d94bb16ebb1430a867d947a05f9e8aeeb826c1a57103451a3d6f7a074fb8cf4", size = 797624 }, + { url = "https://files.pythonhosted.org/packages/ff/8a/517d0f7d30ab117a3ddf0cb0d43ec159afc6ae1342db5267923ef7e948fb/h3-4.1.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ea237b1d07eac2ee2623c13ee7184f25ec53656bcf8bb24b343d1ee2d0e5235b", size = 993459 }, + { url = "https://files.pythonhosted.org/packages/c2/d1/36d3b7f2b09f7f6977e78dc1ab708798fde2824a8ba2be398f0ac65a4c90/h3-4.1.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83ff8045825883e45c30763059828ae9497dc07e3650317617216342b313ab41", size = 993881 }, + { url = "https://files.pythonhosted.org/packages/cb/cf/5c605ece4981ee316f684af642e1d071c8e89509d0456b674e38658eca1d/h3-4.1.2-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:7426c5fc55f2780ac33a4898b86c2330c2676a64a96fadcd879914bf34179b64", size = 1041549 }, + { url = "https://files.pythonhosted.org/packages/5e/1d/ef952b811627e46d611993e8cd5bf906cb98b6401f1fa5fb090f36e45bad/h3-4.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:e88b7189bced71f81e1ffbab8c71ec305100379fd99efb093853351a570d20e1", size = 776971 }, + { url = "https://files.pythonhosted.org/packages/a1/91/e253f888a1ca61eac9534bbf411c6aca1f02e2f9db1d5bd719939d72d159/h3-4.1.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:e6cedb9931098a3bd81b8571105384abeb75c50b2ee8d5c1335b1ac034717c07", size = 863176 }, + { url = "https://files.pythonhosted.org/packages/f3/b9/428c70452f1b74f91fd10e5cc42dae18292b754a847eb59148086b11cb2d/h3-4.1.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a77f901957f1f8a1a6aaee0380dd1ee504eb9cf15290d9944c3f8d303cefd299", size = 801315 }, + { url = "https://files.pythonhosted.org/packages/c1/d6/cac04287d6025069338314542828b3b95a46de25cb4f1f7fe2e8fbb55375/h3-4.1.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a7ea3c418dc595f37ee37184dfdca670dffaa5bdf42d8383f0e1b6a6cb233b5b", size = 977011 }, + { url = "https://files.pythonhosted.org/packages/89/af/8917a380ef342b715197fc05ebe49b79d127a7134bbbc4ed7729650ddd14/h3-4.1.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:194774640e30f849ca69ebcfd0a8b73e580ad87592a40e52f522af670bf23f7d", size = 978175 }, + { url = "https://files.pythonhosted.org/packages/13/79/a266c25e673cf1d6fe235fb5eddde10e34b1278dc7710d10452e53faad17/h3-4.1.2-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:844eddfefcfbafac2fb17f93910bda00fd2fd85a4194145363f84233340d1273", size = 1025172 }, + { url = "https://files.pythonhosted.org/packages/34/8f/7d5905e44f8aa5ca047d54737a37267e10be2108b455689539611b59b6ff/h3-4.1.2-cp312-cp312-win_amd64.whl", hash = "sha256:e9c78eb4240ed737157b8e7ab764ac6bdebbb7643f6ed1d769cc638efcba558a", size = 781406 }, + { url = "https://files.pythonhosted.org/packages/4c/73/af17814750346b0204cd677ad87524f4fb82a7ba5317c7fb8fcd6f344d1e/h3-4.1.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:404746987c9f65e1a369729894c9aaf0f50640b134949cb7a704109f46dc49be", size = 855381 }, + { url = "https://files.pythonhosted.org/packages/4f/7d/e654a19c606581b109824ef28bbc5fad4c3db0e762b05cb37ec34e786c93/h3-4.1.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e680f384b3f2f351c7435a00632acefe006429f5d6271d118c9be4968a5d38e4", size = 794048 }, + { url = "https://files.pythonhosted.org/packages/fb/b9/dfba3b53808e996c1cbdee7a67b3d0bc082515351c86b546c5fdd667cf5e/h3-4.1.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8073e4169624dd4e628055aac0d04094f76919e8798039157163a64bf7f85b07", size = 971109 }, + { url = "https://files.pythonhosted.org/packages/62/38/f3cabbe70bc6ac85eef61fedad8cb1dfeef601f6e875d66cc11c09712df2/h3-4.1.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e64ecdcdd09514dd86a4dbaf811de8927d27a106f887412825ae8bc51dbf8ea", size = 972556 }, + { url = "https://files.pythonhosted.org/packages/92/45/d19b9474c2764baf9584b28775ead87e208ddcd6a73e970521984e206c10/h3-4.1.2-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4d48b0afb056e8c2dd08b265d570490c92122b842c3e1068644625a6c4aa8f8a", size = 1021116 }, + { url = "https://files.pythonhosted.org/packages/b9/12/c1cdd86271397867830c5d65108f8c5e23408fea6801af79269d05473078/h3-4.1.2-cp313-cp313-win_amd64.whl", hash = "sha256:71f964d417d83ee8888f2ab9544c677b7a8a2311fb17eb9e5c23711671cb86be", size = 777732 }, +] + +[[package]] +name = "h5py" +version = "3.12.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/cc/0c/5c2b0a88158682aeafb10c1c2b735df5bc31f165bfe192f2ee9f2a23b5f1/h5py-3.12.1.tar.gz", hash = "sha256:326d70b53d31baa61f00b8aa5f95c2fcb9621a3ee8365d770c551a13dbbcbfdf", size = 411457 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/df/7d/b21045fbb004ad8bb6fb3be4e6ca903841722706f7130b9bba31ef2f88e3/h5py-3.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2f0f1a382cbf494679c07b4371f90c70391dedb027d517ac94fa2c05299dacda", size = 3402133 }, + { url = "https://files.pythonhosted.org/packages/29/a7/3c2a33fba1da64a0846744726fd067a92fb8abb887875a0dd8e3bac8b45d/h5py-3.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:cb65f619dfbdd15e662423e8d257780f9a66677eae5b4b3fc9dca70b5fd2d2a3", size = 2866436 }, + { url = "https://files.pythonhosted.org/packages/1e/d0/4bf67c3937a2437c20844165766ddd1a1817ae6b9544c3743050d8e0f403/h5py-3.12.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b15d8dbd912c97541312c0e07438864d27dbca857c5ad634de68110c6beb1c2", size = 5168596 }, + { url = "https://files.pythonhosted.org/packages/85/bc/e76f4b2096e0859225f5441d1b7f5e2041fffa19fc2c16756c67078417aa/h5py-3.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:59685fe40d8c1fbbee088c88cd4da415a2f8bee5c270337dc5a1c4aa634e3307", size = 5341537 }, + { url = "https://files.pythonhosted.org/packages/99/bd/fb8ed45308bb97e04c02bd7aed324ba11e6a4bf9ed73967ca2a168e9cf92/h5py-3.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:577d618d6b6dea3da07d13cc903ef9634cde5596b13e832476dd861aaf651f3e", size = 2990575 }, + { url = "https://files.pythonhosted.org/packages/33/61/c463dc5fc02fbe019566d067a9d18746cd3c664f29c9b8b3c3f9ed025365/h5py-3.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ccd9006d92232727d23f784795191bfd02294a4f2ba68708825cb1da39511a93", size = 3410828 }, + { url = "https://files.pythonhosted.org/packages/95/9d/eb91a9076aa998bb2179d6b1788055ea09cdf9d6619cd967f1d3321ed056/h5py-3.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ad8a76557880aed5234cfe7279805f4ab5ce16b17954606cca90d578d3e713ef", size = 2872586 }, + { url = "https://files.pythonhosted.org/packages/b0/62/e2b1f9723ff713e3bd3c16dfeceec7017eadc21ef063d8b7080c0fcdc58a/h5py-3.12.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1473348139b885393125126258ae2d70753ef7e9cec8e7848434f385ae72069e", size = 5273038 }, + { url = "https://files.pythonhosted.org/packages/e1/89/118c3255d6ff2db33b062ec996a762d99ae50c21f54a8a6047ae8eda1b9f/h5py-3.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:018a4597f35092ae3fb28ee851fdc756d2b88c96336b8480e124ce1ac6fb9166", size = 5452688 }, + { url = "https://files.pythonhosted.org/packages/1d/4d/cbd3014eb78d1e449b29beba1f3293a841aa8086c6f7968c383c2c7ff076/h5py-3.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:3fdf95092d60e8130ba6ae0ef7a9bd4ade8edbe3569c13ebbaf39baefffc5ba4", size = 3006095 }, + { url = "https://files.pythonhosted.org/packages/d4/e1/ea9bfe18a3075cdc873f0588ff26ce394726047653557876d7101bf0c74e/h5py-3.12.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:06a903a4e4e9e3ebbc8b548959c3c2552ca2d70dac14fcfa650d9261c66939ed", size = 3372538 }, + { url = "https://files.pythonhosted.org/packages/0d/74/1009b663387c025e8fa5f3ee3cf3cd0d99b1ad5c72eeb70e75366b1ce878/h5py-3.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b3b8f3b48717e46c6a790e3128d39c61ab595ae0a7237f06dfad6a3b51d5351", size = 2868104 }, + { url = "https://files.pythonhosted.org/packages/af/52/c604adc06280c15a29037d4aa79a24fe54d8d0b51085e81ed24b2fa995f7/h5py-3.12.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:050a4f2c9126054515169c49cb900949814987f0c7ae74c341b0c9f9b5056834", size = 5194606 }, + { url = "https://files.pythonhosted.org/packages/fa/63/eeaacff417b393491beebabb8a3dc5342950409eb6d7b39d437289abdbae/h5py-3.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5c4b41d1019322a5afc5082864dfd6359f8935ecd37c11ac0029be78c5d112c9", size = 5413256 }, + { url = "https://files.pythonhosted.org/packages/86/f7/bb465dcb92ca3521a15cbe1031f6d18234dbf1fb52a6796a00bfaa846ebf/h5py-3.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:e4d51919110a030913201422fb07987db4338eba5ec8c5a15d6fab8e03d443fc", size = 2993055 }, + { url = "https://files.pythonhosted.org/packages/23/1c/ecdd0efab52c24f2a9bf2324289828b860e8dd1e3c5ada3cf0889e14fdc1/h5py-3.12.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:513171e90ed92236fc2ca363ce7a2fc6f2827375efcbb0cc7fbdd7fe11fecafc", size = 3346239 }, + { url = "https://files.pythonhosted.org/packages/93/cd/5b6f574bf3e318bbe305bc93ba45181676550eb44ba35e006d2e98004eaa/h5py-3.12.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:59400f88343b79655a242068a9c900001a34b63e3afb040bd7cdf717e440f653", size = 2843416 }, + { url = "https://files.pythonhosted.org/packages/8a/4f/b74332f313bfbe94ba03fff784219b9db385e6139708e55b11490149f90a/h5py-3.12.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d3e465aee0ec353949f0f46bf6c6f9790a2006af896cee7c178a8c3e5090aa32", size = 5154390 }, + { url = "https://files.pythonhosted.org/packages/1a/57/93ea9e10a6457ea8d3b867207deb29a527e966a08a84c57ffd954e32152a/h5py-3.12.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba51c0c5e029bb5420a343586ff79d56e7455d496d18a30309616fdbeed1068f", size = 5378244 }, + { url = "https://files.pythonhosted.org/packages/50/51/0bbf3663062b2eeee78aa51da71e065f8a0a6e3cb950cc7020b4444999e6/h5py-3.12.1-cp313-cp313-win_amd64.whl", hash = "sha256:52ab036c6c97055b85b2a242cb540ff9590bacfda0c03dd0cf0661b311f522f8", size = 2979760 }, +] + [[package]] name = "httpcore" version = "1.0.6" @@ -978,7 +1047,7 @@ name = "ipykernel" version = "6.29.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "appnope", marker = "platform_system == 'Darwin'" }, + { name = "appnope", marker = "sys_platform == 'darwin'" }, { name = "comm" }, { name = "debugpy" }, { name = "ipython" }, @@ -2036,6 +2105,23 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/8e/37/efad0257dc6e593a18957422533ff0f87ede7c9c6ea010a2177d738fb82f/pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0", size = 11842 }, ] +[[package]] +name = "pvlib" +version = "0.11.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "h5py" }, + { name = "numpy" }, + { name = "pandas" }, + { name = "pytz" }, + { name = "requests" }, + { name = "scipy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/bf/6f/dcf2d8c193a22782e5c64d127a12553e890c3c266cd8ccd28c71c9cc61e4/pvlib-0.11.2.tar.gz", hash = "sha256:371645f4e66c278e6cf2b1b2487c62c2bad479ff62a26ed844216d0f843cf5cc", size = 35712579 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/47/7b/c4bb55b55083fd2bafae279c9a22f2835aeaa47b09ee81ac1bc25730b817/pvlib-0.11.2-py3-none-any.whl", hash = "sha256:d5cbde1e08175d2b91d5181f235bb102a1a81c1b9eb2dcc97bdbfc5f2cd56462", size = 29222216 }, +] + [[package]] name = "pycparser" version = "2.22" @@ -2882,6 +2968,27 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6a/9e/2064975477fdc887e47ad42157e214526dcad8f317a948dee17e1659a62f/terminado-0.18.1-py3-none-any.whl", hash = "sha256:a4468e1b37bb318f8a86514f65814e1afc977cf29b3992a4500d9dd305dcceb0", size = 14154 }, ] +[[package]] +name = "timezonefinder" +version = "6.5.7" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "cffi" }, + { name = "h3" }, + { name = "numpy" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/e8/a8/6482d2c79ac18450500e077b8b457a94122ac6f57ecc6fc055b542b44731/timezonefinder-6.5.7.tar.gz", hash = "sha256:d9f158d9198ef59c651cdd4777d6408d095bf3a06e4511e89ba6db2c78ba6d58", size = 50701777 } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e4/c2/7e55531b48f4773d300f8aca88e8b5d7e94c1ef66604c8c181e5d215c352/timezonefinder-6.5.7-cp310-cp310-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:071021fc1759ca8e76247cd8b3691b5581bc43e9dafd2d520106aff1d8a586b4", size = 50705211 }, + { url = "https://files.pythonhosted.org/packages/75/cc/f57bcc82193033b362811d3ebf86d4d91f7a1371c6d3bd45df256a995165/timezonefinder-6.5.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2ba1ccd36e04b16a13e59de6f87d478e5e1f063c3226a221309079a772b30281", size = 50707139 }, + { url = "https://files.pythonhosted.org/packages/d9/80/3912d53e9c19bde7638a0c62c3b73add78411adbd39333fb1bf39d3ec894/timezonefinder-6.5.7-cp311-cp311-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcbb197fb607a7ee12a1a65a39bc1230135c17340d018ce1f84a79cefd9fdf9d", size = 50705213 }, + { url = "https://files.pythonhosted.org/packages/3b/3a/8c8228169f2f0710a256718a3071afe52068f31c81aeb7e75ddc72096ac9/timezonefinder-6.5.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:324ac25e89eb504554b71a16be56384bc1c33ba45cb8151cdb508650e8202082", size = 50707175 }, + { url = "https://files.pythonhosted.org/packages/3c/ee/bf90cb60e71a99fed0111a94accf3600a4940f0e8ec2fb99332f0ee7a4d5/timezonefinder-6.5.7-cp312-cp312-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a83f1f67032d26e73adbe08ccc8b2465f44be9c99023a1df477ebbd1e570e6b", size = 50705466 }, + { url = "https://files.pythonhosted.org/packages/fb/6b/4ab7e0639409c529143702824f327361a8900588c2ffbb5f024f9cec8b3d/timezonefinder-6.5.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:ea61bd9c956b986b5dd3489797a21ba9c1769cec03a4f4549e88cdde5a0e4770", size = 50707499 }, + { url = "https://files.pythonhosted.org/packages/b1/1f/61c9a9bb3ddeaa6d309077ef6972f7a89c64834e7af60529db184781aff7/timezonefinder-6.5.7-cp313-cp313-manylinux_2_17_x86_64.manylinux_2_5_x86_64.manylinux1_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fc5ce4386179ddbe792b6b5f4004ac5b5e7960f8e954d9a9af2e0c9faf4372", size = 50705387 }, + { url = "https://files.pythonhosted.org/packages/52/9b/2ed688d87d92ef85d0621f8ab31f1f95705b6bd693e17d21169b767a43f9/timezonefinder-6.5.7-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:f99f4f84852dd00a339ee9dfcfca680dd592178eacb2ad8609f5b4da73bb68b1", size = 50707405 }, +] + [[package]] name = "tinycss2" version = "1.3.0" @@ -2935,7 +3042,7 @@ name = "tqdm" version = "4.66.5" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "colorama", marker = "platform_system == 'Windows'" }, + { name = "colorama", marker = "sys_platform == 'win32'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/58/83/6ba9844a41128c62e810fddddd72473201f3eacde02046066142a2d96cc5/tqdm-4.66.5.tar.gz", hash = "sha256:e1020aef2e5096702d8a025ac7d16b1577279c9d63f8375b63083e9a5f0fcbad", size = 169504 } wheels = [