Skip to content

Commit 577f3b5

Browse files
authored
Accept albedo in weather input to ModelChain.run_model method (#1469)
* permit albedo to be a Series * work on modelchain * fix tests * shh stickler, docstrings * improve coverage * improve coverage correctly * finalize coverage, stickler * whatsnew * from review * don't mutate inputs * get_irradiance in tracking.py * shh stickler * shh stickler * improvements from review
1 parent 04e3ffd commit 577f3b5

11 files changed

+254
-64
lines changed

docs/sphinx/source/whatsnew/v0.9.2.rst

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,17 @@ Deprecations
88

99
Enhancements
1010
~~~~~~~~~~~~
11+
* albedo can now be provided as a column in the `weather` DataFrame input to
12+
:py:method:`pvlib.modelchain.ModelChain.run_model`. (:issue:`1387`, :pull:`1469`)
1113

1214
Bug fixes
1315
~~~~~~~~~
1416
* :py:func:`pvlib.irradiance.get_total_irradiance` and
1517
:py:func:`pvlib.solarposition.spa_python` now raise an error instead
16-
of silently ignoring unknown parameters (:pull:`1437`)
18+
of silently ignoring unknown parameters. (:pull:`1437`)
1719
* Fix a bug in :py:func:`pvlib.solarposition.sun_rise_set_transit_ephem`
1820
where passing localized timezones with large UTC offsets could return
19-
rise/set/transit times for the wrong day in recent versions of ``ephem``
21+
rise/set/transit times for the wrong day in recent versions of ``ephem``.
2022
(:issue:`1449`, :pull:`1448`)
2123

2224

@@ -43,4 +45,5 @@ Contributors
4345
* Naman Priyadarshi (:ghuser:`Naman-Priyadarshi`)
4446
* Chencheng Luo (:ghuser:`roger-lcc`)
4547
* Prajwal Borkar (:ghuser:`PrajwalBorkar`)
48+
* Cliff Hansen (:ghuser:`cwhanse`)
4649
* Kevin Anderson (:ghuser:`kanderso-nrel`)

pvlib/clearsky.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -960,8 +960,8 @@ def bird(zenith, airmass_relative, aod380, aod500, precipitable_water,
960960
Extraterrestrial radiation [W/m^2], defaults to 1364[W/m^2]
961961
asymmetry : numeric
962962
Asymmetry factor, defaults to 0.85
963-
albedo : numeric
964-
Albedo, defaults to 0.2
963+
albedo : numeric, default 0.2
964+
Ground surface albedo. [unitless]
965965
966966
Returns
967967
-------

pvlib/irradiance.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,7 @@ def beam_component(surface_tilt, surface_azimuth, solar_zenith, solar_azimuth,
304304
def get_total_irradiance(surface_tilt, surface_azimuth,
305305
solar_zenith, solar_azimuth,
306306
dni, ghi, dhi, dni_extra=None, airmass=None,
307-
albedo=.25, surface_type=None,
307+
albedo=0.25, surface_type=None,
308308
model='isotropic',
309309
model_perez='allsitescomposite1990'):
310310
r"""
@@ -344,7 +344,7 @@ def get_total_irradiance(surface_tilt, surface_azimuth,
344344
airmass : None or numeric, default None
345345
Relative airmass (not adjusted for pressure). [unitless]
346346
albedo : numeric, default 0.25
347-
Surface albedo. [unitless]
347+
Ground surface albedo. [unitless]
348348
surface_type : None or str, default None
349349
Surface type. See :py:func:`~pvlib.irradiance.get_ground_diffuse` for
350350
the list of accepted values.
@@ -1872,7 +1872,7 @@ def gti_dirint(poa_global, aoi, solar_zenith, solar_azimuth, times,
18721872
applied.
18731873
18741874
albedo : numeric, default 0.25
1875-
Surface albedo
1875+
Ground surface albedo. [unitless]
18761876
18771877
model : String, default 'perez'
18781878
Irradiance model. See :py:func:`get_sky_diffuse` for allowed values.

pvlib/modelchain.py

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,6 +1339,16 @@ def _prep_inputs_solar_pos(self, weather):
13391339
**kwargs)
13401340
return self
13411341

1342+
def _prep_inputs_albedo(self, weather):
1343+
"""
1344+
Get albedo from weather
1345+
"""
1346+
try:
1347+
self.results.albedo = _tuple_from_dfs(weather, 'albedo')
1348+
except KeyError:
1349+
self.results.albedo = None
1350+
return self
1351+
13421352
def _prep_inputs_airmass(self):
13431353
"""
13441354
Assign airmass
@@ -1471,11 +1481,17 @@ def prepare_inputs(self, weather):
14711481
14721482
Parameters
14731483
----------
1474-
weather : DataFrame, or tuple or list of DataFrame
1484+
weather : DataFrame, or tuple or list of DataFrames
14751485
Required column names include ``'dni'``, ``'ghi'``, ``'dhi'``.
1476-
Optional column names are ``'wind_speed'``, ``'temp_air'``; if not
1486+
Optional column names are ``'wind_speed'``, ``'temp_air'``,
1487+
``'albedo'``.
1488+
1489+
If optional columns ``'wind_speed'``, ``'temp_air'`` are not
14771490
provided, air temperature of 20 C and wind speed
1478-
of 0 m/s will be added to the DataFrame.
1491+
of 0 m/s will be added to the `weather` DataFrame.
1492+
1493+
If optional column ``'albedo'`` is provided, albedo values in the
1494+
ModelChain's PVSystem.arrays are ignored.
14791495
14801496
If `weather` is a tuple or list, it must be of the same length and
14811497
order as the Arrays of the ModelChain's PVSystem.
@@ -1494,7 +1510,7 @@ def prepare_inputs(self, weather):
14941510
Notes
14951511
-----
14961512
Assigns attributes to ``results``: ``times``, ``weather``,
1497-
``solar_position``, ``airmass``, ``total_irrad``, ``aoi``
1513+
``solar_position``, ``airmass``, ``total_irrad``, ``aoi``, ``albedo``.
14981514
14991515
See also
15001516
--------
@@ -1507,6 +1523,7 @@ def prepare_inputs(self, weather):
15071523

15081524
self._prep_inputs_solar_pos(weather)
15091525
self._prep_inputs_airmass()
1526+
self._prep_inputs_albedo(weather)
15101527

15111528
# PVSystem.get_irradiance and SingleAxisTracker.get_irradiance
15121529
# and PVSystem.get_aoi and SingleAxisTracker.get_aoi
@@ -1531,6 +1548,7 @@ def prepare_inputs(self, weather):
15311548
_tuple_from_dfs(self.results.weather, 'dni'),
15321549
_tuple_from_dfs(self.results.weather, 'ghi'),
15331550
_tuple_from_dfs(self.results.weather, 'dhi'),
1551+
albedo=self.results.albedo,
15341552
airmass=self.results.airmass['airmass_relative'],
15351553
model=self.transposition_model
15361554
)
@@ -1724,16 +1742,32 @@ def run_model(self, weather):
17241742
Parameters
17251743
----------
17261744
weather : DataFrame, or tuple or list of DataFrame
1727-
Irradiance column names must include ``'dni'``, ``'ghi'``, and
1728-
``'dhi'``. If optional columns ``'temp_air'`` and ``'wind_speed'``
1745+
Column names must include:
1746+
1747+
- ``'dni'``
1748+
- ``'ghi'``
1749+
- ``'dhi'``
1750+
1751+
Optional columns are:
1752+
1753+
- ``'temp_air'``
1754+
- ``'cell_temperature'``
1755+
- ``'module_temperature'``
1756+
- ``'wind_speed'``
1757+
- ``'albedo'``
1758+
1759+
If optional columns ``'temp_air'`` and ``'wind_speed'``
17291760
are not provided, air temperature of 20 C and wind speed of 0 m/s
17301761
are added to the DataFrame. If optional column
17311762
``'cell_temperature'`` is provided, these values are used instead
1732-
of `temperature_model`. If optional column `module_temperature`
1763+
of `temperature_model`. If optional column ``'module_temperature'``
17331764
is provided, `temperature_model` must be ``'sapm'``.
17341765
1735-
If list or tuple, must be of the same length and order as the
1736-
Arrays of the ModelChain's PVSystem.
1766+
If optional column ``'albedo'`` is provided, ``'albedo'`` may not
1767+
be present on the ModelChain's PVSystem.Arrays.
1768+
1769+
If weather is a list or tuple, it must be of the same length and
1770+
order as the Arrays of the ModelChain's PVSystem.
17371771
17381772
Returns
17391773
-------

pvlib/pvsystem.py

Lines changed: 59 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ class PVSystem:
134134
a single array is created from the other parameters (e.g.
135135
`surface_tilt`, `surface_azimuth`). Must contain at least one Array,
136136
if length of arrays is 0 a ValueError is raised. If `arrays` is
137-
specified the following parameters are ignored:
137+
specified the following PVSystem parameters are ignored:
138138
139139
- `surface_tilt`
140140
- `surface_azimuth`
@@ -157,13 +157,16 @@ class PVSystem:
157157
North=0, East=90, South=180, West=270.
158158
159159
albedo : None or float, default None
160-
The ground albedo. If ``None``, will attempt to use
161-
``surface_type`` and ``irradiance.SURFACE_ALBEDOS``
162-
to lookup albedo.
160+
Ground surface albedo. If ``None``, then ``surface_type`` is used
161+
to look up a value in ``irradiance.SURFACE_ALBEDOS``.
162+
If ``surface_type`` is also None then a ground surface albedo
163+
of 0.25 is used. For time-dependent albedos, add ``'albedo'`` to
164+
the input ``'weather'`` DataFrame for
165+
:py:class:`pvlib.modelchain.ModelChain` methods.
163166
164167
surface_type : None or string, default None
165-
The ground surface type. See ``irradiance.SURFACE_ALBEDOS``
166-
for valid values.
168+
The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for
169+
valid values.
167170
168171
module : None or string, default None
169172
The model name of the modules.
@@ -333,30 +336,32 @@ def get_aoi(self, solar_zenith, solar_azimuth):
333336

334337
@_unwrap_single_value
335338
def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
336-
dni_extra=None, airmass=None, model='haydavies',
337-
**kwargs):
339+
albedo=None, dni_extra=None, airmass=None,
340+
model='haydavies', **kwargs):
338341
"""
339342
Uses the :py:func:`irradiance.get_total_irradiance` function to
340343
calculate the plane of array irradiance components on a tilted
341-
surface defined by ``self.surface_tilt``,
342-
``self.surface_azimuth``, and ``self.albedo``.
344+
surface defined by ``self.surface_tilt`` and ``self.surface_azimuth```.
343345
344346
Parameters
345347
----------
346-
solar_zenith : float or Series.
348+
solar_zenith : float or Series
347349
Solar zenith angle.
348-
solar_azimuth : float or Series.
350+
solar_azimuth : float or Series
349351
Solar azimuth angle.
350352
dni : float or Series or tuple of float or Series
351-
Direct Normal Irradiance
353+
Direct Normal Irradiance. [W/m2]
352354
ghi : float or Series or tuple of float or Series
353-
Global horizontal irradiance
355+
Global horizontal irradiance. [W/m2]
354356
dhi : float or Series or tuple of float or Series
355-
Diffuse horizontal irradiance
356-
dni_extra : None, float or Series, default None
357-
Extraterrestrial direct normal irradiance
357+
Diffuse horizontal irradiance. [W/m2]
358+
albedo : None, float or Series, default None
359+
Ground surface albedo. [unitless]
360+
dni_extra : None, float, Series or tuple of float or Series,
361+
default None
362+
Extraterrestrial direct normal irradiance. [W/m2]
358363
airmass : None, float or Series, default None
359-
Airmass
364+
Airmass. [unitless]
360365
model : String, default 'haydavies'
361366
Irradiance model.
362367
@@ -376,17 +381,26 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
376381
poa_irradiance : DataFrame or tuple of DataFrame
377382
Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',
378383
'poa_sky_diffuse', 'poa_ground_diffuse'``.
384+
385+
See also
386+
--------
387+
:py:func:`pvlib.irradiance.get_total_irradiance`
379388
"""
380389
dni = self._validate_per_array(dni, system_wide=True)
381390
ghi = self._validate_per_array(ghi, system_wide=True)
382391
dhi = self._validate_per_array(dhi, system_wide=True)
392+
393+
albedo = self._validate_per_array(albedo, system_wide=True)
394+
383395
return tuple(
384396
array.get_irradiance(solar_zenith, solar_azimuth,
385397
dni, ghi, dhi,
386-
dni_extra, airmass, model,
398+
albedo=albedo,
399+
dni_extra=dni_extra, airmass=airmass,
400+
model=model,
387401
**kwargs)
388-
for array, dni, ghi, dhi in zip(
389-
self.arrays, dni, ghi, dhi
402+
for array, dni, ghi, dhi, albedo in zip(
403+
self.arrays, dni, ghi, dhi, albedo
390404
)
391405
)
392406

@@ -1258,14 +1272,14 @@ class Array:
12581272
If not provided, a FixedMount with zero tilt is used.
12591273
12601274
albedo : None or float, default None
1261-
The ground albedo. If ``None``, will attempt to use
1262-
``surface_type`` to look up an albedo value in
1263-
``irradiance.SURFACE_ALBEDOS``. If a surface albedo
1264-
cannot be found then 0.25 is used.
1275+
Ground surface albedo. If ``None``, then ``surface_type`` is used
1276+
to look up a value in ``irradiance.SURFACE_ALBEDOS``.
1277+
If ``surface_type`` is also None then a ground surface albedo
1278+
of 0.25 is used.
12651279
12661280
surface_type : None or string, default None
1267-
The ground surface type. See ``irradiance.SURFACE_ALBEDOS``
1268-
for valid values.
1281+
The ground surface type. See ``irradiance.SURFACE_ALBEDOS`` for valid
1282+
values.
12691283
12701284
module : None or string, default None
12711285
The model name of the modules.
@@ -1425,15 +1439,14 @@ def get_aoi(self, solar_zenith, solar_azimuth):
14251439
solar_zenith, solar_azimuth)
14261440

14271441
def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
1428-
dni_extra=None, airmass=None, model='haydavies',
1429-
**kwargs):
1442+
albedo=None, dni_extra=None, airmass=None,
1443+
model='haydavies', **kwargs):
14301444
"""
14311445
Get plane of array irradiance components.
14321446
14331447
Uses the :py:func:`pvlib.irradiance.get_total_irradiance` function to
14341448
calculate the plane of array irradiance components for a surface
1435-
defined by ``self.surface_tilt`` and ``self.surface_azimuth`` with
1436-
albedo ``self.albedo``.
1449+
defined by ``self.surface_tilt`` and ``self.surface_azimuth``.
14371450
14381451
Parameters
14391452
----------
@@ -1442,15 +1455,17 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
14421455
solar_azimuth : float or Series.
14431456
Solar azimuth angle.
14441457
dni : float or Series
1445-
Direct Normal Irradiance
1446-
ghi : float or Series
1458+
Direct normal irradiance. [W/m2]
1459+
ghi : float or Series. [W/m2]
14471460
Global horizontal irradiance
14481461
dhi : float or Series
1449-
Diffuse horizontal irradiance
1462+
Diffuse horizontal irradiance. [W/m2]
1463+
albedo : None, float or Series, default None
1464+
Ground surface albedo. [unitless]
14501465
dni_extra : None, float or Series, default None
1451-
Extraterrestrial direct normal irradiance
1466+
Extraterrestrial direct normal irradiance. [W/m2]
14521467
airmass : None, float or Series, default None
1453-
Airmass
1468+
Airmass. [unitless]
14541469
model : String, default 'haydavies'
14551470
Irradiance model.
14561471
@@ -1463,7 +1478,14 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
14631478
poa_irradiance : DataFrame
14641479
Column names are: ``'poa_global', 'poa_direct', 'poa_diffuse',
14651480
'poa_sky_diffuse', 'poa_ground_diffuse'``.
1481+
1482+
See also
1483+
--------
1484+
:py:func:`pvlib.irradiance.get_total_irradiance`
14661485
"""
1486+
if albedo is None:
1487+
albedo = self.albedo
1488+
14671489
# not needed for all models, but this is easier
14681490
if dni_extra is None:
14691491
dni_extra = irradiance.get_extra_radiation(solar_zenith.index)
@@ -1476,10 +1498,10 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
14761498
orientation['surface_azimuth'],
14771499
solar_zenith, solar_azimuth,
14781500
dni, ghi, dhi,
1501+
albedo=albedo,
14791502
dni_extra=dni_extra,
14801503
airmass=airmass,
14811504
model=model,
1482-
albedo=self.albedo,
14831505
**kwargs)
14841506

14851507
def get_iam(self, aoi, iam_model='physical'):

pvlib/tests/test_clearsky.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -756,6 +756,30 @@ def test_bird():
756756
assert np.allclose(
757757
testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:48], rtol=1e-3
758758
)
759+
# repeat test with albedo as a Series
760+
alb_series = pd.Series(0.2, index=times)
761+
irrads = clearsky.bird(
762+
zenith, airmass, aod_380nm, aod_500nm, h2o_cm, o3_cm, press_mB * 100.,
763+
etr, b_a, alb_series
764+
)
765+
Eb, Ebh, Gh, Dh = (irrads[_] for _ in field_names)
766+
direct_beam = pd.Series(np.where(dawn, Eb, 0.), index=times).fillna(0.)
767+
assert np.allclose(
768+
testdata['Direct Beam'].where(dusk, 0.), direct_beam[1:48], rtol=1e-3
769+
)
770+
direct_horz = pd.Series(np.where(dawn, Ebh, 0.), index=times).fillna(0.)
771+
assert np.allclose(
772+
testdata['Direct Hz'].where(dusk, 0.), direct_horz[1:48], rtol=1e-3
773+
)
774+
global_horz = pd.Series(np.where(dawn, Gh, 0.), index=times).fillna(0.)
775+
assert np.allclose(
776+
testdata['Global Hz'].where(dusk, 0.), global_horz[1:48], rtol=1e-3
777+
)
778+
diffuse_horz = pd.Series(np.where(dawn, Dh, 0.), index=times).fillna(0.)
779+
assert np.allclose(
780+
testdata['Dif Hz'].where(dusk, 0.), diffuse_horz[1:48], rtol=1e-3
781+
)
782+
759783
# test keyword parameters
760784
irrads2 = clearsky.bird(
761785
zenith, airmass, aod_380nm, aod_500nm, h2o_cm, dni_extra=etr

0 commit comments

Comments
 (0)