Skip to content
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/sphinx/source/whatsnew/v0.9.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose the PVSystem and Array changes should be noted too


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`)


Expand All @@ -41,3 +43,4 @@ Contributors
* Naman Priyadarshi (:ghuser:`Naman-Priyadarshi`)
* Chencheng Luo (:ghuser:`roger-lcc`)
* Prajwal Borkar (:ghuser:`PrajwalBorkar`)
* Cliff Hansen (:ghuser:`cwhanse`)
4 changes: 2 additions & 2 deletions pvlib/clearsky.py
Original file line number Diff line number Diff line change
Expand Up @@ -960,8 +960,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
-------
Expand Down
4 changes: 2 additions & 2 deletions pvlib/irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
56 changes: 48 additions & 8 deletions pvlib/modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -1471,11 +1471,14 @@ 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'``; 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.
Expand All @@ -1490,6 +1493,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
-----
Expand All @@ -1500,6 +1506,24 @@ 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, 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'])
Expand Down Expand Up @@ -1724,16 +1748,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
-------
Expand Down
27 changes: 15 additions & 12 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand All @@ -157,13 +157,16 @@ class PVSystem:
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.
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.

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.
Expand Down Expand Up @@ -1258,14 +1261,14 @@ class Array:
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.
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.
Expand Down
12 changes: 12 additions & 0 deletions pvlib/tests/test_clearsky.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 41 additions & 4 deletions pvlib/tests/test_irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand Down
39 changes: 39 additions & 0 deletions pvlib/tests/test_modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,6 +497,45 @@ 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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI this comment doesn't match the test code

# 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
# 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):
mc = ModelChain(sapm_dc_snl_ac_system, location)
weather = pd.DataFrame()
Expand Down