Skip to content

Commit f94b5ad

Browse files
authored
Handle poa_global and effective_irradiance for cell temperature models (#1129)
* add switch between poa_global and effective_irradiance for cell temperature models * handle tuple and single DF * handle tuples etc., improve some docstrings * more handle tuples * add missing argument * whatsnew * edits from review, add test for different poa_global and effective_irradiance * move to helper function, add test * edits
1 parent b0d29f5 commit f94b5ad

File tree

4 files changed

+120
-20
lines changed

4 files changed

+120
-20
lines changed

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ Enhancements
4646
:py:class:`~pvlib.pvsystem.PVSystem` and
4747
:py:class:`~pvlib.modelchain.ModelChain` (as ``ac_model='sandia_multi'``).
4848
(:pull:`1076`, :issue:`1067`)
49+
* :py:class:`~pvlib.modelchain.ModelChain` 'run_model' methods now
50+
automatically switch to using ``'effective_irradiance'`` (if available) for
51+
cell temperature models, when ``'poa_global'`` is not provided in input
52+
weather or calculated from input weather data.
4953

5054
Bug fixes
5155
~~~~~~~~~

pvlib/modelchain.py

Lines changed: 65 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1023,7 +1023,9 @@ def _set_celltemp(self, model):
10231023
-------
10241024
self
10251025
"""
1026-
poa = _tuple_from_dfs(self.results.total_irrad, 'poa_global')
1026+
1027+
poa = _irrad_for_celltemp(self.results.total_irrad,
1028+
self.results.effective_irradiance)
10271029
temp_air = _tuple_from_dfs(self.weather, 'temp_air')
10281030
wind_speed = _tuple_from_dfs(self.weather, 'wind_speed')
10291031
self.results.cell_temperature = model(poa, temp_air, wind_speed)
@@ -1464,13 +1466,22 @@ def prepare_inputs_from_poa(self, data):
14641466
return self
14651467

14661468
def _get_cell_temperature(self, data,
1467-
total_irrad, temperature_model_parameters):
1469+
poa, temperature_model_parameters):
14681470
"""Extract the cell temperature data from a DataFrame.
14691471
1470-
If 'cell_temperature' column exists then it is returned. If
1471-
'module_temperature' column exists then it is used to calculate
1472-
the cell temperature. If neither column exists then None is
1472+
If 'cell_temperature' column exists in data then it is returned. If
1473+
'module_temperature' column exists in data, then it is used with poa to
1474+
calculate the cell temperature. If neither column exists then None is
14731475
returned.
1476+
1477+
Parameters
1478+
----------
1479+
data : DataFrame (not a tuple of DataFrame)
1480+
poa : Series (not a tuple of Series)
1481+
1482+
Returns
1483+
-------
1484+
Series
14741485
"""
14751486
if 'cell_temperature' in data:
14761487
return data['cell_temperature']
@@ -1483,14 +1494,14 @@ def _get_cell_temperature(self, data,
14831494
# use SAPM cell temperature model only
14841495
return pvlib.temperature.sapm_cell_from_module(
14851496
module_temperature=data['module_temperature'],
1486-
poa_global=total_irrad['poa_global'],
1497+
poa_global=poa,
14871498
deltaT=temperature_model_parameters['deltaT'])
14881499

1489-
def _prepare_temperature_single_array(self, data):
1490-
"""Set cell_temperature using a single weather data frame."""
1500+
def _prepare_temperature_single_array(self, data, poa):
1501+
"""Set cell_temperature using a single data frame."""
14911502
self.results.cell_temperature = self._get_cell_temperature(
14921503
data,
1493-
self.results.total_irrad,
1504+
poa,
14941505
self.system.temperature_model_parameters
14951506
)
14961507
if self.results.cell_temperature is None:
@@ -1505,7 +1516,7 @@ def _prepare_temperature(self, data=None):
15051516
If 'data' contains 'cell_temperature', these values are assigned to
15061517
attribute ``cell_temperature``. If 'data' contains 'module_temperature`
15071518
and `temperature_model' is 'sapm', cell temperature is calculated using
1508-
:py:func:`pvlib.temperature.sapm_celL_from_module`. Otherwise, cell
1519+
:py:func:`pvlib.temperature.sapm_cell_from_module`. Otherwise, cell
15091520
temperature is calculated by 'temperature_model'.
15101521
15111522
Parameters
@@ -1521,14 +1532,16 @@ def _prepare_temperature(self, data=None):
15211532
Assigns attribute ``results.cell_temperature``.
15221533
15231534
"""
1535+
poa = _irrad_for_celltemp(self.results.total_irrad,
1536+
self.results.effective_irradiance)
15241537
if not isinstance(data, tuple) and self.system.num_arrays > 1:
1538+
# broadcast data to all arrays
15251539
data = (data,) * self.system.num_arrays
15261540
elif not isinstance(data, tuple):
1527-
return self._prepare_temperature_single_array(data)
1541+
return self._prepare_temperature_single_array(data, poa)
15281542
given_cell_temperature = tuple(itertools.starmap(
15291543
self._get_cell_temperature,
1530-
zip(data, self.results.total_irrad,
1531-
self.system.temperature_model_parameters)
1544+
zip(data, poa, self.system.temperature_model_parameters)
15321545
))
15331546
# If cell temperature has been specified for all arrays return
15341547
# immediately and do not try to compute it.
@@ -1716,10 +1729,8 @@ def run_model_from_effective_irradiance(self, data=None):
17161729
----------
17171730
data : DataFrame, or list or tuple of DataFrame
17181731
Required column is ``'effective_irradiance'``.
1719-
If optional column ``'cell_temperature'`` is provided, these values
1720-
are used instead of `temperature_model`. If optional column
1721-
``'module_temperature'`` is provided, `temperature_model` must be
1722-
``'sapm'``.
1732+
Optional columns include ``'cell_temperature'``,
1733+
``'module_temperature'`` and ``'poa_global'``.
17231734
17241735
If the ModelChain's PVSystem has multiple arrays, `data` must be a
17251736
list or tuple with the same length and order as the PVsystem's
@@ -1740,6 +1751,20 @@ def run_model_from_effective_irradiance(self, data=None):
17401751
17411752
Notes
17421753
-----
1754+
Optional ``data`` columns ``'cell_temperature'``,
1755+
``'module_temperature'`` and ``'poa_global'`` are used for determining
1756+
cell temperature.
1757+
1758+
* If optional column ``'cell_temperature'`` is present, these values
1759+
are used and `temperature_model` is ignored.
1760+
* If optional column ``'module_temperature'`` is preset,
1761+
`temperature_model` must be ``'sapm'``.
1762+
* Otherwise, cell temperature is calculated using `temperature_model`.
1763+
1764+
The cell temperature models require plane-of-array irradiance as input.
1765+
If optional column ``'poa_global'`` is present, these data are used.
1766+
If ``'poa_global'`` is not present, ``'effective_irradiance'`` is used.
1767+
17431768
Assigns attributes: ``weather``, ``total_irrad``,
17441769
``effective_irradiance``, ``cell_temperature``, ``dc``, ``ac``,
17451770
``losses``, ``diode_params`` (if dc_model is a single diode model).
@@ -1760,6 +1785,29 @@ def run_model_from_effective_irradiance(self, data=None):
17601785
return self
17611786

17621787

1788+
def _irrad_for_celltemp(total_irrad, effective_irradiance):
1789+
"""
1790+
Determine irradiance to use for cell temperature models, in order
1791+
of preference 'poa_global' then 'effective_irradiance'
1792+
1793+
Returns
1794+
-------
1795+
Series or tuple of Series
1796+
tuple if total_irrad is a tuple of DataFrame
1797+
1798+
"""
1799+
if isinstance(total_irrad, tuple):
1800+
if all(['poa_global' in df for df in total_irrad]):
1801+
return _tuple_from_dfs(total_irrad, 'poa_global')
1802+
else:
1803+
return effective_irradiance
1804+
else:
1805+
if 'poa_global' in total_irrad:
1806+
return total_irrad['poa_global']
1807+
else:
1808+
return effective_irradiance
1809+
1810+
17631811
def _snl_params(inverter_params):
17641812
"""Return True if `inverter_params` includes parameters for the
17651813
Sandia inverter model."""

pvlib/temperature.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ def _temperature_model_params(model, parameter_set):
4949

5050

5151
def sapm_cell(poa_global, temp_air, wind_speed, a, b, deltaT,
52-
irrad_ref=1000):
52+
irrad_ref=1000.):
5353
r'''
5454
Calculate cell temperature per the Sandia Array Performance Model.
5555
@@ -215,7 +215,7 @@ def sapm_module(poa_global, temp_air, wind_speed, a, b):
215215

216216

217217
def sapm_cell_from_module(module_temperature, poa_global, deltaT,
218-
irrad_ref=1000):
218+
irrad_ref=1000.):
219219
r'''
220220
Calculate cell temperature from module temperature using the Sandia Array
221221
Performance Model.

pvlib/tests/test_modelchain.py

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -802,7 +802,7 @@ def test__prepare_temperature_arrays_weather(sapm_dc_snl_ac_system_same_arrays,
802802
location, weather,
803803
total_irrad):
804804
data = weather.copy()
805-
data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad
805+
data[['poa_global', 'poa_direct', 'poa_diffuse']] = total_irrad
806806
data_two = data.copy()
807807
mc = ModelChain(sapm_dc_snl_ac_system_same_arrays, location,
808808
aoi_model='no_loss', spectral_model='no_loss')
@@ -918,6 +918,31 @@ def test_run_model_from_effective_irradiance(sapm_dc_snl_ac_system, location,
918918
assert_series_equal(ac, expected)
919919

920920

921+
def test_run_model_from_effective_irradiance_no_poa_global(
922+
sapm_dc_snl_ac_system, location, weather, total_irrad):
923+
data = weather.copy()
924+
data['effective_irradiance'] = total_irrad['poa_global']
925+
mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss',
926+
spectral_model='no_loss')
927+
ac = mc.run_model_from_effective_irradiance(data).results.ac
928+
expected = pd.Series(np.array([149.280238, 96.678385]),
929+
index=data.index)
930+
assert_series_equal(ac, expected)
931+
932+
933+
def test_run_model_from_effective_irradiance_poa_global_differs(
934+
sapm_dc_snl_ac_system, location, weather, total_irrad):
935+
data = weather.copy()
936+
data[['poa_global', 'poa_diffuse', 'poa_direct']] = total_irrad
937+
data['effective_irradiance'] = data['poa_global'] * 0.8
938+
mc = ModelChain(sapm_dc_snl_ac_system, location, aoi_model='no_loss',
939+
spectral_model='no_loss')
940+
ac = mc.run_model_from_effective_irradiance(data).results.ac
941+
expected = pd.Series(np.array([118.302801, 76.099841]),
942+
index=data.index)
943+
assert_series_equal(ac, expected)
944+
945+
921946
@pytest.mark.parametrize("input_type", [tuple, list])
922947
def test_run_model_from_effective_irradiance_arrays_error(
923948
sapm_dc_snl_ac_system_Array, location, weather, total_irrad,
@@ -1745,3 +1770,26 @@ def test_modelchain__common_keys():
17451770
assert {'b'} == modelchain._common_keys(
17461771
(series, no_a)
17471772
)
1773+
1774+
1775+
def test__irrad_for_celltemp():
1776+
total_irrad = pd.DataFrame(index=[0, 1], columns=['poa_global'],
1777+
data=[10., 20.])
1778+
empty = total_irrad.drop('poa_global', axis=1)
1779+
effect_irrad = pd.Series(index=total_irrad.index, data=[5., 8.])
1780+
# test with single array inputs
1781+
poa = modelchain._irrad_for_celltemp(total_irrad, effect_irrad)
1782+
assert_series_equal(poa, total_irrad['poa_global'])
1783+
poa = modelchain._irrad_for_celltemp(empty, effect_irrad)
1784+
assert_series_equal(poa, effect_irrad)
1785+
# test with tuples
1786+
poa = modelchain._irrad_for_celltemp(
1787+
(total_irrad, total_irrad), (effect_irrad, effect_irrad))
1788+
assert len(poa) == 2
1789+
assert_series_equal(poa[0], total_irrad['poa_global'])
1790+
assert_series_equal(poa[1], total_irrad['poa_global'])
1791+
poa = modelchain._irrad_for_celltemp(
1792+
(empty, empty), (effect_irrad, effect_irrad))
1793+
assert len(poa) == 2
1794+
assert_series_equal(poa[0], effect_irrad)
1795+
assert_series_equal(poa[1], effect_irrad)

0 commit comments

Comments
 (0)