From b4087417384d278bf3242feb15b05372781e0015 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20=C5=A0ediv=C3=BD?= <6774676+eumiro@users.noreply.github.com> Date: Tue, 3 Oct 2023 13:47:37 +0000 Subject: [PATCH 01/13] Remove various repeated words in documentation (#1872) * Remove repeated words * Update pvlib/ivtools/sdm.py Co-authored-by: Kevin Anderson --------- Co-authored-by: Kevin Anderson --- .../irradiance-decomposition/plot_diffuse_fraction.py | 2 +- pvlib/ivtools/sdm.py | 2 +- pvlib/location.py | 2 +- pvlib/modelchain.py | 2 +- pvlib/pvsystem.py | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/examples/irradiance-decomposition/plot_diffuse_fraction.py b/docs/examples/irradiance-decomposition/plot_diffuse_fraction.py index 30d1385f87..1c33824356 100644 --- a/docs/examples/irradiance-decomposition/plot_diffuse_fraction.py +++ b/docs/examples/irradiance-decomposition/plot_diffuse_fraction.py @@ -203,7 +203,7 @@ # `NSRDB`_ user manuals. # # The Erbs and Boland models are correlations only based on the clearness index -# kt, which is the ratio of GHI to the the horizontal component of the +# kt, which is the ratio of GHI to the horizontal component of the # extra-terrestrial irradiance. At low sun elevation (zenith near 90 degrees), # especially near sunset, kt can explode because the denominator # (extra-terrestrial irradiance) approaches zero. In pvlib this behavior is diff --git a/pvlib/ivtools/sdm.py b/pvlib/ivtools/sdm.py index a695e740e7..1f36471a30 100644 --- a/pvlib/ivtools/sdm.py +++ b/pvlib/ivtools/sdm.py @@ -48,7 +48,7 @@ def fit_cec_sam(celltype, v_mp, i_mp, v_oc, i_sc, alpha_sc, beta_voc, beta_voc : float Temperature coefficient of open circuit voltage [V/C] gamma_pmp : float - Temperature coefficient of power at maximum point point [%/C] + Temperature coefficient of power at maximum power point [%/C] cells_in_series : int Number of cells in series temp_ref : float, default 25 diff --git a/pvlib/location.py b/pvlib/location.py index 50f98d375d..46d8477237 100644 --- a/pvlib/location.py +++ b/pvlib/location.py @@ -282,7 +282,7 @@ def get_airmass(self, times=None, solar_position=None, times : None or DatetimeIndex, default None Only used if solar_position is not provided. solar_position : None or DataFrame, default None - DataFrame with with columns 'apparent_zenith', 'zenith'. + DataFrame with columns 'apparent_zenith', 'zenith'. model : str, default 'kastenyoung1989' Relative airmass model. See :py:func:`pvlib.atmosphere.get_relative_airmass` diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index c1ea50e1b2..c88f8a7641 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1518,7 +1518,7 @@ def _build_irrad(data): return self def _assign_times(self): - """Assign self.results.times according the the index of + """Assign self.results.times according the index of self.results.weather. If there are multiple DataFrames in self.results.weather then diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 3d49317cee..f0c1cd2cda 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -893,7 +893,7 @@ def num_arrays(self): class Array: """ - An Array is a set of of modules at the same orientation. + An Array is a set of modules at the same orientation. Specifically, an array is defined by its mount, the module parameters, the number of parallel strings of modules @@ -996,7 +996,7 @@ def __repr__(self): ) def _infer_temperature_model_params(self): - # try to infer temperature model parameters from from racking_model + # try to infer temperature model parameters from racking_model # and module_type param_set = f'{self.mount.racking_model}_{self.module_type}' if param_set in temperature.TEMPERATURE_MODEL_PARAMETERS['sapm']: From 6072e0982c3c0236f532ddfa48fbf461180d834e Mon Sep 17 00:00:00 2001 From: Arjan Keeman Date: Tue, 3 Oct 2023 16:31:47 +0200 Subject: [PATCH 02/13] fix invalid escape sequence '\c' (#1879) * fix invalid escape sequence '\c' pvlib/iam.py:843: DeprecationWarning: invalid escape sequence '\c' Occurence is actually in line 854: `IAM = 1 - (1 - \cos(aoi))^5` * Add to list of contributors --- docs/sphinx/source/whatsnew/v0.10.3.rst | 2 +- pvlib/iam.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index efa2fab075..0d46d035ec 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -23,4 +23,4 @@ Documentation Contributors ~~~~~~~~~~~~ - +* Arjan Keeman (:ghuser:`akeeman`) diff --git a/pvlib/iam.py b/pvlib/iam.py index d72866e1e9..b0b2202ad9 100644 --- a/pvlib/iam.py +++ b/pvlib/iam.py @@ -840,7 +840,7 @@ def schlick(aoi): def schlick_diffuse(surface_tilt): - """ + r""" Determine the incidence angle modifiers (IAM) for diffuse sky and ground-reflected irradiance on a tilted surface using the Schlick incident angle model. From e36e50aa175b90ee8823cf227e01770761397fe9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20S=C3=A1nchez=20de=20Le=C3=B3n=20Peque?= Date: Wed, 4 Oct 2023 14:21:55 +0200 Subject: [PATCH 03/13] Replace use of deprecated `pkg_resources` (#1881) (#1882) --- benchmarks/benchmarks/location.py | 4 ++-- benchmarks/benchmarks/solarposition.py | 4 ++-- benchmarks/benchmarks/solarposition_numba.py | 4 ++-- benchmarks/benchmarks/temperature.py | 6 +++--- docs/sphinx/source/whatsnew/v0.10.3.rst | 2 ++ pvlib/tests/conftest.py | 9 ++++----- setup.py | 2 +- 7 files changed, 16 insertions(+), 15 deletions(-) diff --git a/benchmarks/benchmarks/location.py b/benchmarks/benchmarks/location.py index 2356ca1e7e..57ce125846 100644 --- a/benchmarks/benchmarks/location.py +++ b/benchmarks/benchmarks/location.py @@ -4,7 +4,7 @@ import pandas as pd import pvlib -from pkg_resources import parse_version +from packaging.version import Version def set_solar_position(obj): @@ -37,7 +37,7 @@ def time_location_get_clearsky(self): class Location_0_6_1: def setup(self): - if parse_version(pvlib.__version__) < parse_version('0.6.1'): + if Version(pvlib.__version__) < Version('0.6.1'): raise NotImplementedError set_solar_position(self) diff --git a/benchmarks/benchmarks/solarposition.py b/benchmarks/benchmarks/solarposition.py index 4fac0ea470..0ccb379d8c 100644 --- a/benchmarks/benchmarks/solarposition.py +++ b/benchmarks/benchmarks/solarposition.py @@ -7,10 +7,10 @@ import pvlib from pvlib import solarposition -from pkg_resources import parse_version +from packaging.version import Version -if parse_version(pvlib.__version__) >= parse_version('0.6.1'): +if Version(pvlib.__version__) >= Version('0.6.1'): sun_rise_set_transit_spa = solarposition.sun_rise_set_transit_spa else: sun_rise_set_transit_spa = solarposition.get_sun_rise_set_transit diff --git a/benchmarks/benchmarks/solarposition_numba.py b/benchmarks/benchmarks/solarposition_numba.py index bcadfd1893..5d4c277eb1 100644 --- a/benchmarks/benchmarks/solarposition_numba.py +++ b/benchmarks/benchmarks/solarposition_numba.py @@ -7,7 +7,7 @@ Try to keep relevant sections in sync with benchmarks/solarposition.py """ -from pkg_resources import parse_version +from packaging.version import Version import pandas as pd import os @@ -18,7 +18,7 @@ from pvlib import solarposition # NOQA: E402 -if parse_version(pvlib.__version__) >= parse_version('0.6.1'): +if Version(pvlib.__version__) >= Version('0.6.1'): sun_rise_set_transit_spa = solarposition.sun_rise_set_transit_spa else: sun_rise_set_transit_spa = solarposition.get_sun_rise_set_transit diff --git a/benchmarks/benchmarks/temperature.py b/benchmarks/benchmarks/temperature.py index d8a8dad9a0..a2ffe331f5 100644 --- a/benchmarks/benchmarks/temperature.py +++ b/benchmarks/benchmarks/temperature.py @@ -4,7 +4,7 @@ import pandas as pd import pvlib -from pkg_resources import parse_version +from packaging.version import Version from functools import partial @@ -20,7 +20,7 @@ class SAPM: def setup(self): set_weather_data(self) - if parse_version(pvlib.__version__) >= parse_version('0.7.0'): + if Version(pvlib.__version__) >= Version('0.7.0'): kwargs = pvlib.temperature.TEMPERATURE_MODEL_PARAMETERS['sapm'] kwargs = kwargs['open_rack_glass_glass'] self.sapm_cell_wrapper = partial(pvlib.temperature.sapm_cell, @@ -41,7 +41,7 @@ def time_sapm_cell(self): class Fuentes: def setup(self): - if parse_version(pvlib.__version__) < parse_version('0.8.0'): + if Version(pvlib.__version__) < Version('0.8.0'): raise NotImplementedError set_weather_data(self) diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index 0d46d035ec..1fcd8271b6 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -15,6 +15,7 @@ Bug fixes Testing ~~~~~~~ +* Replace use of deprecated ``pkg_resources``. (:issue:`1881`, :pull:`1882`) Documentation @@ -24,3 +25,4 @@ Documentation Contributors ~~~~~~~~~~~~ * Arjan Keeman (:ghuser:`akeeman`) +* Miguel Sánchez de León Peque (:ghuser:`Peque`) diff --git a/pvlib/tests/conftest.py b/pvlib/tests/conftest.py index 3e1a22d031..15b0cd70e8 100644 --- a/pvlib/tests/conftest.py +++ b/pvlib/tests/conftest.py @@ -4,15 +4,14 @@ import pandas as pd import os -from pkg_resources import parse_version +from packaging.version import Version import pytest from functools import wraps import pvlib from pvlib.location import Location -pvlib_base_version = \ - parse_version(parse_version(pvlib.__version__).base_version) +pvlib_base_version = Version(Version(pvlib.__version__).base_version) # decorator takes one argument: the base version for which it should fail @@ -27,7 +26,7 @@ def wrapper(func): @wraps(func) def inner(*args, **kwargs): # fail if the version is too high - if pvlib_base_version >= parse_version(version): + if pvlib_base_version >= Version(version): pytest.fail('the tested function is scheduled to be ' 'removed in %s' % version) # otherwise return the function to be executed @@ -40,7 +39,7 @@ def inner(*args, **kwargs): def _check_pandas_assert_kwargs(kwargs): # handles the change in API related to default # tolerances in pandas 1.1.0. See pvlib GH #1018 - if parse_version(pd.__version__) >= parse_version('1.1.0'): + if Version(pd.__version__) >= Version('1.1.0'): if kwargs.pop('check_less_precise', False): kwargs['atol'] = 1e-3 kwargs['rtol'] = 1e-3 diff --git a/setup.py b/setup.py index dcc49edf2f..5d9b1a69fd 100755 --- a/setup.py +++ b/setup.py @@ -45,7 +45,7 @@ TESTS_REQUIRE = ['pytest', 'pytest-cov', 'pytest-mock', 'requests-mock', 'pytest-timeout', 'pytest-rerunfailures', - 'pytest-remotedata'] + 'pytest-remotedata', 'packaging'] EXTRAS_REQUIRE = { 'optional': ['cython', 'ephem', 'nrel-pysam', 'numba', 'solarfactors', 'statsmodels'], From 46851d91bb70e9b5521feb7ec119b057ec3d7f70 Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Wed, 4 Oct 2023 11:04:49 -0500 Subject: [PATCH 04/13] Update infinite_sheds.py to add shaded fraction to returned variables in infinite_sheds.get_irradiance and infinite_sheds.get_irradiance_poa (#1871) * Update infinite_sheds.py Added shaded fraction to returned variables. * Update v0.10.3.rst * Update test_infinite_sheds.py added tests for shaded fraction * Update test_infinite_sheds.py Corrected the shaded fraction tests in the haydavies portion. * Update pvlib/bifacial/infinite_sheds.py Co-authored-by: Kevin Anderson * Update infinite_sheds.py * Update infinite_sheds.py * Update infinite_sheds.py fixed indentation issues --------- Co-authored-by: Kevin Anderson --- docs/sphinx/source/whatsnew/v0.10.3.rst | 5 ++- pvlib/bifacial/infinite_sheds.py | 12 +++++++- pvlib/tests/bifacial/test_infinite_sheds.py | 34 ++++++++++++++++++++- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index 1fcd8271b6..30b9f576b0 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -7,7 +7,9 @@ v0.10.3 (Anticipated December, 2023) Enhancements ~~~~~~~~~~~~ - +* :py:func:`pvlib.bifacial.infinite_sheds.get_irradiance` and + :py:func:`pvlib.bifacial.infinite_sheds.get_irradiance_poa` now include + shaded fraction in returned variables. (:pull:`1871`) Bug fixes ~~~~~~~~~ @@ -26,3 +28,4 @@ Contributors ~~~~~~~~~~~~ * Arjan Keeman (:ghuser:`akeeman`) * Miguel Sánchez de León Peque (:ghuser:`Peque`) +* Will Hobbs (:ghuser:`williamhobbs`) \ No newline at end of file diff --git a/pvlib/bifacial/infinite_sheds.py b/pvlib/bifacial/infinite_sheds.py index 7d84d4c3f4..9f8a3787ae 100644 --- a/pvlib/bifacial/infinite_sheds.py +++ b/pvlib/bifacial/infinite_sheds.py @@ -276,6 +276,8 @@ def get_irradiance_poa(surface_tilt, surface_azimuth, solar_zenith, [W/m^2] - ``poa_ground_diffuse`` : total ground-reflected diffuse irradiance on the plane of array. [W/m^2] + - ``shaded_fraction`` : fraction of row slant height from the bottom that + is shaded from direct irradiance by adjacent rows. [unitless] References ---------- @@ -369,7 +371,7 @@ def get_irradiance_poa(surface_tilt, surface_azimuth, solar_zenith, output = { 'poa_global': poa_global, 'poa_direct': poa_direct, 'poa_diffuse': poa_diffuse, 'poa_ground_diffuse': poa_gnd_pv, - 'poa_sky_diffuse': poa_sky_pv} + 'poa_sky_diffuse': poa_sky_pv, 'shaded_fraction': f_x} if isinstance(poa_global, pd.Series): output = pd.DataFrame(output) return output @@ -502,6 +504,9 @@ def get_irradiance(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, cells from the front surface. [W/m^2] - ``poa_front_ground_diffuse`` : ground-reflected diffuse irradiance reaching the module cells from the front surface. [W/m^2] + - ``shaded_fraction_front`` : fraction of row slant height from the bottom + that is shaded from direct irradiance on the front surface by adjacent + rows. [unitless] - ``poa_back_direct`` : direct irradiance reaching the module cells from the back surface. [W/m^2] - ``poa_back_diffuse`` : total diffuse irradiance reaching the module @@ -510,6 +515,9 @@ def get_irradiance(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, cells from the back surface. [W/m^2] - ``poa_back_ground_diffuse`` : ground-reflected diffuse irradiance reaching the module cells from the back surface. [W/m^2] + - ``shaded_fraction_back`` : fraction of row slant height from the bottom + that is shaded from direct irradiance on the back surface by adjacent + rows. [unitless] References ---------- @@ -545,6 +553,7 @@ def get_irradiance(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, 'poa_diffuse': 'poa_front_diffuse', 'poa_sky_diffuse': 'poa_front_sky_diffuse', 'poa_ground_diffuse': 'poa_front_ground_diffuse', + 'shaded_fraction': 'shaded_fraction_front', } colmap_back = { 'poa_global': 'poa_back', @@ -552,6 +561,7 @@ def get_irradiance(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, 'poa_diffuse': 'poa_back_diffuse', 'poa_sky_diffuse': 'poa_back_sky_diffuse', 'poa_ground_diffuse': 'poa_back_ground_diffuse', + 'shaded_fraction': 'shaded_fraction_back', } if isinstance(ghi, pd.Series): diff --git a/pvlib/tests/bifacial/test_infinite_sheds.py b/pvlib/tests/bifacial/test_infinite_sheds.py index 017b15f943..1f6dadfd5f 100644 --- a/pvlib/tests/bifacial/test_infinite_sheds.py +++ b/pvlib/tests/bifacial/test_infinite_sheds.py @@ -100,9 +100,11 @@ def test_get_irradiance_poa(): expected_diffuse = np.array([300.]) expected_direct = np.array([700.]) expected_global = expected_diffuse + expected_direct + expected_shaded_fraction = np.array([0.]) assert np.isclose(res['poa_global'], expected_global) assert np.isclose(res['poa_diffuse'], expected_diffuse) assert np.isclose(res['poa_direct'], expected_direct) + assert np.isclose(res['shaded_fraction'], expected_shaded_fraction) # vector inputs surface_tilt = np.array([0., 0., 0., 0.]) height = 1. @@ -115,6 +117,8 @@ def test_get_irradiance_poa(): expected_direct = np.array( [700., 350. * np.sqrt(2), 350. * np.sqrt(2), 0.]) expected_global = expected_diffuse + expected_direct + expected_shaded_fraction = np.array( + [0., 0., 0., 0.]) res = infinite_sheds.get_irradiance_poa( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, @@ -122,6 +126,7 @@ def test_get_irradiance_poa(): assert np.allclose(res['poa_global'], expected_global) assert np.allclose(res['poa_diffuse'], expected_diffuse) assert np.allclose(res['poa_direct'], expected_direct) + assert np.allclose(res['shaded_fraction'], expected_shaded_fraction) # series inputs surface_tilt = pd.Series(surface_tilt) surface_azimuth = pd.Series(data=surface_azimuth, index=surface_tilt.index) @@ -133,15 +138,19 @@ def test_get_irradiance_poa(): data=expected_direct, index=surface_tilt.index) expected_global = expected_diffuse + expected_direct expected_global.name = 'poa_global' # to match output Series + expected_shaded_fraction = pd.Series( + data=expected_shaded_fraction, index=surface_tilt.index) + expected_shaded_fraction.name = 'shaded_fraction' # to match output Series res = infinite_sheds.get_irradiance_poa( surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, gcr, height, pitch, ghi, dhi, dni, albedo, iam=iam, npoints=npoints) assert isinstance(res, pd.DataFrame) assert_series_equal(res['poa_global'], expected_global) + assert_series_equal(res['shaded_fraction'], expected_shaded_fraction) assert all(k in res.columns for k in [ 'poa_global', 'poa_diffuse', 'poa_direct', 'poa_ground_diffuse', - 'poa_sky_diffuse']) + 'poa_sky_diffuse', 'shaded_fraction']) def test__backside_tilt(): @@ -177,10 +186,16 @@ def test_get_irradiance(vectorize): expected_front_diffuse = np.array([300.]) expected_front_direct = np.array([700.]) expected_front_global = expected_front_diffuse + expected_front_direct + expected_shaded_fraction_front = np.array([0.]) + expected_shaded_fraction_back = np.array([0.]) assert np.isclose(result['poa_front'], expected_front_global) assert np.isclose(result['poa_front_diffuse'], expected_front_diffuse) assert np.isclose(result['poa_front_direct'], expected_front_direct) assert np.isclose(result['poa_global'], result['poa_front']) + assert np.isclose(result['shaded_fraction_front'], + expected_shaded_fraction_front) + assert np.isclose(result['shaded_fraction_back'], + expected_shaded_fraction_back) # series inputs ghi = pd.Series([1000., 500., 500., np.nan]) dhi = pd.Series([300., 500., 500., 500.], index=ghi.index) @@ -200,7 +215,12 @@ def test_get_irradiance(vectorize): expected_poa_global = pd.Series( [1000., 500., result_front['poa_global'][2] * (1 + 0.8 * 0.98), np.nan], index=ghi.index, name='poa_global') + expected_shaded_fraction = pd.Series( + result_front['shaded_fraction'], index=ghi.index, + name='shaded_fraction_front') assert_series_equal(result['poa_global'], expected_poa_global) + assert_series_equal(result['shaded_fraction_front'], + expected_shaded_fraction) def test_get_irradiance_limiting_gcr(): @@ -230,6 +250,8 @@ def test_get_irradiance_limiting_gcr(): expected_direct = np.array([0.]) expected_diffuse = expected_ground_diffuse + expected_sky_diffuse expected_poa = expected_diffuse + expected_direct + expected_shaded_fraction_front = np.array([0.]) + expected_shaded_fraction_back = np.array([0.]) assert np.isclose(result['poa_front'], expected_poa, rtol=0.01) assert np.isclose(result['poa_front_diffuse'], expected_diffuse, rtol=0.01) assert np.isclose(result['poa_front_direct'], expected_direct) @@ -244,6 +266,10 @@ def test_get_irradiance_limiting_gcr(): result['poa_back_sky_diffuse']) assert np.isclose(result['poa_front_ground_diffuse'], result['poa_back_ground_diffuse']) + assert np.isclose(result['shaded_fraction_front'], + expected_shaded_fraction_front) + assert np.isclose(result['shaded_fraction_back'], + expected_shaded_fraction_back) def test_get_irradiance_with_haydavies(): @@ -272,10 +298,16 @@ def test_get_irradiance_with_haydavies(): expected_front_diffuse = np.array([151.38]) expected_front_direct = np.array([848.62]) expected_front_global = expected_front_diffuse + expected_front_direct + expected_shaded_fraction_front = np.array([0.]) + expected_shaded_fraction_back = np.array([0.]) assert np.isclose(result['poa_front'], expected_front_global) assert np.isclose(result['poa_front_diffuse'], expected_front_diffuse) assert np.isclose(result['poa_front_direct'], expected_front_direct) assert np.isclose(result['poa_global'], result['poa_front']) + assert np.isclose(result['shaded_fraction_front'], + expected_shaded_fraction_front) + assert np.isclose(result['shaded_fraction_back'], + expected_shaded_fraction_back) # test for when dni_extra is not supplied with pytest.raises(ValueError, match='supply dni_extra for haydavies'): result = infinite_sheds.get_irradiance( From af8dde56f2b65a12ec42a78fb9bafd41a48738d3 Mon Sep 17 00:00:00 2001 From: Anton Driesse Date: Mon, 16 Oct 2023 15:02:30 +0200 Subject: [PATCH 05/13] Continuous version of the Perez transposition model implementation (#1876) * Definitely not ready for review! * Big step forward. * Add entry in docs. * A working model but just one test sofar. * Add new model as option in get_sky_diffuse. Docstring edits pending. * Completed doc strings. Also a bit of fine-tuning code. * Updated whatsnew. * Bugfix, formatting fix, and add all tests. * Test warning plus some other small changes. * Make flake happy. * Update pvlib/irradiance.py Co-authored-by: Cliff Hansen * Address comments. * Add contributor code comments. * Update pvlib/irradiance.py Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> * Adapt to reviewer preferences. * Adapt to flake preferences. * Remove model pseudo-option. * Flake --------- Co-authored-by: Cliff Hansen Co-authored-by: Adam R. Jensen <39184289+AdamRJensen@users.noreply.github.com> --- .../reference/irradiance/transposition.rst | 1 + docs/sphinx/source/whatsnew/v0.10.3.rst | 5 +- pvlib/irradiance.py | 260 +++++++++++++++++- pvlib/tests/test_irradiance.py | 85 +++++- 4 files changed, 333 insertions(+), 18 deletions(-) diff --git a/docs/sphinx/source/reference/irradiance/transposition.rst b/docs/sphinx/source/reference/irradiance/transposition.rst index 4749b5d5b9..7b3624e692 100644 --- a/docs/sphinx/source/reference/irradiance/transposition.rst +++ b/docs/sphinx/source/reference/irradiance/transposition.rst @@ -10,6 +10,7 @@ Transposition models irradiance.get_sky_diffuse irradiance.isotropic irradiance.perez + irradiance.perez_driesse irradiance.haydavies irradiance.klucher irradiance.reindl diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index 30b9f576b0..0853254983 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -7,6 +7,8 @@ v0.10.3 (Anticipated December, 2023) Enhancements ~~~~~~~~~~~~ +* Added the continuous Perez-Driesse transposition model. + :py:func:`pvlib.irradiance.perez_driesse` (:issue:`1841`, :pull:`1876`) * :py:func:`pvlib.bifacial.infinite_sheds.get_irradiance` and :py:func:`pvlib.bifacial.infinite_sheds.get_irradiance_poa` now include shaded fraction in returned variables. (:pull:`1871`) @@ -28,4 +30,5 @@ Contributors ~~~~~~~~~~~~ * Arjan Keeman (:ghuser:`akeeman`) * Miguel Sánchez de León Peque (:ghuser:`Peque`) -* Will Hobbs (:ghuser:`williamhobbs`) \ No newline at end of file +* Will Hobbs (:ghuser:`williamhobbs`) +* Anton Driesse (:ghuser:`adriesse`) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 7e66281974..efae7f6236 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -10,6 +10,7 @@ import numpy as np import pandas as pd +from scipy.interpolate import splev from pvlib import atmosphere, solarposition, tools import pvlib # used to avoid dni name collision in complete_irradiance @@ -323,6 +324,7 @@ def get_total_irradiance(surface_tilt, surface_azimuth, * reindl * king * perez + * perez-driesse Parameters ---------- @@ -351,7 +353,8 @@ def get_total_irradiance(surface_tilt, surface_azimuth, the list of accepted values. model : str, default 'isotropic' Irradiance model. Can be one of ``'isotropic'``, ``'klucher'``, - ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``. + ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``, + ``'perez-driesse'``. model_perez : str, default 'allsitescomposite1990' Used only if ``model='perez'``. See :py:func:`~pvlib.irradiance.perez`. @@ -363,13 +366,13 @@ def get_total_irradiance(surface_tilt, surface_azimuth, Notes ----- - Models ``'haydavies'``, ``'reindl'``, or ``'perez'`` require - ``'dni_extra'``. Values can be calculated using + Models ``'haydavies'``, ``'reindl'``, ``'perez'`` and ``'perez-driesse'`` + require ``'dni_extra'``. Values can be calculated using :py:func:`~pvlib.irradiance.get_extra_radiation`. - The ``'perez'`` model requires relative airmass (``airmass``) as input. If - ``airmass`` is not provided, it is calculated using the defaults in - :py:func:`~pvlib.atmosphere.get_relative_airmass`. + The ``'perez'`` and ``'perez-driesse'`` models require relative airmass + (``airmass``) as input. If ``airmass`` is not provided, it is calculated + using the defaults in :py:func:`~pvlib.atmosphere.get_relative_airmass`. """ poa_sky_diffuse = get_sky_diffuse( @@ -400,6 +403,7 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, * reindl * king * perez + * perez-driesse Parameters ---------- @@ -423,7 +427,8 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, Relative airmass (not adjusted for pressure). [unitless] model : str, default 'isotropic' Irradiance model. Can be one of ``'isotropic'``, ``'klucher'``, - ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``. + ``'haydavies'``, ``'reindl'``, ``'king'``, ``'perez'``, + ``'perez-driesse'``. model_perez : str, default 'allsitescomposite1990' Used only if ``model='perez'``. See :py:func:`~pvlib.irradiance.perez`. @@ -440,18 +445,19 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, Notes ----- - Models ``'haydavies'``, ``'reindl'``, and ``'perez``` require 'dni_extra'. - Values can be calculated using + Models ``'haydavies'``, ``'reindl'``, ``'perez'`` and ``'perez-driesse'`` + require ``'dni_extra'``. Values can be calculated using :py:func:`~pvlib.irradiance.get_extra_radiation`. - The ``'perez'`` model requires relative airmass (``airmass``) as input. If - ``airmass`` is not provided, it is calculated using the defaults in - :py:func:`~pvlib.atmosphere.get_relative_airmass`. + The ``'perez'`` and ``'perez-driesse'`` models require relative airmass + (``airmass``) as input. If ``airmass`` is not provided, it is calculated + using the defaults in :py:func:`~pvlib.atmosphere.get_relative_airmass`. """ model = model.lower() - if (model in {'haydavies', 'reindl', 'perez'}) and (dni_extra is None): + if dni_extra is None and model in {'haydavies', 'reindl', + 'perez', 'perez-driesse'}: raise ValueError(f'dni_extra is required for model {model}') if model == 'isotropic': @@ -473,6 +479,10 @@ def get_sky_diffuse(surface_tilt, surface_azimuth, sky = perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra, solar_zenith, solar_azimuth, airmass, model=model_perez) + elif model == 'perez-driesse': + # perez_driesse will calculate its own airmass if needed + sky = perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, + solar_zenith, solar_azimuth, airmass) else: raise ValueError(f'invalid model selection {model}') @@ -1212,6 +1222,228 @@ def perez(surface_tilt, surface_azimuth, dhi, dni, dni_extra, return sky_diffuse +def _calc_delta(dhi, dni_extra, solar_zenith, airmass=None): + ''' + Compute the delta parameter, which represents sky dome "brightness" + in the Perez and Perez-Driesse models. + + Helper function for perez_driesse transposition. + ''' + if airmass is None: + # use the same airmass model as in the original perez work + airmass = atmosphere.get_relative_airmass(solar_zenith, + 'kastenyoung1989') + + max_airmass = atmosphere.get_relative_airmass(90, 'kastenyoung1989') + airmass = np.where(solar_zenith >= 90, max_airmass, airmass) + + return dhi / (dni_extra / airmass) + + +def _calc_zeta(dhi, dni, zenith): + ''' + Compute the zeta parameter, which represents sky dome "clearness" + in the Perez-Driesse model. + + Helper function for perez_driesse transposition. + ''' + dhi = np.asarray(dhi) + dni = np.asarray(dni) + + # first calculate what zeta would be without the kappa correction + # using eq. 5 and eq. 13 + with np.errstate(invalid='ignore'): + zeta = dni / (dhi + dni) + + zeta = np.where(dhi == 0, 0.0, zeta) + + # then apply the kappa correction in a manner analogous to eq. 7 + kappa = 1.041 + kterm = kappa * np.radians(zenith) ** 3 + zeta = zeta / (1 - kterm * (zeta - 1)) + + return zeta + + +def _f(i, j, zeta): + ''' + Evaluate the quadratic splines corresponding to the + allsitescomposite1990 Perez model look-up table. + + Helper function for perez_driesse transposition. + ''' + knots = np.array( + [0.000, 0.000, 0.000, + 0.061, 0.187, 0.333, 0.487, 0.643, 0.778, 0.839, + 1.000, 1.000, 1.000]) + + coefs = np.array( + [[-0.053, +0.529, -0.028, -0.071, +0.061, -0.019], + [-0.008, +0.588, -0.062, -0.060, +0.072, -0.022], + [+0.131, +0.770, -0.167, -0.026, +0.106, -0.032], + [+0.328, +0.471, -0.216, +0.069, -0.105, -0.028], + [+0.557, +0.241, -0.300, +0.086, -0.085, -0.012], + [+0.861, -0.323, -0.355, +0.240, -0.467, -0.008], + [ 1.212, -1.239, -0.444, +0.305, -0.797, +0.047], + [ 1.099, -1.847, -0.365, +0.275, -1.132, +0.124], + [+0.544, +0.157, -0.213, +0.118, -1.455, +0.292], + [+0.544, +0.157, -0.213, +0.118, -1.455, +0.292], + [+0.000, +0.000, +0.000, +0.000, +0.000, +0.000], + [+0.000, +0.000, +0.000, +0.000, +0.000, +0.000], + [+0.000, +0.000, +0.000, +0.000, +0.000, +0.000]]) + + coefs = coefs.T.reshape((2, 3, 13)) + + tck = (knots, coefs[i-1, j-1], 2) + + return splev(zeta, tck) + + +def perez_driesse(surface_tilt, surface_azimuth, dhi, dni, dni_extra, + solar_zenith, solar_azimuth, airmass=None, + return_components=False): + ''' + Determine diffuse irradiance from the sky on a tilted surface using + the continuous Perez-Driesse model. + + The Perez-Driesse model [1]_ is a reformulation of the 1990 Perez + model [2]_ that provides continuity of the function and of its first + derivatives. This is achieved by replacing the look-up table of + coefficients with quadratic splines. + + Parameters + ---------- + surface_tilt : numeric + Surface tilt angles in decimal degrees. surface_tilt must be >=0 + and <=180. The tilt angle is defined as degrees from horizontal + (e.g. surface facing up = 0, surface facing horizon = 90) + + surface_azimuth : numeric + Surface azimuth angles in decimal degrees. surface_azimuth must + be >=0 and <=360. The azimuth convention is defined as degrees + east of north (e.g. North = 0, South=180 East = 90, West = 270). + + dhi : numeric + Diffuse horizontal irradiance in W/m^2. dhi must be >=0. + + dni : numeric + Direct normal irradiance in W/m^2. dni must be >=0. + + dni_extra : numeric + Extraterrestrial normal irradiance in W/m^2. + + solar_zenith : numeric + apparent (refraction-corrected) zenith angles in decimal + degrees. solar_zenith must be >=0 and <=180. + + solar_azimuth : numeric + Sun azimuth angles in decimal degrees. solar_azimuth must be >=0 + and <=360. The azimuth convention is defined as degrees east of + north (e.g. North = 0, East = 90, West = 270). + + airmass : numeric (optional, default None) + Relative (not pressure-corrected) airmass values. If airmass is a + DataFrame it must be of the same size as all other DataFrame + inputs. The kastenyoung1989 airmass calculation is used internally + and is also recommended when pre-calculating airmass because + it was used in the original model development. + + return_components: bool (optional, default=False) + Flag used to decide whether to return the calculated diffuse components + or not. + + Returns + -------- + numeric, OrderedDict, or DataFrame + Return type controlled by `return_components` argument. + If ``return_components=False``, `sky_diffuse` is returned. + If ``return_components=True``, `diffuse_components` is returned. + + sky_diffuse : numeric + The sky diffuse component of the solar radiation on a tilted + surface. + + diffuse_components : OrderedDict (array input) or DataFrame (Series input) + Keys/columns are: + * sky_diffuse: Total sky diffuse + * isotropic + * circumsolar + * horizon + + Notes + ----- + The Perez-Driesse model can be considered a plug-in replacement for the + 1990 Perez model using the ``'allsitescomposite1990'`` coefficient set. + Deviations between the two are very small, as demonstrated in [1]_. + Other coefficient sets are not supported because the 1990 set is + based on the largest and most diverse set of empirical data. + + References + ---------- + .. [1] A. Driesse, A. Jensen, R. Perez, A Continuous Form of the Perez + Diffuse Sky Model for Forward and Reverse Transposition, accepted + for publication in the Solar Energy Journal. + + .. [2] Perez, R., Ineichen, P., Seals, R., Michalsky, J., Stewart, R., + 1990. Modeling daylight availability and irradiance components from + direct and global irradiance. Solar Energy 44 (5), 271-289. + + See also + -------- + perez + isotropic + haydavies + klucher + reindl + king + ''' + # Contributed by Anton Driesse (@adriesse), PV Performance Labs. Oct., 2023 + + delta = _calc_delta(dhi, dni_extra, solar_zenith, airmass) + zeta = _calc_zeta(dhi, dni, solar_zenith) + + z = np.radians(solar_zenith) + + F1 = _f(1, 1, zeta) + _f(1, 2, zeta) * delta + _f(1, 3, zeta) * z + F2 = _f(2, 1, zeta) + _f(2, 2, zeta) * delta + _f(2, 3, zeta) * z + + # note the newly recommended upper limit on F1 + F1 = np.clip(F1, 0, 0.9) + + # lines after this point are identical to the original perez function + # with some checks removed + + A = aoi_projection(surface_tilt, surface_azimuth, + solar_zenith, solar_azimuth) + A = np.maximum(A, 0) + + B = tools.cosd(solar_zenith) + B = np.maximum(B, tools.cosd(85)) + + # Calculate Diffuse POA from sky dome + term1 = 0.5 * (1 - F1) * (1 + tools.cosd(surface_tilt)) + term2 = F1 * A / B + term3 = F2 * tools.sind(surface_tilt) + + sky_diffuse = np.maximum(dhi * (term1 + term2 + term3), 0) + + if return_components: + diffuse_components = OrderedDict() + diffuse_components['sky_diffuse'] = sky_diffuse + + # Calculate the different components + diffuse_components['isotropic'] = dhi * term1 + diffuse_components['circumsolar'] = dhi * term2 + diffuse_components['horizon'] = dhi * term3 + + if isinstance(sky_diffuse, pd.Series): + diffuse_components = pd.DataFrame(diffuse_components) + + return diffuse_components + else: + return sky_diffuse + + def clearsky_index(ghi, clearsky_ghi, max_clearsky_index=2.0): """ Calculate the clearsky index. @@ -2350,6 +2582,8 @@ def erbs_driesse(ghi, zenith, datetime_or_doy=None, dni_extra=None, orgill_hollands boland """ + # Contributed by Anton Driesse (@adriesse), PV Performance Labs. Aug., 2023 + # central polynomial coefficients with float64 precision p = [+12.26911439571261000, -16.47050842469730700, diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 2f660d5c97..8f24e7a605 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -273,6 +273,31 @@ def test_perez(irrad_data, ephem_data, dni_et, relative_airmass): assert_series_equal(out, expected, check_less_precise=2) +def test_perez_driesse(irrad_data, ephem_data, dni_et, relative_airmass): + dni = irrad_data['dni'].copy() + dni.iloc[2] = np.nan + out = irradiance.perez_driesse(40, 180, irrad_data['dhi'], dni, + dni_et, ephem_data['apparent_zenith'], + ephem_data['azimuth'], relative_airmass) + expected = pd.Series(np.array( + [0., 29.991, np.nan, 47.397]), + index=irrad_data.index) + assert_series_equal(out, expected, check_less_precise=2) + + +def test_perez_driesse_airmass(irrad_data, ephem_data, dni_et): + dni = irrad_data['dni'].copy() + dni.iloc[2] = np.nan + out = irradiance.perez_driesse(40, 180, irrad_data['dhi'], dni, + dni_et, ephem_data['apparent_zenith'], + ephem_data['azimuth'], airmass=None) + print(out) + expected = pd.Series(np.array( + [0., 29.991, np.nan, 47.397]), + index=irrad_data.index) + assert_series_equal(out, expected, check_less_precise=2) + + def test_perez_components(irrad_data, ephem_data, dni_et, relative_airmass): dni = irrad_data['dni'].copy() dni.iloc[2] = np.nan @@ -297,6 +322,32 @@ def test_perez_components(irrad_data, ephem_data, dni_et, relative_airmass): assert_series_equal(sum_components, expected_for_sum, check_less_precise=2) +def test_perez_driesse_components(irrad_data, ephem_data, dni_et, + relative_airmass): + dni = irrad_data['dni'].copy() + dni.iloc[2] = np.nan + out = irradiance.perez_driesse(40, 180, irrad_data['dhi'], dni, + dni_et, ephem_data['apparent_zenith'], + ephem_data['azimuth'], relative_airmass, + return_components=True) + + expected = pd.DataFrame(np.array( + [[0., 29.991, np.nan, 47.397], + [0., 25.806, np.nan, 33.181], + [0., 0.000, np.nan, 4.197], + [0., 4.184, np.nan, 10.018]]).T, + columns=['sky_diffuse', 'isotropic', 'circumsolar', 'horizon'], + index=irrad_data.index + ) + expected_for_sum = expected['sky_diffuse'].copy() + expected_for_sum.iloc[2] = 0 + sum_components = out.iloc[:, 1:].sum(axis=1) + sum_components.name = 'sky_diffuse' + + assert_frame_equal(out, expected, check_less_precise=2) + assert_series_equal(sum_components, expected_for_sum, check_less_precise=2) + + def test_perez_negative_horizon(): times = pd.date_range(start='20190101 11:30:00', freq='1H', periods=5, tz='US/Central') @@ -356,6 +407,21 @@ def test_perez_arrays(irrad_data, ephem_data, dni_et, relative_airmass): assert isinstance(out, np.ndarray) +def test_perez_driesse_arrays(irrad_data, ephem_data, dni_et, + relative_airmass): + dni = irrad_data['dni'].copy() + dni.iloc[2] = np.nan + out = irradiance.perez_driesse(40, 180, irrad_data['dhi'].values, + dni.values, dni_et, + ephem_data['apparent_zenith'].values, + ephem_data['azimuth'].values, + relative_airmass.values) + expected = np.array( + [0., 29.990, np.nan, 47.396]) + assert_allclose(out, expected, atol=1e-2) + assert isinstance(out, np.ndarray) + + def test_perez_scalar(): # copied values from fixtures out = irradiance.perez(40, 180, 118.45831879, 939.95469881, @@ -366,8 +432,17 @@ def test_perez_scalar(): assert_allclose(out, 109.084332) +def test_perez_driesse_scalar(): + # copied values from fixtures + out = irradiance.perez_driesse(40, 180, 118.458, 939.954, + 1321.165, 10.564, 144.765, 1.016) + # this will fail. out is ndarry with ndim == 0. fix in future version. + # assert np.isscalar(out) + assert_allclose(out, 110.341, atol=1e-2) + + @pytest.mark.parametrize('model', ['isotropic', 'klucher', 'haydavies', - 'reindl', 'king', 'perez']) + 'reindl', 'king', 'perez', 'perez-driesse']) def test_sky_diffuse_zenith_close_to_90(model): # GH 432 sky_diffuse = irradiance.get_sky_diffuse( @@ -419,7 +494,7 @@ def test_campbell_norman(): def test_get_total_irradiance(irrad_data, ephem_data, dni_et, relative_airmass): models = ['isotropic', 'klucher', - 'haydavies', 'reindl', 'king', 'perez'] + 'haydavies', 'reindl', 'king', 'perez', 'perez-driesse'] for model in models: total = irradiance.get_total_irradiance( @@ -437,7 +512,8 @@ def test_get_total_irradiance(irrad_data, ephem_data, dni_et, @pytest.mark.parametrize('model', ['isotropic', 'klucher', - 'haydavies', 'reindl', 'king', 'perez']) + 'haydavies', 'reindl', 'king', + 'perez', 'perez-driesse']) def test_get_total_irradiance_albedo( irrad_data, ephem_data, dni_et, relative_airmass, model): albedo = pd.Series(0.2, index=ephem_data.index) @@ -456,7 +532,8 @@ def test_get_total_irradiance_albedo( @pytest.mark.parametrize('model', ['isotropic', 'klucher', - 'haydavies', 'reindl', 'king', 'perez']) + 'haydavies', 'reindl', 'king', + 'perez', 'perez-driesse']) def test_get_total_irradiance_scalars(model): total = irradiance.get_total_irradiance( 32, 180, From 49be5b592bc83b321aec099551169478ad02c25a Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Tue, 17 Oct 2023 16:20:23 -0400 Subject: [PATCH 06/13] Fix spurious test error with pandas 2.1 (#1891) https://github.com/pandas-dev/pandas/issues/55014 --- pvlib/tests/test_solarposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_solarposition.py b/pvlib/tests/test_solarposition.py index 36af6afff0..17870de27e 100644 --- a/pvlib/tests/test_solarposition.py +++ b/pvlib/tests/test_solarposition.py @@ -310,7 +310,7 @@ def test_sun_rise_set_transit_ephem_horizon(golden): sunrise_delta = datetime.datetime(2016, 1, 3, 7, 17, 11) - \ datetime.datetime(2016, 1, 3, 7, 21, 33) expected = pd.Series(index=times, - data=sunrise_delta, + data=[sunrise_delta], name='sunrise').dt.round('min') assert_series_equal(expected, result_rounded) From 63a2ca47badf692cd650883f21e07c072145876e Mon Sep 17 00:00:00 2001 From: Will Hobbs <45701090+williamhobbs@users.noreply.github.com> Date: Mon, 23 Oct 2023 10:15:23 -0500 Subject: [PATCH 07/13] Fix plotting in plot_singlediode.py gallery page (#1895) * Update plot_singlediode.py fixed plot annotations by moving plt.show() further down * Update whatsnew.rst * Update v0.10.3.rst * Update docs/sphinx/source/whatsnew.rst Undoing changes to whatsnew.rst --- docs/examples/iv-modeling/plot_singlediode.py | 2 +- docs/sphinx/source/whatsnew/v0.10.3.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/iv-modeling/plot_singlediode.py b/docs/examples/iv-modeling/plot_singlediode.py index c8cbe506ca..1cdda9f348 100644 --- a/docs/examples/iv-modeling/plot_singlediode.py +++ b/docs/examples/iv-modeling/plot_singlediode.py @@ -118,7 +118,6 @@ plt.xlabel('Module voltage [V]') plt.ylabel('Module current [A]') plt.title(parameters['Name']) -plt.show() plt.gcf().set_tight_layout(True) @@ -136,6 +135,7 @@ def draw_arrow(ax, label, x0, y0, rotation, size, direction): ax = plt.gca() draw_arrow(ax, 'Irradiance', 20, 2.5, 90, 15, 'r') draw_arrow(ax, 'Temperature', 35, 1, 0, 15, 'l') +plt.show() print(pd.DataFrame({ 'i_sc': curve_info['i_sc'], diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index 0853254983..8bdc2cf98f 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -24,7 +24,7 @@ Testing Documentation ~~~~~~~~~~~~~ - +* Fixed a plotting issue in the IV curve gallery example (:pull:`1895`) Contributors ~~~~~~~~~~~~ From 4e55d500821847b1876c443cbaef4ecc4fe0b05e Mon Sep 17 00:00:00 2001 From: matsuobasho Date: Thu, 9 Nov 2023 08:52:00 -0500 Subject: [PATCH 08/13] Address pandas FutureWarnings in test suite (#1900) * Cahnged expected reference in test_detect_clearskY_window to 1 from True to avoid Futurewarning * Change reference to etr in ibird function to avoid FutureWarning * In test_modelchain, update all instances when referring to series by position to using iloc to get rid of FutureWarning * Update to iloc method for referencing by position in test_irradiance to get rid of FutureWarning * In test_singlediode change applymap to map to get rid of FutureWarning * Test_srml update to select using iloc to get rid of FutureWarning * Substitute changing to float64 dtype using map with base functionality that's accessible across Pandas versions * Added username to Contributors * Update line break in test_clearsky to adhere to line length limit --- docs/sphinx/source/whatsnew/v0.10.3.rst | 1 + pvlib/tests/iotools/test_srml.py | 4 ++-- pvlib/tests/test_clearsky.py | 5 +++-- pvlib/tests/test_irradiance.py | 8 ++++---- pvlib/tests/test_modelchain.py | 22 +++++++++++----------- pvlib/tests/test_singlediode.py | 7 +++---- 6 files changed, 24 insertions(+), 23 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.10.3.rst b/docs/sphinx/source/whatsnew/v0.10.3.rst index 8bdc2cf98f..b7eece4108 100644 --- a/docs/sphinx/source/whatsnew/v0.10.3.rst +++ b/docs/sphinx/source/whatsnew/v0.10.3.rst @@ -32,3 +32,4 @@ Contributors * Miguel Sánchez de León Peque (:ghuser:`Peque`) * Will Hobbs (:ghuser:`williamhobbs`) * Anton Driesse (:ghuser:`adriesse`) +* :ghuser:`matsuobasho` diff --git a/pvlib/tests/iotools/test_srml.py b/pvlib/tests/iotools/test_srml.py index 330b1313f8..7e586dd9b3 100644 --- a/pvlib/tests/iotools/test_srml.py +++ b/pvlib/tests/iotools/test_srml.py @@ -42,8 +42,8 @@ def test_read_srml_map_variables_false(): def test_read_srml_nans_exist(): data = srml.read_srml(srml_testfile) - assert isnan(data['dni_0'][1119]) - assert data['dni_0_flag'][1119] == 99 + assert isnan(data['dni_0'].iloc[1119]) + assert data['dni_0_flag'].iloc[1119] == 99 @pytest.mark.parametrize('url,year,month', [ diff --git a/pvlib/tests/test_clearsky.py b/pvlib/tests/test_clearsky.py index c2ef607f0f..fa4dee2cd0 100644 --- a/pvlib/tests/test_clearsky.py +++ b/pvlib/tests/test_clearsky.py @@ -631,7 +631,7 @@ def test_detect_clearsky_window(detect_clearsky_data): clear_samples = clearsky.detect_clearsky( expected['GHI'], cs['ghi'], window_length=3) expected = expected['Clear or not'].copy() - expected.iloc[-3:] = True + expected.iloc[-3:] = 1 assert_series_equal(expected, clear_samples, check_dtype=False, check_names=False) @@ -855,7 +855,8 @@ def test_bird(): # test scalars just at noon # XXX: calculations start at 12am so noon is at index = 12 irrads3 = clearsky.bird( - zenith[12], airmass[12], aod_380nm, aod_500nm, h2o_cm, dni_extra=etr[12] + zenith[12], airmass[12], aod_380nm, aod_500nm, h2o_cm, + dni_extra=etr.iloc[12] ) Eb3, Ebh3, Gh3, Dh3 = (irrads3[_] for _ in field_names) # XXX: testdata starts at 1am so noon is at index = 11 diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 8f24e7a605..ba66f4dc36 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -237,13 +237,13 @@ def test_haydavies_components(irrad_data, ephem_data, dni_et): 40, 180, irrad_data['dhi'].values[-1], irrad_data['dni'].values[-1], dni_et[-1], ephem_data['apparent_zenith'].values[-1], ephem_data['azimuth'].values[-1], return_components=True) - assert_allclose(result['sky_diffuse'], expected['sky_diffuse'][-1], + assert_allclose(result['sky_diffuse'], expected['sky_diffuse'].iloc[-1], atol=1e-4) - assert_allclose(result['isotropic'], expected['isotropic'][-1], + assert_allclose(result['isotropic'], expected['isotropic'].iloc[-1], atol=1e-4) - assert_allclose(result['circumsolar'], expected['circumsolar'][-1], + assert_allclose(result['circumsolar'], expected['circumsolar'].iloc[-1], atol=1e-4) - assert_allclose(result['horizon'], expected['horizon'][-1], atol=1e-4) + assert_allclose(result['horizon'], expected['horizon'].iloc[-1], atol=1e-4) assert isinstance(result, dict) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index d9dfc7bd64..c59a02e9c4 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -1373,7 +1373,7 @@ def test_ac_models(sapm_dc_snl_ac_system, cec_dc_adr_ac_system, assert m.call_count == 1 assert isinstance(mc.results.ac, pd.Series) assert not mc.results.ac.empty - assert mc.results.ac[1] < 1 + assert mc.results.ac.iloc[1] < 1 def test_ac_model_user_func(pvwatts_dc_pvwatts_ac_system, location, weather, @@ -1425,8 +1425,8 @@ def test_aoi_models(sapm_dc_snl_ac_system, location, aoi_model, assert m.call_count == 1 assert isinstance(mc.results.ac, pd.Series) assert not mc.results.ac.empty - assert mc.results.ac[0] > 150 and mc.results.ac[0] < 200 - assert mc.results.ac[1] < 1 + assert mc.results.ac.iloc[0] > 150 and mc.results.ac.iloc[0] < 200 + assert mc.results.ac.iloc[1] < 1 @pytest.mark.parametrize('aoi_model', [ @@ -1441,8 +1441,8 @@ def test_aoi_models_singleon_weather_single_array( assert len(mc.results.aoi_modifier) == 1 assert isinstance(mc.results.ac, pd.Series) assert not mc.results.ac.empty - assert mc.results.ac[0] > 150 and mc.results.ac[0] < 200 - assert mc.results.ac[1] < 1 + assert mc.results.ac.iloc[0] > 150 and mc.results.ac.iloc[0] < 200 + assert mc.results.ac.iloc[1] < 1 def test_aoi_model_no_loss(sapm_dc_snl_ac_system, location, weather): @@ -1451,8 +1451,8 @@ def test_aoi_model_no_loss(sapm_dc_snl_ac_system, location, weather): mc.run_model(weather) assert mc.results.aoi_modifier == 1.0 assert not mc.results.ac.empty - assert mc.results.ac[0] > 150 and mc.results.ac[0] < 200 - assert mc.results.ac[1] < 1 + assert mc.results.ac.iloc[0] > 150 and mc.results.ac.iloc[0] < 200 + assert mc.results.ac.iloc[1] < 1 def test_aoi_model_interp(sapm_dc_snl_ac_system, location, weather, mocker): @@ -1472,8 +1472,8 @@ def test_aoi_model_interp(sapm_dc_snl_ac_system, location, weather, mocker): assert m.call_args[1]['theta_ref'] == theta_ref assert isinstance(mc.results.ac, pd.Series) assert not mc.results.ac.empty - assert mc.results.ac[0] > 150 and mc.results.ac[0] < 200 - assert mc.results.ac[1] < 1 + assert mc.results.ac.iloc[0] > 150 and mc.results.ac.iloc[0] < 200 + assert mc.results.ac.iloc[1] < 1 def test_aoi_model_user_func(sapm_dc_snl_ac_system, location, weather, mocker): @@ -1484,8 +1484,8 @@ def test_aoi_model_user_func(sapm_dc_snl_ac_system, location, weather, mocker): assert m.call_count == 1 assert mc.results.aoi_modifier == 0.9 assert not mc.results.ac.empty - assert mc.results.ac[0] > 140 and mc.results.ac[0] < 200 - assert mc.results.ac[1] < 1 + assert mc.results.ac.iloc[0] > 140 and mc.results.ac.iloc[0] < 200 + assert mc.results.ac.iloc[1] < 1 @pytest.mark.parametrize('aoi_model', [ diff --git a/pvlib/tests/test_singlediode.py b/pvlib/tests/test_singlediode.py index 8e0d05668e..899f158415 100644 --- a/pvlib/tests/test_singlediode.py +++ b/pvlib/tests/test_singlediode.py @@ -110,12 +110,11 @@ def build_precise_iv_curve_dataframe(file_csv, file_json): # parse strings to np.float64 is_array = ['Currents', 'Voltages', 'diode_voltage'] - joined[is_array] = joined[is_array].applymap( - lambda a: np.asarray(a, dtype=np.float64) - ) + for col in is_array: + joined[col] = [np.asarray(a, dtype=np.float64) for a in joined[col]] is_number = ['v_oc', 'i_sc', 'v_mp', 'i_mp', 'p_mp', 'i_x', 'i_xx', 'Temperature'] - joined[is_number] = joined[is_number].applymap(np.float64) + joined[is_number] = joined[is_number].astype(np.float64) joined['Boltzmann'] = scipy.constants.Boltzmann joined['Elementary Charge'] = scipy.constants.elementary_charge From e1d158bf9d2f71abc65673ff0f34f0dccf4b536f Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 15 Nov 2023 13:25:33 -0500 Subject: [PATCH 09/13] add comparisons to other tools --- paper/paper.bib | 73 ++++++++++++++++++++++++++++++++++++++++++++++++- paper/paper.md | 26 ++++++++++++++---- 2 files changed, 92 insertions(+), 7 deletions(-) diff --git a/paper/paper.bib b/paper/paper.bib index 531cdc361b..76b9026b54 100644 --- a/paper/paper.bib +++ b/paper/paper.bib @@ -66,12 +66,83 @@ @article{pvlibjoss2018 @inproceedings{Holmgren2018, Author = {William F. Holmgren and Clifford W. Hansen and Joshua S. Stein and Mark A. Mikofski}, - Booktitle = {IEEE 45th Photovoltaic Specialists Conference}, + Booktitle = {45th Photovoltaic Specialists Conference}, Title = {Review of open source tools for PV modeling}, Year = {2018}, doi = {10.5281/zenodo.1401378} } +@inproceedings{Andrews2014, + doi = {10.1109/pvsc.2014.6925501}, + url = {https://doi.org/10.1109/pvsc.2014.6925501}, + year = {2014}, + month = jun, + publisher = {{IEEE}}, + author = {Robert W. Andrews and Joshua S. Stein and Clifford Hansen and Daniel Riley}, + title = {Introduction to the open source {PV} {LIB} for python Photovoltaic system modelling package}, + booktitle = {40th Photovoltaic Specialist Conference} +} + +@inproceedings{Mermoud1994, + title = {{PVSYST}: a user-friendly software for {PV}-systems simulation}, + booktitle = {Twelfth European Photovoltaic Solar Energy Conference}, + year = {1994}, + author = {André Mermoud} +} + +@inproceedings{Mikofski2018, + title = {Accurate Performance Predictions of Large PV Systems with Shading using Submodule Mismatch Calculation}, + url = {http://dx.doi.org/10.1109/PVSC.2018.8547323}, + DOI = {10.1109/pvsc.2018.8547323}, + booktitle = {7th World Conference on Photovoltaic Energy Conversion (WCPEC)}, + publisher = {IEEE}, + author = {Mikofski, Mark A. and Lynn, Matthew and Byrne, James and Hamer, Mike and Neubert, Anja and Newmiller, Jeff}, + year = {2018}, + month = jun +} + +@inproceedings{Passow2017, + title = {PlantPredict: Solar Performance Modeling Made Simple}, + url = {http://dx.doi.org/10.1109/PVSC.2017.8366450}, + DOI = {10.1109/pvsc.2017.8366450}, + booktitle = {44th Photovoltaic Specialist Conference (PVSC)}, + publisher = {IEEE}, + author = {Passow, Kendra and Ngan, Lauren and Rich, Geoffrey and Lee, Mitch and Kaplan, Stephen}, + year = {2017}, + month = jun +} + +@techreport{Gilman2018, + title = {SAM Photovoltaic Model Technical Reference 2016 Update}, + url = {http://dx.doi.org/10.2172/1429291}, + DOI = {10.2172/1429291}, + institution = {Office of Scientific and Technical Information (OSTI)}, + author = {Gilman, Paul and DiOrio, Nicholas A. and Freeman, Janine M. and Janzou, Steven and Dobos, Aron and Ryberg, David}, + year = {2018}, + month = mar +} + +@misc{pysam, + doi = {10.11578/DC.20190903.1}, + url = {https://www.osti.gov/doecode/biblio/29023}, + author = {Gilman, Paul and Janzou, Steven and Guittet, Darice and Freeman, Janine and DiOrio, Nicholas and Blair, Nathan and Boyd, Matthew and Neises, Ty and Wagner, Michael}, + language = {en}, + title = {PySAM (Python Wrapper for System Advisor Model "SAM") [SWR-19-57]}, + publisher = {National Renewable Energy Laboratory (NREL), Golden, CO (United States)}, + year = {2019} +} + +@inproceedings{Pai2016, + title = {Introducing CASSYS: An open-source software for simulation of grid-connected photovoltaic systems}, + url = {http://dx.doi.org/10.1109/PVSC.2016.7749839}, + DOI = {10.1109/pvsc.2016.7749839}, + booktitle = {43rd Photovoltaic Specialists Conference}, + publisher = {IEEE}, + author = {Pai, Abhijeet and Thevenard, Didier}, + year = {2016}, + month = jun +} + @techreport{Augspurger2023, author = {Tobias Augspurger and Eirini Malliaraki and Josh Hopkins and Dan Brown}, title = {The Open Source Sustainability Ecosystem}, diff --git a/paper/paper.md b/paper/paper.md index f0521edb52..89dd70af38 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -73,26 +73,40 @@ of the global solar energy industry demands correspondingly more capable models. Per the United States Department of Energy, "the importance of accurate modeling is hard to overstate" [@seto2022]. -Compared with other PV modeling tools, pvlib python stands out in several +Compared with other modern PV system modeling tools, pvlib python stands out in several key aspects. One is its toolbox design, providing the user a level of flexibility and customization beyond that of other tools. Rather than organizing -the user interface around pre-built modeling workflows, pvlib python -makes the individual "building blocks" of PV performance models accessible to +the user interface around pre-built modeling workflows as in other PV system +modeling tools (e.g. SAM [@Gilman2018], PVsyst [@Mermoud1994], SolarFarmer [@Mikofski2018], +PlantPredict [@Passow2017], and CASSYS [@Pai2016], to name a few software tools with +comparable breadth of PV system modeling capability), pvlib python +makes the individual "building blocks" of PV system performance models accessible to the user. This allows the user to assemble their own model workflows, including the ability of incorporating custom modeling steps. This flexibility -is essential for applications in both academia and industry. +is essential for applications in both academia and industry. To our knowledge, +the only other PV system modeling software with such a toolbox design is the +MATLAB version of pvlib [@Andrews2014], the code base from which pvlib python +was translated and since surpassed in capability and community uptake. Another key aspect of pvlib python is that it is used via a general-purpose programming language (Python), which allows pvlib python functions to be combined with capabilities in other Python packages, such as database query, data manipulation, numerical optimization, -plotting, and reporting packages. +plotting, and reporting packages. In contrast, most other PV system modeling +tools are used via some form of GUI. Some of these other tools are also accessible +from Python via web APIs or wrapper libraries (e.g. [@pysam]), but these +"black box" interfaces offer only limited ability to combine the PV models +with functionality from other Python packages. A final key aspect of pvlib python is its open peer review approach and foundation on published scientific research, allowing it to be developed by a decentralized and diverse community of PV researchers and practitioners without compromising its focus on transparent and reliable model -implementations. +implementations. This is in contrast to the inherent opaqueness of closed-source +commercial software, which prevents users from inspecting the source code +to ensure a model implementation's validity or traceability to a reference. +It is also in contrast to other open-source projects, which tend to be +be "walled garden" projects with few contributions from outside the host organization. These key aspects, along with sustained contributions from a passionate and committed community, have led to pvlib python's widespread adoption across the PV From 44cd666c7b95d4f0d22c08780ee40b941f490a8b Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 17 Nov 2023 09:32:48 -0500 Subject: [PATCH 10/13] Apply suggestions from code review Co-authored-by: Cliff Hansen --- paper/paper.bib | 2 +- paper/paper.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/paper/paper.bib b/paper/paper.bib index 76b9026b54..ca09bd1352 100644 --- a/paper/paper.bib +++ b/paper/paper.bib @@ -66,7 +66,7 @@ @article{pvlibjoss2018 @inproceedings{Holmgren2018, Author = {William F. Holmgren and Clifford W. Hansen and Joshua S. Stein and Mark A. Mikofski}, - Booktitle = {45th Photovoltaic Specialists Conference}, + Booktitle = {45th IEEE Photovoltaic Specialists Conference}, Title = {Review of open source tools for PV modeling}, Year = {2018}, doi = {10.5281/zenodo.1401378} diff --git a/paper/paper.md b/paper/paper.md index 89dd70af38..0e06d2c197 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -86,7 +86,7 @@ the ability of incorporating custom modeling steps. This flexibility is essential for applications in both academia and industry. To our knowledge, the only other PV system modeling software with such a toolbox design is the MATLAB version of pvlib [@Andrews2014], the code base from which pvlib python -was translated and since surpassed in capability and community uptake. +was translated and has since surpassed in capability and community uptake. Another key aspect of pvlib python is that it is used via a general-purpose programming language (Python), which From 9d29042f6735393c786dda01b980370a4257bbb0 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 17 Nov 2023 09:58:53 -0500 Subject: [PATCH 11/13] revision re: other open-source projects --- paper/paper.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/paper/paper.md b/paper/paper.md index 0e06d2c197..09a21187c3 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -105,8 +105,8 @@ without compromising its focus on transparent and reliable model implementations. This is in contrast to the inherent opaqueness of closed-source commercial software, which prevents users from inspecting the source code to ensure a model implementation's validity or traceability to a reference. -It is also in contrast to other open-source projects, which tend to be -be "walled garden" projects with few contributions from outside the host organization. +It is also in contrast to other open-source PV projects, where code review +and contributions typically come from a single institution. These key aspects, along with sustained contributions from a passionate and committed community, have led to pvlib python's widespread adoption across the PV From 7d2673ef60138c544d59cd2dca0a06ef8f4c543a Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 17 Nov 2023 10:00:23 -0500 Subject: [PATCH 12/13] bibtex tweaks --- paper/paper.bib | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/paper/paper.bib b/paper/paper.bib index ca09bd1352..3c47d01c09 100644 --- a/paper/paper.bib +++ b/paper/paper.bib @@ -80,7 +80,7 @@ @inproceedings{Andrews2014 publisher = {{IEEE}}, author = {Robert W. Andrews and Joshua S. Stein and Clifford Hansen and Daniel Riley}, title = {Introduction to the open source {PV} {LIB} for python Photovoltaic system modelling package}, - booktitle = {40th Photovoltaic Specialist Conference} + booktitle = {40th IEEE Photovoltaic Specialists Conference} } @inproceedings{Mermoud1994, @@ -105,7 +105,7 @@ @inproceedings{Passow2017 title = {PlantPredict: Solar Performance Modeling Made Simple}, url = {http://dx.doi.org/10.1109/PVSC.2017.8366450}, DOI = {10.1109/pvsc.2017.8366450}, - booktitle = {44th Photovoltaic Specialist Conference (PVSC)}, + booktitle = {44th IEEE Photovoltaic Specialists Conference (PVSC)}, publisher = {IEEE}, author = {Passow, Kendra and Ngan, Lauren and Rich, Geoffrey and Lee, Mitch and Kaplan, Stephen}, year = {2017}, @@ -136,7 +136,7 @@ @inproceedings{Pai2016 title = {Introducing CASSYS: An open-source software for simulation of grid-connected photovoltaic systems}, url = {http://dx.doi.org/10.1109/PVSC.2016.7749839}, DOI = {10.1109/pvsc.2016.7749839}, - booktitle = {43rd Photovoltaic Specialists Conference}, + booktitle = {43rd IEEE Photovoltaic Specialists Conference}, publisher = {IEEE}, author = {Pai, Abhijeet and Thevenard, Didier}, year = {2016}, From 21f971fade59e32600302ed9fc524ddf4942c95c Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Wed, 29 Nov 2023 10:37:57 -0500 Subject: [PATCH 13/13] clarify pvlib matlab comparison --- paper/paper.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/paper/paper.md b/paper/paper.md index 09a21187c3..0b4ea67126 100644 --- a/paper/paper.md +++ b/paper/paper.md @@ -85,8 +85,9 @@ the user. This allows the user to assemble their own model workflows, including the ability of incorporating custom modeling steps. This flexibility is essential for applications in both academia and industry. To our knowledge, the only other PV system modeling software with such a toolbox design is the -MATLAB version of pvlib [@Andrews2014], the code base from which pvlib python -was translated and has since surpassed in capability and community uptake. +original MATLAB version of pvlib [@Andrews2014]. pvlib python began as +a translation of that code base and has since surpassed it in terms of +capability, community uptake, and development attention. Another key aspect of pvlib python is that it is used via a general-purpose programming language (Python), which