From d0c52b69d6e2ba3cdaf4f55250d58acb0761dcbe Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 8 Jun 2022 19:42:16 -0600 Subject: [PATCH 01/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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/26] 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) From cbe20996de26da49b8f5f65f42954ee42b18377d Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 21 Jun 2022 16:22:09 -0600 Subject: [PATCH 15/26] update whatsnew --- docs/sphinx/source/whatsnew/v0.9.2.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.9.2.rst b/docs/sphinx/source/whatsnew/v0.9.2.rst index 799823f986..9321bc1ac9 100644 --- a/docs/sphinx/source/whatsnew/v0.9.2.rst +++ b/docs/sphinx/source/whatsnew/v0.9.2.rst @@ -9,7 +9,9 @@ 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`) + :py:method:`pvlib.modelchain.ModelChain.run_model`. (:issue:`1387`, :pull:`1478`) +* albedo is now available as an input to :py:method`pvlib.pvsystem.PVSystem.get_irradiance` + and :py:method`pvlib.pvsystem.Array.get_irradiance`. (:pull:`1478`) Bug fixes ~~~~~~~~~ From 18a4ed1794624024a3d8fa54ae6bca6ad992f4e3 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 22 Jun 2022 20:53:31 -0600 Subject: [PATCH 16/26] Apply suggestions from code review Co-authored-by: Kevin Anderson --- docs/sphinx/source/whatsnew/v0.9.2.rst | 4 ++-- pvlib/pvsystem.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.9.2.rst b/docs/sphinx/source/whatsnew/v0.9.2.rst index 9321bc1ac9..4dd1203205 100644 --- a/docs/sphinx/source/whatsnew/v0.9.2.rst +++ b/docs/sphinx/source/whatsnew/v0.9.2.rst @@ -10,8 +10,8 @@ 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:`1478`) -* albedo is now available as an input to :py:method`pvlib.pvsystem.PVSystem.get_irradiance` - and :py:method`pvlib.pvsystem.Array.get_irradiance`. (:pull:`1478`) +* albedo is now available as an input to :py:meth:`pvlib.pvsystem.PVSystem.get_irradiance` + and :py:meth:`pvlib.pvsystem.Array.get_irradiance`. (:pull:`1478`) Bug fixes ~~~~~~~~~ diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 3f37f66da2..e6be1e4f20 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -357,7 +357,7 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, Diffuse horizontal 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, + 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 From e2e9f5e07f87f7bb4bac36f1cca4e2f02aa818d5 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 22 Jun 2022 21:16:51 -0600 Subject: [PATCH 17/26] docstring improvements --- pvlib/pvsystem.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index e6be1e4f20..fb267605c3 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -160,9 +160,7 @@ class PVSystem: 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. For time-dependent albedos, add ``'albedo'`` to - the input ``'weather'`` DataFrame for - :py:class:`pvlib.modelchain.ModelChain` methods. + of 0.25 is used. surface_type : None or string, default None The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for @@ -340,8 +338,9 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, 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`` and ``self.surface_azimuth```. + calculate the plane of array irradiance components on the tilted + surfaces defined by each array's ``surface_tilt`` and + ``surface_azimuth```. Parameters ---------- From 0bde3c75a99f3d581069f2f0f32c5307cfd06551 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 30 Jul 2022 10:03:48 -0600 Subject: [PATCH 18/26] Apply suggestions from code review Co-authored-by: Will Holmgren --- pvlib/modelchain.py | 4 ++-- pvlib/pvsystem.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 8bdb924d0a..b3eeb48328 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1488,7 +1488,7 @@ def prepare_inputs(self, weather): 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 `weather` DataFrame. + 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. @@ -1761,7 +1761,7 @@ def run_model(self, weather): 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'`` - is provided, `temperature_model` must be ``'sapm'``. + is provided, ``temperature_model`` must be ``'sapm'``. If optional column ``'albedo'`` is provided, ``'albedo'`` may not be present on the ModelChain's PVSystem.Arrays. diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index fb267605c3..ed9914b7d3 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -340,7 +340,7 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, Uses the :py:func:`irradiance.get_total_irradiance` function to calculate the plane of array irradiance components on the tilted surfaces defined by each array's ``surface_tilt`` and - ``surface_azimuth```. + ``surface_azimuth``. Parameters ---------- From 2ea9c97f3ab4d6477aca3f66a83115b8ba1b978a Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 30 Jul 2022 10:08:29 -0600 Subject: [PATCH 19/26] Update pvlib/tests/test_irradiance.py Co-authored-by: Will Holmgren --- pvlib/tests/test_irradiance.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index ff66c4457b..c4d4c5469c 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -396,22 +396,21 @@ def test_get_total_irradiance(irrad_data, ephem_data, dni_et, 'poa_ground_diffuse'] +@pytest.mark.parametrize('model', ['isotropic', 'klucher', + 'haydavies', 'reindl', 'king', 'perez']) def test_get_total_irradiance_albedo( - irrad_data, ephem_data, dni_et, relative_airmass): - models = ['isotropic', 'klucher', - 'haydavies', 'reindl', 'king', 'perez'] + irrad_data, ephem_data, dni_et, relative_airmass, model): 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) + 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', + assert total.columns.tolist() == ['poa_global', 'poa_direct', 'poa_diffuse', 'poa_sky_diffuse', 'poa_ground_diffuse'] From ab2c11a70d1457a381e9cdda3a3351e98133c19f Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 30 Jul 2022 10:36:31 -0600 Subject: [PATCH 20/26] from review --- pvlib/modelchain.py | 6 +++++- pvlib/pvsystem.py | 24 +++++++++++------------- pvlib/tests/test_irradiance.py | 6 +++--- pvlib/tests/test_pvsystem.py | 32 ++++++++++++++++---------------- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index b3eeb48328..85b5bc03e9 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -268,7 +268,7 @@ class ModelChainResult: _per_array_fields = {'total_irrad', 'aoi', 'aoi_modifier', 'spectral_modifier', 'cell_temperature', 'effective_irradiance', 'dc', 'diode_params', - 'dc_ohmic_losses', 'weather'} + 'dc_ohmic_losses', 'weather', 'albedo'} # system-level information solar_position: Optional[pd.DataFrame] = field(default=None) @@ -366,6 +366,10 @@ class ModelChainResult: """DatetimeIndex containing a copy of the index of the input weather data. """ + albedo: Optional[PerArray[pd.Series]] = None + """Series (or tuple of Series, one for each array) containing albedo. + """ + def _result_type(self, value): """Coerce `value` to the correct type according to ``self._singleton_tuples``.""" diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index ed9914b7d3..77560e04c0 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -334,7 +334,7 @@ def get_aoi(self, solar_zenith, solar_azimuth): @_unwrap_single_value def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, - albedo=None, dni_extra=None, airmass=None, + dni_extra=None, airmass=None, albedo=None, model='haydavies', **kwargs): """ Uses the :py:func:`irradiance.get_total_irradiance` function to @@ -354,13 +354,13 @@ 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] - 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] + albedo : None, float or Series, default None + Ground surface albedo. [unitless] model : String, default 'haydavies' Irradiance model. @@ -383,7 +383,7 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, See also -------- - :py:func:`pvlib.irradiance.get_total_irradiance` + pvlib.irradiance.get_total_irradiance """ dni = self._validate_per_array(dni, system_wide=True) ghi = self._validate_per_array(ghi, system_wide=True) @@ -394,10 +394,8 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, return tuple( array.get_irradiance(solar_zenith, solar_azimuth, dni, ghi, dhi, - albedo=albedo, dni_extra=dni_extra, airmass=airmass, - model=model, - **kwargs) + albedo=albedo, model=model, **kwargs) for array, dni, ghi, dhi, albedo in zip( self.arrays, dni, ghi, dhi, albedo ) @@ -1438,7 +1436,7 @@ def get_aoi(self, solar_zenith, solar_azimuth): solar_zenith, solar_azimuth) def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, - albedo=None, dni_extra=None, airmass=None, + dni_extra=None, airmass=None, albedo=None, model='haydavies', **kwargs): """ Get plane of array irradiance components. @@ -1459,12 +1457,12 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi, Global horizontal irradiance dhi : float or Series 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. [W/m2] airmass : None, float or Series, default None Airmass. [unitless] + albedo : None, float or Series, default None + Ground surface albedo. [unitless] model : String, default 'haydavies' Irradiance model. @@ -1497,9 +1495,9 @@ 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, + albedo=albedo, model=model, **kwargs) @@ -3314,7 +3312,7 @@ def dc_ohms_from_percent(vmp_ref, imp_ref, dc_ohmic_percent, See Also -------- - :py:func:`~pvlib.pvsystem.dc_ohmic_losses` + pvlib.pvsystem.dc_ohmic_losses References ---------- @@ -3349,7 +3347,7 @@ def dc_ohmic_losses(resistance, current): See Also -------- - :py:func:`~pvlib.pvsystem.dc_ohms_from_percent` + pvlib.pvsystem.dc_ohms_from_percent References ---------- diff --git a/pvlib/tests/test_irradiance.py b/pvlib/tests/test_irradiance.py index c4d4c5469c..8dc4877d0d 100644 --- a/pvlib/tests/test_irradiance.py +++ b/pvlib/tests/test_irradiance.py @@ -397,7 +397,7 @@ 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']) def test_get_total_irradiance_albedo( irrad_data, ephem_data, dni_et, relative_airmass, model): albedo = pd.Series(0.2, index=ephem_data.index) @@ -411,8 +411,8 @@ def test_get_total_irradiance_albedo( albedo=albedo) assert total.columns.tolist() == ['poa_global', 'poa_direct', - 'poa_diffuse', 'poa_sky_diffuse', - 'poa_ground_diffuse'] + 'poa_diffuse', 'poa_sky_diffuse', + 'poa_ground_diffuse'] @pytest.mark.parametrize('model', ['isotropic', 'klucher', diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index a6ffe93d46..08faff8995 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1673,17 +1673,21 @@ def test_PVSystem_multiple_array_get_aoi(): assert aoi_one > 0 -def test_PVSystem_get_irradiance(): - system = pvsystem.PVSystem(surface_tilt=32, surface_azimuth=135) +@pytest.fixture +def solar_pos(): 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) + return location.get_solarposition(times) + + +def test_PVSystem_get_irradiance(solar_pos): + system = pvsystem.PVSystem(surface_tilt=32, surface_azimuth=135) irrads = pd.DataFrame({'dni':[900,0], 'ghi':[600,0], 'dhi':[100,0]}, - index=times) + index=solar_pos.index) - irradiance = system.get_irradiance(solar_position['apparent_zenith'], - solar_position['azimuth'], + irradiance = system.get_irradiance(solar_pos['apparent_zenith'], + solar_pos['azimuth'], irrads['dni'], irrads['ghi'], irrads['dhi']) @@ -1694,22 +1698,18 @@ def test_PVSystem_get_irradiance(): columns=['poa_global', 'poa_direct', 'poa_diffuse', 'poa_sky_diffuse', 'poa_ground_diffuse'], - index=times) + index=solar_pos.index) assert_frame_equal(irradiance, expected, check_less_precise=2) -def test_PVSystem_get_irradiance_albedo(): +def test_PVSystem_get_irradiance_albedo(solar_pos): 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) + index=solar_pos.index) # albedo as a Series - irradiance = system.get_irradiance(solar_position['apparent_zenith'], - solar_position['azimuth'], + irradiance = system.get_irradiance(solar_pos['apparent_zenith'], + solar_pos['azimuth'], irrads['dni'], irrads['ghi'], irrads['dhi'], @@ -1719,7 +1719,7 @@ def test_PVSystem_get_irradiance_albedo(): [0., -0., 0., 0., 0.]]), columns=['poa_global', 'poa_direct', 'poa_diffuse', 'poa_sky_diffuse', 'poa_ground_diffuse'], - index=times) + index=solar_pos.index) assert_frame_equal(irradiance, expected, check_less_precise=2) From 4340bb0ac7a3d4491b1b799edc3929ebfb764998 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 30 Jul 2022 10:41:41 -0600 Subject: [PATCH 21/26] one more --- pvlib/tests/test_modelchain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 1c990d73e8..ffbcd2e595 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -508,11 +508,11 @@ def test_prepare_inputs_albedo_in_weather( # 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 + assert len(mc.results.albedo) == 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 + assert len(mc.results.albedo) == num_arrays def test_prepare_inputs_no_irradiance(sapm_dc_snl_ac_system, location): From 6193147f75653ab6937d9b2fbde970fab5af945a Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 1 Aug 2022 11:11:39 -0600 Subject: [PATCH 22/26] more use of fixture, add Array.get_irradiance test --- pvlib/tests/test_pvsystem.py | 109 ++++++++++++++++++++++------------- 1 file changed, 68 insertions(+), 41 deletions(-) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 08faff8995..475128a1fe 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -18,6 +18,7 @@ from pvlib.pvsystem import FixedMount from pvlib import temperature from pvlib._deprecation import pvlibDeprecationWarning +from pvlib.tools import cosd @pytest.mark.parametrize('iam_model,model_params', [ @@ -1723,24 +1724,20 @@ def test_PVSystem_get_irradiance_albedo(solar_pos): assert_frame_equal(irradiance, expected, check_less_precise=2) -def test_PVSystem_get_irradiance_model(mocker): +def test_PVSystem_get_irradiance_model(mocker, solar_pos): spy_perez = mocker.spy(irradiance, 'perez') spy_haydavies = mocker.spy(irradiance, 'haydavies') 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]}, - index=times) - system.get_irradiance(solar_position['apparent_zenith'], - solar_position['azimuth'], + index=solar_pos.index) + system.get_irradiance(solar_pos['apparent_zenith'], + solar_pos['azimuth'], irrads['dni'], irrads['ghi'], irrads['dhi']) spy_haydavies.assert_called_once() - system.get_irradiance(solar_position['apparent_zenith'], - solar_position['azimuth'], + system.get_irradiance(solar_pos['apparent_zenith'], + solar_pos['azimuth'], irrads['dni'], irrads['ghi'], irrads['dhi'], @@ -1748,31 +1745,28 @@ def test_PVSystem_get_irradiance_model(mocker): spy_perez.assert_called_once() -def test_PVSystem_multi_array_get_irradiance(): +def test_PVSystem_multi_array_get_irradiance(solar_pos): array_one = pvsystem.Array(pvsystem.FixedMount(surface_tilt=32, surface_azimuth=135)) array_two = pvsystem.Array(pvsystem.FixedMount(surface_tilt=5, surface_azimuth=150)) system = pvsystem.PVSystem(arrays=[array_one, array_two]) - location = Location(latitude=32, longitude=-111) - times = pd.date_range(start='20160101 1200-0700', - end='20160101 1800-0700', freq='6H') - solar_position = location.get_solarposition(times) + irrads = pd.DataFrame({'dni': [900, 0], 'ghi': [600, 0], 'dhi': [100, 0]}, - index=times) + index=solar_pos.index) array_one_expected = array_one.get_irradiance( - solar_position['apparent_zenith'], - solar_position['azimuth'], + solar_pos['apparent_zenith'], + solar_pos['azimuth'], irrads['dni'], irrads['ghi'], irrads['dhi'] ) array_two_expected = array_two.get_irradiance( - solar_position['apparent_zenith'], - solar_position['azimuth'], + solar_pos['apparent_zenith'], + solar_pos['azimuth'], irrads['dni'], irrads['ghi'], irrads['dhi'] ) array_one_irrad, array_two_irrad = system.get_irradiance( - solar_position['apparent_zenith'], - solar_position['azimuth'], + solar_pos['apparent_zenith'], + solar_pos['azimuth'], irrads['dni'], irrads['ghi'], irrads['dhi'] ) assert_frame_equal( @@ -1783,7 +1777,7 @@ def test_PVSystem_multi_array_get_irradiance(): ) -def test_PVSystem_multi_array_get_irradiance_multi_irrad(): +def test_PVSystem_multi_array_get_irradiance_multi_irrad(solar_pos): """Test a system with two identical arrays but different irradiance. Because only the irradiance is different we expect the same output @@ -1794,39 +1788,36 @@ def test_PVSystem_multi_array_get_irradiance_multi_irrad(): array_one = pvsystem.Array(pvsystem.FixedMount(0, 180)) array_two = pvsystem.Array(pvsystem.FixedMount(0, 180)) system = pvsystem.PVSystem(arrays=[array_one, array_two]) - location = Location(latitude=32, longitude=-111) - times = pd.date_range(start='20160101 1200-0700', - end='20160101 1800-0700', freq='6H') - solar_position = location.get_solarposition(times) + irrads = pd.DataFrame({'dni': [900, 0], 'ghi': [600, 0], 'dhi': [100, 0]}, - index=times) + index=solar_pos.index) irrads_two = pd.DataFrame( {'dni': [0, 900], 'ghi': [0, 600], 'dhi': [0, 100]}, - index=times + index=solar_pos.index ) array_irrad = system.get_irradiance( - solar_position['apparent_zenith'], - solar_position['azimuth'], + solar_pos['apparent_zenith'], + solar_pos['azimuth'], (irrads['dhi'], irrads['dhi']), (irrads['ghi'], irrads['ghi']), (irrads['dni'], irrads['dni']) ) assert_frame_equal(array_irrad[0], array_irrad[1]) array_irrad = system.get_irradiance( - solar_position['apparent_zenith'], - solar_position['azimuth'], + solar_pos['apparent_zenith'], + solar_pos['azimuth'], (irrads['dhi'], irrads_two['dhi']), (irrads['ghi'], irrads_two['ghi']), (irrads['dni'], irrads_two['dni']) ) array_one_expected = array_one.get_irradiance( - solar_position['apparent_zenith'], - solar_position['azimuth'], + solar_pos['apparent_zenith'], + solar_pos['azimuth'], irrads['dhi'], irrads['ghi'], irrads['dni'] ) array_two_expected = array_two.get_irradiance( - solar_position['apparent_zenith'], - solar_position['azimuth'], + solar_pos['apparent_zenith'], + solar_pos['azimuth'], irrads_two['dhi'], irrads_two['ghi'], irrads_two['dni'] ) assert not array_irrad[0].equals(array_irrad[1]) @@ -1835,15 +1826,15 @@ def test_PVSystem_multi_array_get_irradiance_multi_irrad(): with pytest.raises(ValueError, match="Length mismatch for per-array parameter"): system.get_irradiance( - solar_position['apparent_zenith'], - solar_position['azimuth'], + solar_pos['apparent_zenith'], + solar_pos['azimuth'], (irrads['dhi'], irrads_two['dhi'], irrads['dhi']), (irrads['ghi'], irrads_two['ghi']), irrads['dni'] ) array_irrad = system.get_irradiance( - solar_position['apparent_zenith'], - solar_position['azimuth'], + solar_pos['apparent_zenith'], + solar_pos['azimuth'], (irrads['dhi'], irrads_two['dhi']), irrads['ghi'], irrads['dni'] @@ -1852,6 +1843,42 @@ def test_PVSystem_multi_array_get_irradiance_multi_irrad(): assert not array_irrad[0].equals(array_irrad[1]) +def test_Array_get_irradiance(solar_pos): + array = pvsystem.Array(pvsystem.FixedMount(surface_tilt=32, + surface_azimuth=135)) + irrads = pd.DataFrame({'dni': [900, 0], 'ghi': [600, 0], 'dhi': [100, 0]}, + index=solar_pos.index) + # defaults for kwargs + modeled = array.get_irradiance( + solar_pos['apparent_zenith'], + solar_pos['azimuth'], + irrads['dni'], irrads['ghi'], irrads['dhi'] + ) + expected = pd.DataFrame(data=np.array( + [[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=solar_pos.index) + assert_frame_equal(modeled, expected, check_less_precise=2) + # with specified kwargs, use isotropic sky diffuse because it's easier + modeled = array.get_irradiance( + solar_pos['apparent_zenith'], + solar_pos['azimuth'], + irrads['dni'], irrads['ghi'], irrads['dhi'], + albedo=0.5, model='isotropic' + ) + sky_diffuse = irradiance.isotropic(array.mount.surface_tilt, irrads['dhi']) + ground_diff = irradiance.get_ground_diffuse( + array.mount.surface_tilt, irrads['ghi'], 0.5, surface_type=None) + aoi = irradiance.aoi(array.mount.surface_tilt, array.mount.surface_azimuth, + solar_pos['apparent_zenith'], solar_pos['azimuth']) + direct = irrads['dni'] * cosd(aoi) + expected = sky_diffuse + ground_diff + direct + assert_series_equal(expected, expected, check_less_precise=5) + + @fail_on_pvlib_version('0.10') @pytest.mark.parametrize('attr', ['module_parameters', 'module', 'module_type', 'temperature_model_parameters', 'albedo', From 0ecfb7d799da99d5a9e58423969bbd4fe54332be Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 1 Aug 2022 11:16:55 -0600 Subject: [PATCH 23/26] more decimal places --- pvlib/tests/test_pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 475128a1fe..239991fc6d 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1861,7 +1861,7 @@ def test_Array_get_irradiance(solar_pos): 'poa_diffuse', 'poa_sky_diffuse', 'poa_ground_diffuse'], index=solar_pos.index) - assert_frame_equal(modeled, expected, check_less_precise=2) + assert_frame_equal(modeled, expected, check_less_precise=5) # with specified kwargs, use isotropic sky diffuse because it's easier modeled = array.get_irradiance( solar_pos['apparent_zenith'], From af6ff0ea17cfd0123cfdaf39fe431512e7b1b568 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 10 Aug 2022 08:50:35 -0600 Subject: [PATCH 24/26] spacing --- pvlib/tests/test_pvsystem.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 239991fc6d..e2aa470bfa 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1854,13 +1854,14 @@ def test_Array_get_irradiance(solar_pos): solar_pos['azimuth'], irrads['dni'], irrads['ghi'], irrads['dhi'] ) - expected = pd.DataFrame(data=np.array( - [[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=solar_pos.index) + expected = pd.DataFrame( + data=np.array( + [[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=solar_pos.index + ) assert_frame_equal(modeled, expected, check_less_precise=5) # with specified kwargs, use isotropic sky diffuse because it's easier modeled = array.get_irradiance( From 100a8a1d1455a23f6d6d1978e04cf4104a2a3150 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 17 Aug 2022 14:18:27 -0600 Subject: [PATCH 25/26] line length --- pvlib/tests/test_pvsystem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index e2aa470bfa..7fa013d0dc 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -1856,7 +1856,8 @@ def test_Array_get_irradiance(solar_pos): ) expected = pd.DataFrame( data=np.array( - [[883.65494055, 745.86141676, 137.79352379, 126.397131, 11.39639279], + [[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'], From 1a835b1b106added3f6c29252c42a5f7b2bf2a52 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 18 Aug 2022 15:49:14 -0600 Subject: [PATCH 26/26] write albedo from system.arrays to ModelChainResult --- pvlib/modelchain.py | 3 ++- pvlib/tests/test_modelchain.py | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 85b5bc03e9..8211981433 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -1350,7 +1350,8 @@ def _prep_inputs_albedo(self, weather): try: self.results.albedo = _tuple_from_dfs(weather, 'albedo') except KeyError: - self.results.albedo = None + self.results.albedo = tuple([ + a.albedo for a in self.system.arrays]) return self def _prep_inputs_airmass(self): diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index ffbcd2e595..62b71f2042 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -495,6 +495,8 @@ def test_prepare_inputs_multi_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 + # check that albedo is transfered to mc.results from mc.system.arrays + assert mc.results.albedo == (0.2, 0.2) @pytest.mark.parametrize("input_type", [tuple, list])