From d0c52b69d6e2ba3cdaf4f55250d58acb0761dcbe Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 8 Jun 2022 19:42:16 -0600 Subject: [PATCH 01/14] permit albedo to be a Series --- pvlib/clearsky.py | 4 +-- pvlib/irradiance.py | 4 +-- pvlib/modelchain.py | 8 ++++++ pvlib/pvsystem.py | 27 ++++++++++---------- pvlib/tests/test_clearsky.py | 15 ++++++++++++ pvlib/tests/test_irradiance.py | 45 +++++++++++++++++++++++++++++++--- 6 files changed, 82 insertions(+), 21 deletions(-) diff --git a/pvlib/clearsky.py b/pvlib/clearsky.py index 9f355669d0..0c0c0018b8 100644 --- a/pvlib/clearsky.py +++ b/pvlib/clearsky.py @@ -962,8 +962,8 @@ def bird(zenith, airmass_relative, aod380, aod500, precipitable_water, Extraterrestrial radiation [W/m^2], defaults to 1364[W/m^2] asymmetry : numeric Asymmetry factor, defaults to 0.85 - albedo : numeric - Albedo, defaults to 0.2 + albedo : numeric, default 0.2 + Ground surface albedo. [unitless] Returns ------- diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 3de4d96f65..994ad012ed 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -344,7 +344,7 @@ def get_total_irradiance(surface_tilt, surface_azimuth, airmass : None or numeric, default None Relative airmass (not adjusted for pressure). [unitless] albedo : numeric, default 0.25 - Surface albedo. [unitless] + Ground surface albedo. [unitless] surface_type : None or str, default None Surface type. See :py:func:`~pvlib.irradiance.get_ground_diffuse` for the list of accepted values. @@ -1872,7 +1872,7 @@ def gti_dirint(poa_global, aoi, solar_zenith, solar_azimuth, times, applied. albedo : numeric, default 0.25 - Surface albedo + Gound surface albedo. [unitless] model : String, default 'perez' Irradiance model. See :py:func:`get_sky_diffuse` for allowed values. diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 2798c39a68..fe18005141 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1500,6 +1500,14 @@ def prepare_inputs(self, weather): -------- ModelChain.complete_irradiance """ + # transfer albedo to weather if needed + if 'albedo' in weather.columns: + for array in self.system.Arrays: + if array.albedo: + raise ValueError('albedo found in both weather and on' + ' PVsystem. Provide albedo on one or' + ' on neither, but not on both.') + array.albedo = weather['albedo'] weather = _to_tuple(weather) self._check_multiple_input(weather, strict=False) self._verify_df(weather, required=['ghi', 'dni', 'dhi']) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 6bb89f34a3..d09909620b 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -156,14 +156,15 @@ class PVSystem: Azimuth angle of the module surface. North=0, East=90, South=180, West=270. - albedo : None or float, default None - The ground albedo. If ``None``, will attempt to use - ``surface_type`` and ``irradiance.SURFACE_ALBEDOS`` - to lookup albedo. + albedo : None or numeric, default None + Ground surface albedo. If ``None``, then ``surface_type`` is used + to look up a value in ``irradiance.SURFACE_ALBEDOS``. + If ``surface_type`` is also None then a ground surface albedo + of 0.25 is used. surface_type : None or string, default None - The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` - for valid values. + The ground surface type. Required if ``albedo`` is None. + See ``irradiance.SURFACE_ALBEDOS`` for valid values. module : None or string, default None The model name of the modules. @@ -1257,15 +1258,15 @@ class Array: single axis tracker. Mounting is used to determine module orientation. If not provided, a FixedMount with zero tilt is used. - albedo : None or float, default None - The ground albedo. If ``None``, will attempt to use - ``surface_type`` to look up an albedo value in - ``irradiance.SURFACE_ALBEDOS``. If a surface albedo - cannot be found then 0.25 is used. + albedo : None or numeric, default None + Ground surface albedo. If ``None``, then ``surface_type`` is used + to look up a value in ``irradiance.SURFACE_ALBEDOS``. + If ``surface_type`` is also None then a ground surface albedo + of 0.25 is used. surface_type : None or string, default None - The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` - for valid values. + The ground surface type. Required if ``albedo`` is None. + See ``irradiance.SURFACE_ALBEDOS`` for valid values. module : None or string, default None The model name of the modules. diff --git a/pvlib/tests/test_clearsky.py b/pvlib/tests/test_clearsky.py index 15fc74e383..1ded67bef8 100644 --- a/pvlib/tests/test_clearsky.py +++ b/pvlib/tests/test_clearsky.py @@ -756,6 +756,18 @@ def test_bird(): assert np.allclose( testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:48], rtol=1e-3 ) + # repeat test with albedo as a Series + alb_series = pd.Series(0.2, index=times) + irrads = clearsky.bird( + zenith, airmass, aod_380nm, aod_500nm, h2o_cm, o3_cm, press_mB * 100., + etr, b_a, alb_series + ) + Eb, Ebh, Gh, Dh = (irrads[_] for _ in field_names) + assert np.allclose(testdata['DEC'], np.rad2deg(declination[1:48])) + assert np.allclose(testdata['EQT'], eot[1:48], rtol=1e-4) + assert np.allclose(testdata['Hour Angle'], hour_angle[1:48]) + assert np.allclose(testdata['Zenith Ang'], zenith[1:48]) + # test keyword parameters irrads2 = clearsky.bird( zenith, airmass, aod_380nm, aod_500nm, h2o_cm, dni_extra=etr @@ -792,3 +804,6 @@ def test_bird(): testdata2[['Direct Beam', 'Direct Hz', 'Global Hz', 'Dif Hz']].iloc[11], rtol=1e-3) return pd.DataFrame({'Eb': Eb, 'Ebh': Ebh, 'Gh': Gh, 'Dh': Dh}, index=times) + + +test_bird() \ No newline at end of file diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index 80986f26c3..e39ca02955 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -120,29 +120,38 @@ def test_get_extra_radiation_invalid(): irradiance.get_extra_radiation(300, method='invalid') -def test_grounddiffuse_simple_float(): +def test_get_ground_diffuse_simple_float(): result = irradiance.get_ground_diffuse(40, 900) assert_allclose(result, 26.32000014911496) -def test_grounddiffuse_simple_series(irrad_data): +def test_get_ground_diffuse_simple_series(irrad_data): ground_irrad = irradiance.get_ground_diffuse(40, irrad_data['ghi']) assert ground_irrad.name == 'diffuse_ground' -def test_grounddiffuse_albedo_0(irrad_data): +def test_get_ground_diffuse_albedo_0(irrad_data): ground_irrad = irradiance.get_ground_diffuse( 40, irrad_data['ghi'], albedo=0) assert 0 == ground_irrad.all() +def test_get_ground_diffuse_albedo_series(times): + albedo = pd.Series(0.2, index=times) + ground_irrad = irradiance.get_ground_diffuse( + 45, pd.Series(1000, index=times), albedo) + expected = albedo * 0.5 * (1 - np.sqrt(2) / 2.) * 1000 + expected.name = 'diffuse_ground' + assert_series_equal(ground_irrad, expected) + + def test_grounddiffuse_albedo_invalid_surface(irrad_data): with pytest.raises(KeyError): irradiance.get_ground_diffuse( 40, irrad_data['ghi'], surface_type='invalid') -def test_grounddiffuse_albedo_surface(irrad_data): +def test_get_ground_diffuse_albedo_surface(irrad_data): result = irradiance.get_ground_diffuse(40, irrad_data['ghi'], surface_type='sand') assert_allclose(result, [0, 3.731058, 48.778813, 12.035025], atol=1e-4) @@ -387,6 +396,26 @@ def test_get_total_irradiance(irrad_data, ephem_data, dni_et, 'poa_ground_diffuse'] +def test_get_total_irradiance_albedo( + irrad_data, ephem_data, dni_et, relative_airmass): + models = ['isotropic', 'klucher', + 'haydavies', 'reindl', 'king', 'perez'] + albedo = pd.Series(0.2, index=ephem_data.index) + for model in models: + total = irradiance.get_total_irradiance( + 32, 180, + ephem_data['apparent_zenith'], ephem_data['azimuth'], + dni=irrad_data['dni'], ghi=irrad_data['ghi'], + dhi=irrad_data['dhi'], + dni_extra=dni_et, airmass=relative_airmass, + model=model, + albedo=albedo) + + assert total.columns.tolist() == ['poa_global', 'poa_direct', + 'poa_diffuse', 'poa_sky_diffuse', + 'poa_ground_diffuse'] + + @pytest.mark.parametrize('model', ['isotropic', 'klucher', 'haydavies', 'reindl', 'king', 'perez']) def test_get_total_irradiance_scalars(model): @@ -698,6 +727,14 @@ def test_gti_dirint(): assert_frame_equal(output, expected) + # test with albedo as a Series + albedo = pd.Series(0.05, index=times) + output = irradiance.gti_dirint( + poa_global, aoi, zenith, azimuth, times, surface_tilt, surface_azimuth, + albedo=albedo) + + assert_frame_equal(output, expected) + # test temp_dew input temp_dew = np.array([70, 80, 20]) output = irradiance.gti_dirint( From e64a9403e0952a5f6f4760d6ed7493b860d17510 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 9 Jun 2022 19:34:09 -0600 Subject: [PATCH 02/14] work on modelchain --- pvlib/modelchain.py | 26 ++++++++++++++++++-------- pvlib/tests/test_modelchain.py | 29 +++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index fe18005141..f222d55790 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1500,14 +1500,24 @@ def prepare_inputs(self, weather): -------- ModelChain.complete_irradiance """ - # transfer albedo to weather if needed - if 'albedo' in weather.columns: - for array in self.system.Arrays: - if array.albedo: - raise ValueError('albedo found in both weather and on' - ' PVsystem. Provide albedo on one or' - ' on neither, but not on both.') - array.albedo = weather['albedo'] + # transfer albedo from weather to mc.system.arrays if needed + if isinstance(weather, pd.DataFrame): + if 'albedo' in weather.columns: + for array in self.system.arrays: + if hasattr('array', 'albedo'): + raise ValueError('albedo found in both weather and on' + ' PVsystem.Array Provide albedo on' + ' one or on neither, but not both.') + array.albedo = weather['albedo'] + else: # weather is a list or tuple + for w, a in zip(weather, self.system.arrays): + if 'albedo' in w.columns: + if hasattr('a', 'albedo'): + raise ValueError('albedo found in both weather and on' + ' PVsystem.Array Provide albedo on' + ' one or on neither, but not both.') + a.albedo = weather['albedo'] + weather = _to_tuple(weather) self._check_multiple_input(weather, strict=False) self._verify_df(weather, required=['ghi', 'dni', 'dhi']) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index f4a92eadad..fa7ec3ff47 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -497,6 +497,35 @@ def test_prepare_inputs_multi_weather( assert len(mc.results.total_irrad) == num_arrays +@pytest.mark.parametrize("input_type", [tuple, list]) +def test_prepare_inputs_transfer_albedo( + sapm_dc_snl_ac_system_Array, location, input_type): + times = pd.date_range(start='20160101 1200-0700', + end='20160101 1800-0700', freq='6H') + mc = ModelChain(sapm_dc_snl_ac_system_Array, location) + # albedo on pvsystem but not in weather + weather = pd.DataFrame({'ghi': 1, 'dhi': 1, 'dni': 1}, + index=times) + mc.prepare_inputs(input_type((weather, weather))) + num_arrays = sapm_dc_snl_ac_system_Array.num_arrays + assert len(mc.results.total_irrad) == num_arrays + # albedo on both weather and system + weather['albedo'] = 0.5 + with pytest.raises(ValueError, match='albedo found in both weather'): + mc.prepare_inputs(input_type((weather, weather))) + # albedo on weather but not system + pvsystem = sapm_dc_snl_ac_system_Array + for a in pvsystem.arrays: + del a.albedo + mc = ModelChain(pvsystem, location) + mc = mc.prepare_inputs(weather) + assert mc.system.arrays[0].albedo==0.5 + mc = ModelChain(pvsystem, location) + mc = mc.prepare_inputs(input_type((weather, weather))) + for a in mc.system_arrays: + assert a.albedo==0.5 + + def test_prepare_inputs_no_irradiance(sapm_dc_snl_ac_system, location): mc = ModelChain(sapm_dc_snl_ac_system, location) weather = pd.DataFrame() From 0fa45e82ebeec08d27668977cc39e419b3b86046 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 13 Jun 2022 11:04:00 -0600 Subject: [PATCH 03/14] fix tests --- pvlib/modelchain.py | 6 +++--- pvlib/tests/test_modelchain.py | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index f222d55790..c82011f707 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1504,7 +1504,7 @@ def prepare_inputs(self, weather): if isinstance(weather, pd.DataFrame): if 'albedo' in weather.columns: for array in self.system.arrays: - if hasattr('array', 'albedo'): + if hasattr(array, 'albedo'): raise ValueError('albedo found in both weather and on' ' PVsystem.Array Provide albedo on' ' one or on neither, but not both.') @@ -1512,11 +1512,11 @@ def prepare_inputs(self, weather): else: # weather is a list or tuple for w, a in zip(weather, self.system.arrays): if 'albedo' in w.columns: - if hasattr('a', 'albedo'): + if hasattr(a, 'albedo'): raise ValueError('albedo found in both weather and on' ' PVsystem.Array Provide albedo on' ' one or on neither, but not both.') - a.albedo = weather['albedo'] + a.albedo = w['albedo'] weather = _to_tuple(weather) self._check_multiple_input(weather, strict=False) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index fa7ec3ff47..ebefec3d8f 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -519,11 +519,14 @@ def test_prepare_inputs_transfer_albedo( del a.albedo mc = ModelChain(pvsystem, location) mc = mc.prepare_inputs(weather) - assert mc.system.arrays[0].albedo==0.5 + assert (mc.system.arrays[0].albedo.values==0.5).all() + # again with weather as a tuple + for a in pvsystem.arrays: + del a.albedo mc = ModelChain(pvsystem, location) mc = mc.prepare_inputs(input_type((weather, weather))) - for a in mc.system_arrays: - assert a.albedo==0.5 + for a in mc.system.arrays: + assert (a.albedo.values==0.5).all() def test_prepare_inputs_no_irradiance(sapm_dc_snl_ac_system, location): From cdf853e4e6a3aeb003a9cffc7f4458d0794bdd56 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 13 Jun 2022 11:25:25 -0600 Subject: [PATCH 04/14] shh stickler, docstrings --- pvlib/modelchain.py | 35 +++++++++++++++++++++++++++------- pvlib/pvsystem.py | 10 ++++++---- pvlib/tests/test_clearsky.py | 3 --- pvlib/tests/test_irradiance.py | 2 +- pvlib/tests/test_modelchain.py | 4 ++-- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index c82011f707..635a85d1b3 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1473,9 +1473,11 @@ def prepare_inputs(self, weather): ---------- weather : DataFrame, or tuple or list of DataFrame Required column names include ``'dni'``, ``'ghi'``, ``'dhi'``. - Optional column names are ``'wind_speed'``, ``'temp_air'``; if not + Optional column names are ``'wind_speed'``, ``'temp_air'``, ``'albedo'``. + + If optional columns ``'wind_speed'``, ``'temp_air'`` are not provided, air temperature of 20 C and wind speed - of 0 m/s will be added to the DataFrame. + of 0 m/s will be added to the `weather` DataFrame. If `weather` is a tuple or list, it must be of the same length and order as the Arrays of the ModelChain's PVSystem. @@ -1490,6 +1492,9 @@ def prepare_inputs(self, weather): ValueError If `weather` is a tuple or list with a different length than the number of Arrays in the system. + ValueError + If ``'albedo'`` is a column in `weather` and is also an attribute + of the ModelChain's PVSystem.Arrays. Notes ----- @@ -1742,16 +1747,32 @@ def run_model(self, weather): Parameters ---------- weather : DataFrame, or tuple or list of DataFrame - Irradiance column names must include ``'dni'``, ``'ghi'``, and - ``'dhi'``. If optional columns ``'temp_air'`` and ``'wind_speed'`` + Column names must include: + + - ``'dni'`` + - ``'ghi'`` + - ``'dhi'`` + + Optional columns are: + + - ``'temp_air'`` + - ``'cell_temperature'`` + - ``'module_temperature'`` + - ``'wind_speed'`` + - ``'albedo'`` + + If optional columns ``'temp_air'`` and ``'wind_speed'`` are not provided, air temperature of 20 C and wind speed of 0 m/s are added to the DataFrame. If optional column ``'cell_temperature'`` is provided, these values are used instead - of `temperature_model`. If optional column `module_temperature` + of `temperature_model`. If optional column ``'module_temperature'`` is provided, `temperature_model` must be ``'sapm'``. - If list or tuple, must be of the same length and order as the - Arrays of the ModelChain's PVSystem. + If optional column ``'albedo'`` is provided, ``'albedo'`` may not + be present on the ModelChain's PVSystem or PVSystem.Arrays. + + If weather is a list or tuple, it must be of the same length and + order as the Arrays of the ModelChain's PVSystem. Returns ------- diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index d09909620b..ce3caa27a9 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -134,7 +134,7 @@ class PVSystem: a single array is created from the other parameters (e.g. `surface_tilt`, `surface_azimuth`). Must contain at least one Array, if length of arrays is 0 a ValueError is raised. If `arrays` is - specified the following parameters are ignored: + specified the following PVSystem parameters are ignored: - `surface_tilt` - `surface_azimuth` @@ -156,11 +156,13 @@ class PVSystem: Azimuth angle of the module surface. North=0, East=90, South=180, West=270. - albedo : None or numeric, default None + albedo : None or float, default None Ground surface albedo. If ``None``, then ``surface_type`` is used to look up a value in ``irradiance.SURFACE_ALBEDOS``. If ``surface_type`` is also None then a ground surface albedo - of 0.25 is used. + of 0.25 is used. For time-dependent albedos, add ``'albedo'`` to + the input ``'weather'`` DataFrame for + :py:class:`pvlib.modelchain.ModelChain` methods. surface_type : None or string, default None The ground surface type. Required if ``albedo`` is None. @@ -1258,7 +1260,7 @@ class Array: single axis tracker. Mounting is used to determine module orientation. If not provided, a FixedMount with zero tilt is used. - albedo : None or numeric, default None + albedo : None or float, default None Ground surface albedo. If ``None``, then ``surface_type`` is used to look up a value in ``irradiance.SURFACE_ALBEDOS``. If ``surface_type`` is also None then a ground surface albedo diff --git a/pvlib/tests/test_clearsky.py b/pvlib/tests/test_clearsky.py index 1ded67bef8..9bae7113f5 100644 --- a/pvlib/tests/test_clearsky.py +++ b/pvlib/tests/test_clearsky.py @@ -804,6 +804,3 @@ def test_bird(): testdata2[['Direct Beam', 'Direct Hz', 'Global Hz', 'Dif Hz']].iloc[11], rtol=1e-3) return pd.DataFrame({'Eb': Eb, 'Ebh': Ebh, 'Gh': Gh, 'Dh': Dh}, index=times) - - -test_bird() \ No newline at end of file diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index e39ca02955..ff66c4457b 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -139,7 +139,7 @@ def test_get_ground_diffuse_albedo_0(irrad_data): def test_get_ground_diffuse_albedo_series(times): albedo = pd.Series(0.2, index=times) ground_irrad = irradiance.get_ground_diffuse( - 45, pd.Series(1000, index=times), albedo) + 45, pd.Series(1000, index=times), albedo) expected = albedo * 0.5 * (1 - np.sqrt(2) / 2.) * 1000 expected.name = 'diffuse_ground' assert_series_equal(ground_irrad, expected) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index ebefec3d8f..733cc127b8 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -519,14 +519,14 @@ def test_prepare_inputs_transfer_albedo( del a.albedo mc = ModelChain(pvsystem, location) mc = mc.prepare_inputs(weather) - assert (mc.system.arrays[0].albedo.values==0.5).all() + assert (mc.system.arrays[0].albedo.values == 0.5).all() # again with weather as a tuple for a in pvsystem.arrays: del a.albedo mc = ModelChain(pvsystem, location) mc = mc.prepare_inputs(input_type((weather, weather))) for a in mc.system.arrays: - assert (a.albedo.values==0.5).all() + assert (a.albedo.values == 0.5).all() def test_prepare_inputs_no_irradiance(sapm_dc_snl_ac_system, location): From b3ee1564a2e89ab2c13b2e4f1fa8ffe6203ca20c Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 13 Jun 2022 12:18:03 -0600 Subject: [PATCH 05/14] improve coverage --- pvlib/modelchain.py | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 635a85d1b3..90f3839031 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1471,9 +1471,10 @@ def prepare_inputs(self, weather): Parameters ---------- - weather : DataFrame, or tuple or list of DataFrame + weather : tuple or list of DataFrames Required column names include ``'dni'``, ``'ghi'``, ``'dhi'``. - Optional column names are ``'wind_speed'``, ``'temp_air'``, ``'albedo'``. + Optional column names are ``'wind_speed'``, ``'temp_air'``, + ``'albedo'``. If optional columns ``'wind_speed'``, ``'temp_air'`` are not provided, air temperature of 20 C and wind speed @@ -1506,22 +1507,23 @@ def prepare_inputs(self, weather): ModelChain.complete_irradiance """ # transfer albedo from weather to mc.system.arrays if needed - if isinstance(weather, pd.DataFrame): - if 'albedo' in weather.columns: + if len(weather) == 1: # single weather, multiple arrays + w = weather[0] + if 'albedo' in w.columns: for array in self.system.arrays: if hasattr(array, 'albedo'): raise ValueError('albedo found in both weather and on' ' PVsystem.Array Provide albedo on' ' one or on neither, but not both.') - array.albedo = weather['albedo'] - else: # weather is a list or tuple - for w, a in zip(weather, self.system.arrays): + array.albedo = w['albedo'] + else: # multiple weather and arrays + for w, array in zip(weather, self.system.arrays): if 'albedo' in w.columns: - if hasattr(a, 'albedo'): + if hasattr(array, 'albedo'): raise ValueError('albedo found in both weather and on' ' PVsystem.Array Provide albedo on' ' one or on neither, but not both.') - a.albedo = w['albedo'] + array.albedo = w['albedo'] weather = _to_tuple(weather) self._check_multiple_input(weather, strict=False) From 9e1ce0eb60c3ca6a3010bf29f110c27eae6c765b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 13 Jun 2022 13:00:54 -0600 Subject: [PATCH 06/14] improve coverage correctly --- pvlib/modelchain.py | 7 +++---- pvlib/tests/test_modelchain.py | 5 +++++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 90f3839031..0629914162 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1507,15 +1507,14 @@ def prepare_inputs(self, weather): ModelChain.complete_irradiance """ # transfer albedo from weather to mc.system.arrays if needed - if len(weather) == 1: # single weather, multiple arrays - w = weather[0] - if 'albedo' in w.columns: + if isinstance(weather, pd.DataFrame): # single weather, multiple arrays + if 'albedo' in weather.columns: for array in self.system.arrays: if hasattr(array, 'albedo'): raise ValueError('albedo found in both weather and on' ' PVsystem.Array Provide albedo on' ' one or on neither, but not both.') - array.albedo = w['albedo'] + array.albedo = weather['albedo'] else: # multiple weather and arrays for w, array in zip(weather, self.system.arrays): if 'albedo' in w.columns: diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 733cc127b8..2349e69711 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -506,6 +506,11 @@ def test_prepare_inputs_transfer_albedo( # albedo on pvsystem but not in weather weather = pd.DataFrame({'ghi': 1, 'dhi': 1, 'dni': 1}, index=times) + # weather as a single DataFrame + mc.prepare_inputs(weather) + num_arrays = sapm_dc_snl_ac_system_Array.num_arrays + assert len(mc.results.total_irrad) == num_arrays + # repeat with tuple of weather mc.prepare_inputs(input_type((weather, weather))) num_arrays = sapm_dc_snl_ac_system_Array.num_arrays assert len(mc.results.total_irrad) == num_arrays From 59a37da05131a41a735684d8e61f796a763ca9bb Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 13 Jun 2022 13:19:18 -0600 Subject: [PATCH 07/14] finalize coverage, stickler --- pvlib/modelchain.py | 2 +- pvlib/tests/test_modelchain.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 0629914162..cb0644c7f1 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1507,7 +1507,7 @@ def prepare_inputs(self, weather): ModelChain.complete_irradiance """ # transfer albedo from weather to mc.system.arrays if needed - if isinstance(weather, pd.DataFrame): # single weather, multiple arrays + if isinstance(weather, pd.DataFrame): # single weather, many arrays if 'albedo' in weather.columns: for array in self.system.arrays: if hasattr(array, 'albedo'): diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 2349e69711..1ff238e836 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -516,6 +516,8 @@ def test_prepare_inputs_transfer_albedo( assert len(mc.results.total_irrad) == num_arrays # albedo on both weather and system weather['albedo'] = 0.5 + with pytest.raises(ValueError, match='albedo found in both weather'): + mc.prepare_inputs(weather) with pytest.raises(ValueError, match='albedo found in both weather'): mc.prepare_inputs(input_type((weather, weather))) # albedo on weather but not system From 0295dd015ac0fa5ca2de36f5b1b5980ee750a121 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 13 Jun 2022 13:38:27 -0600 Subject: [PATCH 08/14] whatsnew --- docs/sphinx/source/whatsnew/v0.9.2.rst | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.9.2.rst b/docs/sphinx/source/whatsnew/v0.9.2.rst index dd5c18cb4e..0c067b0aa4 100644 --- a/docs/sphinx/source/whatsnew/v0.9.2.rst +++ b/docs/sphinx/source/whatsnew/v0.9.2.rst @@ -8,15 +8,17 @@ Deprecations Enhancements ~~~~~~~~~~~~ +* albedo can now be provided as a column in the `weather` DataFrame input to + :py:method:`pvlib.modelchain.ModelChain.run_model`. (:issue:`1387`, :pull:`1469`) Bug fixes ~~~~~~~~~ * :py:func:`pvlib.irradiance.get_total_irradiance` and :py:func:`pvlib.solarposition.spa_python` now raise an error instead - of silently ignoring unknown parameters (:pull:`1437`) + of silently ignoring unknown parameters. (:pull:`1437`) * Fix a bug in :py:func:`pvlib.solarposition.sun_rise_set_transit_ephem` where passing localized timezones with large UTC offsets could return - rise/set/transit times for the wrong day in recent versions of ``ephem`` + rise/set/transit times for the wrong day in recent versions of ``ephem``. (:issue:`1449`, :pull:`1448`) @@ -41,3 +43,4 @@ Contributors * Naman Priyadarshi (:ghuser:`Naman-Priyadarshi`) * Chencheng Luo (:ghuser:`roger-lcc`) * Prajwal Borkar (:ghuser:`PrajwalBorkar`) +* Cliff Hansen (:ghuser:`cwhanse`) From b9a7308c9df02df7490c7b9a679547af0d5e9032 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 13 Jun 2022 17:37:45 -0600 Subject: [PATCH 09/14] from review --- pvlib/irradiance.py | 2 +- pvlib/modelchain.py | 4 ++-- pvlib/pvsystem.py | 8 ++++---- pvlib/tests/test_clearsky.py | 20 ++++++++++++++++---- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 994ad012ed..4e03a1e1d6 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -1872,7 +1872,7 @@ def gti_dirint(poa_global, aoi, solar_zenith, solar_azimuth, times, applied. albedo : numeric, default 0.25 - Gound surface albedo. [unitless] + Ground surface albedo. [unitless] model : String, default 'perez' Irradiance model. See :py:func:`get_sky_diffuse` for allowed values. diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index cb0644c7f1..e21e0edd0c 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1471,7 +1471,7 @@ def prepare_inputs(self, weather): Parameters ---------- - weather : tuple or list of DataFrames + weather : DataFrame, or tuple or list of DataFrames Required column names include ``'dni'``, ``'ghi'``, ``'dhi'``. Optional column names are ``'wind_speed'``, ``'temp_air'``, ``'albedo'``. @@ -1770,7 +1770,7 @@ def run_model(self, weather): is provided, `temperature_model` must be ``'sapm'``. If optional column ``'albedo'`` is provided, ``'albedo'`` may not - be present on the ModelChain's PVSystem or PVSystem.Arrays. + be present on the ModelChain's PVSystem.Arrays. If weather is a list or tuple, it must be of the same length and order as the Arrays of the ModelChain's PVSystem. diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index ce3caa27a9..cb69a0a1cd 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -165,8 +165,8 @@ class PVSystem: :py:class:`pvlib.modelchain.ModelChain` methods. surface_type : None or string, default None - The ground surface type. Required if ``albedo`` is None. - See ``irradiance.SURFACE_ALBEDOS`` for valid values. + The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for + valid values. module : None or string, default None The model name of the modules. @@ -1267,8 +1267,8 @@ class Array: of 0.25 is used. surface_type : None or string, default None - The ground surface type. Required if ``albedo`` is None. - See ``irradiance.SURFACE_ALBEDOS`` for valid values. + The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for valid + values. module : None or string, default None The model name of the modules. diff --git a/pvlib/tests/test_clearsky.py b/pvlib/tests/test_clearsky.py index 9bae7113f5..d603cbcdfe 100644 --- a/pvlib/tests/test_clearsky.py +++ b/pvlib/tests/test_clearsky.py @@ -763,10 +763,22 @@ def test_bird(): etr, b_a, alb_series ) Eb, Ebh, Gh, Dh = (irrads[_] for _ in field_names) - assert np.allclose(testdata['DEC'], np.rad2deg(declination[1:48])) - assert np.allclose(testdata['EQT'], eot[1:48], rtol=1e-4) - assert np.allclose(testdata['Hour Angle'], hour_angle[1:48]) - assert np.allclose(testdata['Zenith Ang'], zenith[1:48]) + direct_beam = pd.Series(np.where(dawn, Eb, 0.), index=times).fillna(0.) + assert np.allclose( + testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:48], rtol=1e-3 + ) + direct_horz = pd.Series(np.where(dawn, Ebh, 0.), index=times).fillna(0.) + assert np.allclose( + testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:48], rtol=1e-3 + ) + global_horz = pd.Series(np.where(dawn, Gh, 0.), index=times).fillna(0.) + assert np.allclose( + testdata['Global Hz'].where(dusk, 0.), global_horz[1:48], rtol=1e-3 + ) + diffuse_horz = pd.Series(np.where(dawn, Dh, 0.), index=times).fillna(0.) + assert np.allclose( + testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:48], rtol=1e-3 + ) # test keyword parameters irrads2 = clearsky.bird( From e4c5fa513d42532401a2ac7ed6b0589d7d91c885 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 20 Jun 2022 15:43:22 -0600 Subject: [PATCH 10/14] don't mutate inputs --- pvlib/irradiance.py | 2 +- pvlib/modelchain.py | 38 +++++++++------------ pvlib/pvsystem.py | 61 ++++++++++++++++++++++------------ pvlib/tests/test_modelchain.py | 24 ++----------- pvlib/tests/test_pvsystem.py | 32 +++++++++++++++--- 5 files changed, 87 insertions(+), 70 deletions(-) diff --git a/pvlib/irradiance.py b/pvlib/irradiance.py index 4e03a1e1d6..03ddd13f5a 100644 --- a/pvlib/irradiance.py +++ b/pvlib/irradiance.py @@ -304,7 +304,7 @@ def beam_component(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, def get_total_irradiance(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, dni, ghi, dhi, dni_extra=None, airmass=None, - albedo=.25, surface_type=None, + albedo=0.25, surface_type=None, model='isotropic', model_perez='allsitescomposite1990'): r""" diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index e21e0edd0c..8bdb924d0a 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1339,6 +1339,16 @@ def _prep_inputs_solar_pos(self, weather): **kwargs) return self + def _prep_inputs_albedo(self, weather): + """ + Get albedo from weather + """ + try: + self.results.albedo = _tuple_from_dfs(weather, 'albedo') + except KeyError: + self.results.albedo = None + return self + def _prep_inputs_airmass(self): """ Assign airmass @@ -1480,6 +1490,9 @@ def prepare_inputs(self, weather): provided, air temperature of 20 C and wind speed of 0 m/s will be added to the `weather` DataFrame. + If optional column ``'albedo'`` is provided, albedo values in the + ModelChain's PVSystem.arrays are ignored. + If `weather` is a tuple or list, it must be of the same length and order as the Arrays of the ModelChain's PVSystem. @@ -1493,37 +1506,16 @@ def prepare_inputs(self, weather): ValueError If `weather` is a tuple or list with a different length than the number of Arrays in the system. - ValueError - If ``'albedo'`` is a column in `weather` and is also an attribute - of the ModelChain's PVSystem.Arrays. Notes ----- Assigns attributes to ``results``: ``times``, ``weather``, - ``solar_position``, ``airmass``, ``total_irrad``, ``aoi`` + ``solar_position``, ``airmass``, ``total_irrad``, ``aoi``, ``albedo``. See also -------- ModelChain.complete_irradiance """ - # transfer albedo from weather to mc.system.arrays if needed - if isinstance(weather, pd.DataFrame): # single weather, many arrays - if 'albedo' in weather.columns: - for array in self.system.arrays: - if hasattr(array, 'albedo'): - raise ValueError('albedo found in both weather and on' - ' PVsystem.Array Provide albedo on' - ' one or on neither, but not both.') - array.albedo = weather['albedo'] - else: # multiple weather and arrays - for w, array in zip(weather, self.system.arrays): - if 'albedo' in w.columns: - if hasattr(array, 'albedo'): - raise ValueError('albedo found in both weather and on' - ' PVsystem.Array Provide albedo on' - ' one or on neither, but not both.') - array.albedo = w['albedo'] - weather = _to_tuple(weather) self._check_multiple_input(weather, strict=False) self._verify_df(weather, required=['ghi', 'dni', 'dhi']) @@ -1531,6 +1523,7 @@ def prepare_inputs(self, weather): self._prep_inputs_solar_pos(weather) self._prep_inputs_airmass() + self._prep_inputs_albedo(weather) # PVSystem.get_irradiance and SingleAxisTracker.get_irradiance # and PVSystem.get_aoi and SingleAxisTracker.get_aoi @@ -1555,6 +1548,7 @@ def prepare_inputs(self, weather): _tuple_from_dfs(self.results.weather, 'dni'), _tuple_from_dfs(self.results.weather, 'ghi'), _tuple_from_dfs(self.results.weather, 'dhi'), + albedo=self.results.albedo, airmass=self.results.airmass['airmass_relative'], model=self.transposition_model ) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index cb69a0a1cd..6362df038f 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -336,13 +336,12 @@ def get_aoi(self, solar_zenith, solar_azimuth): @_unwrap_single_value def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, - dni_extra=None, airmass=None, model='haydavies', - **kwargs): + dni_extra=None, airmass=None, + model='haydavies', **kwargs): """ Uses the :py:func:`irradiance.get_total_irradiance` function to calculate the plane of array irradiance components on a tilted - surface defined by ``self.surface_tilt``, - ``self.surface_azimuth``, and ``self.albedo``. + surface defined by ``self.surface_tilt`` and ``self.surface_azimuth```. Parameters ---------- @@ -351,15 +350,15 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, solar_azimuth : float or Series. Solar azimuth angle. dni : float or Series or tuple of float or Series - Direct Normal Irradiance + Direct Normal Irradiance. [W/m2] ghi : float or Series or tuple of float or Series - Global horizontal irradiance + Global horizontal irradiance. [W/m2] dhi : float or Series or tuple of float or Series - Diffuse horizontal irradiance + Diffuse horizontal irradiance. [W/m2] dni_extra : None, float or Series, default None - Extraterrestrial direct normal irradiance + Extraterrestrial direct normal irradiance [W/m2] airmass : None, float or Series, default None - Airmass + Airmass. [unitless] model : String, default 'haydavies' Irradiance model. @@ -379,17 +378,29 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, poa_irradiance : DataFrame or tuple of DataFrame Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse', 'poa_sky_diffuse', 'poa_ground_diffuse'``. + + See also + -------- + :py:func:`pvlib.irradiance.get_total_irradiance` """ dni = self._validate_per_array(dni, system_wide=True) ghi = self._validate_per_array(ghi, system_wide=True) dhi = self._validate_per_array(dhi, system_wide=True) + + try: + albedo = kwargs.pop('albedo') + except KeyError: + albedo = None + albedo = self._validate_per_array(albedo, system_wide=True) + return tuple( array.get_irradiance(solar_zenith, solar_azimuth, dni, ghi, dhi, + albedo, dni_extra, airmass, model, **kwargs) - for array, dni, ghi, dhi in zip( - self.arrays, dni, ghi, dhi + for array, dni, ghi, dhi, albedo in zip( + self.arrays, dni, ghi, dhi, albedo ) ) @@ -1428,15 +1439,14 @@ def get_aoi(self, solar_zenith, solar_azimuth): solar_zenith, solar_azimuth) def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, - dni_extra=None, airmass=None, model='haydavies', - **kwargs): + albedo=None, dni_extra=None, airmass=None, + model='haydavies', **kwargs): """ Get plane of array irradiance components. Uses the :py:func:`pvlib.irradiance.get_total_irradiance` function to calculate the plane of array irradiance components for a surface - defined by ``self.surface_tilt`` and ``self.surface_azimuth`` with - albedo ``self.albedo``. + defined by ``self.surface_tilt`` and ``self.surface_azimuth``. Parameters ---------- @@ -1445,15 +1455,17 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, solar_azimuth : float or Series. Solar azimuth angle. dni : float or Series - Direct Normal Irradiance - ghi : float or Series + Direct normal irradiance. [W/m2] + ghi : float or Series. [W/m2] Global horizontal irradiance dhi : float or Series - Diffuse horizontal irradiance + Diffuse horizontal irradiance. [W/m2] + albedo : None, float or Series, default None + Ground surface albedo. [unitless] dni_extra : None, float or Series, default None - Extraterrestrial direct normal irradiance + Extraterrestrial direct normal irradiance. [W/m2] airmass : None, float or Series, default None - Airmass + Airmass. [unitless] model : String, default 'haydavies' Irradiance model. @@ -1466,7 +1478,14 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, poa_irradiance : DataFrame Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse', 'poa_sky_diffuse', 'poa_ground_diffuse'``. + + See also + -------- + :py:func:`pvlib.irradiance.get_total_irradiance` """ + if albedo is None: + albedo = self.albedo + # not needed for all models, but this is easier if dni_extra is None: dni_extra = irradiance.get_extra_radiation(solar_zenith.index) @@ -1479,10 +1498,10 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, orientation['surface_azimuth'], solar_zenith, solar_azimuth, dni, ghi, dhi, + albedo=albedo, dni_extra=dni_extra, airmass=airmass, model=model, - albedo=self.albedo, **kwargs) def get_iam(self, aoi, iam_model='physical'): diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 1ff238e836..1dafe3eddb 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -498,13 +498,13 @@ def test_prepare_inputs_multi_weather( @pytest.mark.parametrize("input_type", [tuple, list]) -def test_prepare_inputs_transfer_albedo( +def test_prepare_inputs_albedo_in_weather( sapm_dc_snl_ac_system_Array, location, input_type): times = pd.date_range(start='20160101 1200-0700', end='20160101 1800-0700', freq='6H') mc = ModelChain(sapm_dc_snl_ac_system_Array, location) # albedo on pvsystem but not in weather - weather = pd.DataFrame({'ghi': 1, 'dhi': 1, 'dni': 1}, + weather = pd.DataFrame({'ghi': 1, 'dhi': 1, 'dni': 1, 'albedo': 0.5}, index=times) # weather as a single DataFrame mc.prepare_inputs(weather) @@ -514,26 +514,6 @@ def test_prepare_inputs_transfer_albedo( mc.prepare_inputs(input_type((weather, weather))) num_arrays = sapm_dc_snl_ac_system_Array.num_arrays assert len(mc.results.total_irrad) == num_arrays - # albedo on both weather and system - weather['albedo'] = 0.5 - with pytest.raises(ValueError, match='albedo found in both weather'): - mc.prepare_inputs(weather) - with pytest.raises(ValueError, match='albedo found in both weather'): - mc.prepare_inputs(input_type((weather, weather))) - # albedo on weather but not system - pvsystem = sapm_dc_snl_ac_system_Array - for a in pvsystem.arrays: - del a.albedo - mc = ModelChain(pvsystem, location) - mc = mc.prepare_inputs(weather) - assert (mc.system.arrays[0].albedo.values == 0.5).all() - # again with weather as a tuple - for a in pvsystem.arrays: - del a.albedo - mc = ModelChain(pvsystem, location) - mc = mc.prepare_inputs(input_type((weather, weather))) - for a in mc.system.arrays: - assert (a.albedo.values == 0.5).all() def test_prepare_inputs_no_irradiance(sapm_dc_snl_ac_system, location): diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 1141e490e9..f09019de79 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1689,17 +1689,41 @@ def test_PVSystem_get_irradiance(): irrads['dhi']) expected = pd.DataFrame(data=np.array( - [[ 883.65494055, 745.86141676, 137.79352379, 126.397131 , - 11.39639279], - [ 0. , -0. , 0. , 0. , 0. ]]), + [[883.65494055, 745.86141676, 137.79352379, 126.397131, 11.39639279], + [ 0. , -0. , 0. , 0. , 0. ]]), columns=['poa_global', 'poa_direct', 'poa_diffuse', 'poa_sky_diffuse', 'poa_ground_diffuse'], index=times) - assert_frame_equal(irradiance, expected, check_less_precise=2) +def test_PVSystem_get_irradiance_albedo(): + system = pvsystem.PVSystem(surface_tilt=32, surface_azimuth=135) + times = pd.date_range(start='20160101 1200-0700', + end='20160101 1800-0700', freq='6H') + location = Location(latitude=32, longitude=-111) + solar_position = location.get_solarposition(times) + irrads = pd.DataFrame({'dni':[900,0], 'ghi':[600,0], 'dhi':[100,0], + 'albedo':[0.5, 0.5]}, + index=times) + # albedo as a Series + irradiance = system.get_irradiance(solar_position['apparent_zenith'], + solar_position['azimuth'], + irrads['dni'], + irrads['ghi'], + irrads['dhi'], + albedo=irrads['albedo']) + expected = pd.DataFrame(data=np.array( + [[895.05134334, 745.86141676, 149.18992658, 126.397131, 22.79279558], + [ 0. , -0. , 0. , 0. , 0. ]]), + columns=['poa_global', 'poa_direct', + 'poa_diffuse', 'poa_sky_diffuse', + 'poa_ground_diffuse'], + index=times) + assert_frame_equal(irradiance, expected, check_less_precise=2) + + def test_PVSystem_get_irradiance_model(mocker): spy_perez = mocker.spy(irradiance, 'perez') spy_haydavies = mocker.spy(irradiance, 'haydavies') From acb3657080a24f5708dca9437a39a8abfd875f39 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 20 Jun 2022 16:53:37 -0600 Subject: [PATCH 11/14] get_irradiance in tracking.py --- pvlib/tests/test_tracking.py | 19 +++++++++++++++++++ pvlib/tracking.py | 21 +++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/pvlib/tests/test_tracking.py b/pvlib/tests/test_tracking.py index c88c92b248..bf6a77eeea 100644 --- a/pvlib/tests/test_tracking.py +++ b/pvlib/tests/test_tracking.py @@ -393,6 +393,25 @@ def test_get_irradiance(): assert_frame_equal(irradiance, expected, check_less_precise=2) + # test with albedo as a Series + irrads['albedo'] = [0.5, 0.5] + with np.errstate(invalid='ignore'): + irradiance = system.get_irradiance(tracker_data['surface_tilt'], + tracker_data['surface_azimuth'], + solar_zenith, + solar_azimuth, + irrads['dni'], + irrads['ghi'], + irrads['dhi'], + albedo=irrads['albedo']) + + expected = pd.Series(data=[21.05514984, nan], index=times, + name='poa_ground_diffuse') + + assert_series_equal(irradiance['poa_ground_diffuse'], expected, + check_less_precise=2) + + def test_SingleAxisTracker___repr__(): with pytest.warns(pvlibDeprecationWarning): diff --git a/pvlib/tracking.py b/pvlib/tracking.py index 951f2e886e..dc8173f46b 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -187,7 +187,8 @@ def get_aoi(self, surface_tilt, surface_azimuth, solar_zenith, @_unwrap_single_value def get_irradiance(self, surface_tilt, surface_azimuth, solar_zenith, solar_azimuth, dni, ghi, dhi, - dni_extra=None, airmass=None, model='haydavies', + albedo=None, dni_extra=None, airmass=None, + model='haydavies', **kwargs): """ Uses the :func:`irradiance.get_total_irradiance` function to @@ -214,6 +215,8 @@ def get_irradiance(self, surface_tilt, surface_azimuth, Global horizontal irradiance dhi : float or Series Diffuse horizontal irradiance + albedo : None, float or Series, default None + Ground surface albedo. [unitless] dni_extra : float or Series, default None Extraterrestrial direct normal irradiance airmass : float or Series, default None @@ -244,6 +247,16 @@ def get_irradiance(self, surface_tilt, surface_azimuth, ghi = self._validate_per_array(ghi, system_wide=True) dhi = self._validate_per_array(dhi, system_wide=True) + if albedo is None: + try: + albedo = kwargs.pop('albedo') + except KeyError: + # assign default albedo here because SingleAxisTracker initializes + # albedo to None + albedo = 0.25 + + albedo = self._validate_per_array(albedo, system_wide=True) + return tuple( irradiance.get_total_irradiance( surface_tilt, @@ -254,10 +267,10 @@ def get_irradiance(self, surface_tilt, surface_azimuth, dni_extra=dni_extra, airmass=airmass, model=model, - albedo=self.arrays[0].albedo, + albedo=albedo, **kwargs) - for array, dni, ghi, dhi in zip( - self.arrays, dni, ghi, dhi + for array, dni, ghi, dhi, albedo in zip( + self.arrays, dni, ghi, dhi, albedo ) ) From 2bd97b8c34d349aeb12c027b7caf359b7c5e3303 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 20 Jun 2022 17:34:25 -0600 Subject: [PATCH 12/14] shh stickler --- pvlib/tests/test_pvsystem.py | 10 +++++----- pvlib/tracking.py | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index f09019de79..7ecd19ecc6 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1690,7 +1690,7 @@ def test_PVSystem_get_irradiance(): expected = pd.DataFrame(data=np.array( [[883.65494055, 745.86141676, 137.79352379, 126.397131, 11.39639279], - [ 0. , -0. , 0. , 0. , 0. ]]), + [0., -0., 0., 0., 0.]]), columns=['poa_global', 'poa_direct', 'poa_diffuse', 'poa_sky_diffuse', 'poa_ground_diffuse'], @@ -1704,8 +1704,8 @@ def test_PVSystem_get_irradiance_albedo(): end='20160101 1800-0700', freq='6H') location = Location(latitude=32, longitude=-111) solar_position = location.get_solarposition(times) - irrads = pd.DataFrame({'dni':[900,0], 'ghi':[600,0], 'dhi':[100,0], - 'albedo':[0.5, 0.5]}, + irrads = pd.DataFrame({'dni': [900, 0], 'ghi': [600, 0], 'dhi': [100, 0], + 'albedo': [0.5, 0.5]}, index=times) # albedo as a Series irradiance = system.get_irradiance(solar_position['apparent_zenith'], @@ -1716,14 +1716,14 @@ def test_PVSystem_get_irradiance_albedo(): albedo=irrads['albedo']) expected = pd.DataFrame(data=np.array( [[895.05134334, 745.86141676, 149.18992658, 126.397131, 22.79279558], - [ 0. , -0. , 0. , 0. , 0. ]]), + [0., -0., 0., 0., 0.]]), columns=['poa_global', 'poa_direct', 'poa_diffuse', 'poa_sky_diffuse', 'poa_ground_diffuse'], index=times) assert_frame_equal(irradiance, expected, check_less_precise=2) - + def test_PVSystem_get_irradiance_model(mocker): spy_perez = mocker.spy(irradiance, 'perez') spy_haydavies = mocker.spy(irradiance, 'haydavies') diff --git a/pvlib/tracking.py b/pvlib/tracking.py index dc8173f46b..df013ff33d 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -251,8 +251,8 @@ def get_irradiance(self, surface_tilt, surface_azimuth, try: albedo = kwargs.pop('albedo') except KeyError: - # assign default albedo here because SingleAxisTracker initializes - # albedo to None + # assign default albedo here because SingleAxisTracker + # initializes albedo to None albedo = 0.25 albedo = self._validate_per_array(albedo, system_wide=True) From 5f0d455a254f7a7667207d1682ed67bf6436c631 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 20 Jun 2022 17:49:33 -0600 Subject: [PATCH 13/14] shh stickler --- pvlib/tests/test_pvsystem.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 7ecd19ecc6..a6ffe93d46 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1717,10 +1717,9 @@ def test_PVSystem_get_irradiance_albedo(): expected = pd.DataFrame(data=np.array( [[895.05134334, 745.86141676, 149.18992658, 126.397131, 22.79279558], [0., -0., 0., 0., 0.]]), - columns=['poa_global', 'poa_direct', - 'poa_diffuse', 'poa_sky_diffuse', - 'poa_ground_diffuse'], - index=times) + columns=['poa_global', 'poa_direct', 'poa_diffuse', 'poa_sky_diffuse', + 'poa_ground_diffuse'], + index=times) assert_frame_equal(irradiance, expected, check_less_precise=2) From 0a10e56772eeb25b57631a7fda826e7ff5a5209e Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 21 Jun 2022 11:08:53 -0600 Subject: [PATCH 14/14] improvements from review --- pvlib/pvsystem.py | 22 +++++++++++----------- pvlib/tests/test_modelchain.py | 1 - pvlib/tracking.py | 9 +++------ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 6362df038f..3f37f66da2 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -336,7 +336,7 @@ def get_aoi(self, solar_zenith, solar_azimuth): @_unwrap_single_value def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, - dni_extra=None, airmass=None, + albedo=None, dni_extra=None, airmass=None, model='haydavies', **kwargs): """ Uses the :py:func:`irradiance.get_total_irradiance` function to @@ -345,9 +345,9 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, Parameters ---------- - solar_zenith : float or Series. + solar_zenith : float or Series Solar zenith angle. - solar_azimuth : float or Series. + solar_azimuth : float or Series Solar azimuth angle. dni : float or Series or tuple of float or Series Direct Normal Irradiance. [W/m2] @@ -355,8 +355,11 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, Global horizontal irradiance. [W/m2] dhi : float or Series or tuple of float or Series Diffuse horizontal irradiance. [W/m2] - dni_extra : None, float or Series, default None - Extraterrestrial direct normal irradiance [W/m2] + albedo : None, float or Series, default None + Ground surface albedo. [unitless] + dni_extra : None, float, Series or tuple of float or Series, + default None + Extraterrestrial direct normal irradiance. [W/m2] airmass : None, float or Series, default None Airmass. [unitless] model : String, default 'haydavies' @@ -387,17 +390,14 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, ghi = self._validate_per_array(ghi, system_wide=True) dhi = self._validate_per_array(dhi, system_wide=True) - try: - albedo = kwargs.pop('albedo') - except KeyError: - albedo = None albedo = self._validate_per_array(albedo, system_wide=True) return tuple( array.get_irradiance(solar_zenith, solar_azimuth, dni, ghi, dhi, - albedo, - dni_extra, airmass, model, + albedo=albedo, + dni_extra=dni_extra, airmass=airmass, + model=model, **kwargs) for array, dni, ghi, dhi, albedo in zip( self.arrays, dni, ghi, dhi, albedo diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 1dafe3eddb..1c990d73e8 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -503,7 +503,6 @@ def test_prepare_inputs_albedo_in_weather( times = pd.date_range(start='20160101 1200-0700', end='20160101 1800-0700', freq='6H') mc = ModelChain(sapm_dc_snl_ac_system_Array, location) - # albedo on pvsystem but not in weather weather = pd.DataFrame({'ghi': 1, 'dhi': 1, 'dni': 1, 'albedo': 0.5}, index=times) # weather as a single DataFrame diff --git a/pvlib/tracking.py b/pvlib/tracking.py index df013ff33d..c3df9e1f7e 100644 --- a/pvlib/tracking.py +++ b/pvlib/tracking.py @@ -248,12 +248,9 @@ def get_irradiance(self, surface_tilt, surface_azimuth, dhi = self._validate_per_array(dhi, system_wide=True) if albedo is None: - try: - albedo = kwargs.pop('albedo') - except KeyError: - # assign default albedo here because SingleAxisTracker - # initializes albedo to None - albedo = 0.25 + # assign default albedo here because SingleAxisTracker + # initializes albedo to None + albedo = 0.25 albedo = self._validate_per_array(albedo, system_wide=True)