Skip to content

Commit 684b247

Browse files
authored
NOCT cell temperature function (#1177)
* initial code for noct cell temperature function * error message, test, docs * complete the docstring * correct noct function, adjust tests, add comments * Revert "correct noct function, adjust tests, add comments" This reverts commit 31240db. * redo function fixes and tests * finish redo of edits * fix tests, change name to noct_sam * add test to reproduce SAM output * format, fix text asserts * more assert fixes, add rtol * fix tes * docstring format * fixes from review
1 parent dc617d0 commit 684b247

File tree

4 files changed

+183
-1
lines changed

4 files changed

+183
-1
lines changed

docs/sphinx/source/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ PV temperature models
238238
temperature.faiman
239239
temperature.fuentes
240240
temperature.ross
241+
temperature.noct_sam
241242
pvsystem.PVSystem.sapm_celltemp
242243
pvsystem.PVSystem.pvsyst_celltemp
243244
pvsystem.PVSystem.faiman_celltemp

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ Enhancements
101101
* :py:meth:`~pvlib.pvsystem.PVSystem.get_ac` is added to calculate AC power
102102
from DC power. Use parameter ``model`` to specify which inverter model to use.
103103
(:pull:`1147`, :issue:`998`, :pull:`1150`)
104+
* Added :py:func:`~pvlib.temperature.noct_sam`, a cell temperature model
105+
implemented in SAM (:pull:`1177`)
104106

105107
Bug fixes
106108
~~~~~~~~~

pvlib/temperature.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,3 +707,109 @@ def fuentes(poa_global, temp_air, wind_speed, noct_installed, module_height=5,
707707
sun0 = sun
708708

709709
return pd.Series(tmod_array - 273.15, index=poa_global.index, name='tmod')
710+
711+
712+
def _adj_for_mounting_standoff(x):
713+
# supports noct cell temperature function. Except for x > 3.5, the SAM code
714+
# and documentation aren't clear on the precise intervals. The choice of
715+
# < or <= here is pvlib's.
716+
return np.piecewise(x, [x <= 0, (x > 0) & (x < 0.5),
717+
(x >= 0.5) & (x < 1.5), (x >= 1.5) & (x < 2.5),
718+
(x >= 2.5) & (x <= 3.5), x > 3.5],
719+
[0., 18., 11., 6., 2., 0.])
720+
721+
722+
def noct_sam(poa_global, temp_air, wind_speed, noct, eta_m_ref,
723+
effective_irradiance=None, transmittance_absorptance=0.9,
724+
array_height=1, mount_standoff=4):
725+
r'''
726+
Cell temperature model from the System Advisor Model (SAM).
727+
728+
The model is described in [1]_, Section 10.6.
729+
730+
Parameters
731+
----------
732+
poa_global : numeric
733+
Total incident irradiance. [W/m^2]
734+
735+
temp_air : numeric
736+
Ambient dry bulb temperature. [C]
737+
738+
wind_speed : numeric
739+
Wind speed in m/s measured at the same height for which the wind loss
740+
factor was determined. The default value 1.0 m/s is the wind
741+
speed at module height used to determine NOCT. [m/s]
742+
743+
noct : float
744+
Nominal operating cell temperature [C], determined at conditions of
745+
800 W/m^2 irradiance, 20 C ambient air temperature and 1 m/s wind.
746+
747+
eta_m_ref : float
748+
Module external efficiency [unitless] at reference conditions of
749+
1000 W/m^2 and 20C. Calculate as
750+
:math:`\eta_{m} = \frac{V_{mp} I_{mp}}{A \times 1000 W/m^2}`
751+
where A is module area [m^2].
752+
753+
effective_irradiance : numeric, default None.
754+
The irradiance that is converted to photocurrent. If None,
755+
assumed equal to poa_global. [W/m^2]
756+
757+
transmittance_absorptance : numeric, default 0.9
758+
Coefficient for combined transmittance and absorptance effects.
759+
[unitless]
760+
761+
array_height : int, default 1
762+
Height of array above ground in stories (one story is about 3m). Must
763+
be either 1 or 2. For systems elevated less than one story, use 1.
764+
If system is elevated more than two stories, use 2.
765+
766+
mount_standoff : numeric, default 4
767+
Distance between array mounting and mounting surface. Use default
768+
if system is ground-mounted. [inches]
769+
770+
Returns
771+
-------
772+
cell_temperature : numeric
773+
Cell temperature. [C]
774+
775+
Raises
776+
------
777+
ValueError
778+
If array_height is an invalid value (must be 1 or 2).
779+
780+
References
781+
----------
782+
.. [1] Gilman, P., Dobos, A., DiOrio, N., Freeman, J., Janzou, S.,
783+
Ryberg, D., 2018, "SAM Photovoltaic Model Technical Reference
784+
Update", National Renewable Energy Laboratory Report
785+
NREL/TP-6A20-67399.
786+
'''
787+
# in [1] the denominator for irr_ratio isn't precisely clear. From
788+
# reproducing output of the SAM function noct_celltemp_t, we determined
789+
# that:
790+
# - G_total (SAM) is broadband plane-of-array irradiance before
791+
# reflections. Equivalent to pvlib variable poa_global
792+
# - Geff_total (SAM) is POA irradiance after reflections and
793+
# adjustment for spectrum. Equivalent to effective_irradiance
794+
if effective_irradiance is None:
795+
irr_ratio = 1.
796+
else:
797+
irr_ratio = effective_irradiance / poa_global
798+
799+
if array_height == 1:
800+
wind_adj = 0.51 * wind_speed
801+
elif array_height == 2:
802+
wind_adj = 0.61 * wind_speed
803+
else:
804+
raise ValueError(
805+
f'array_height must be 1 or 2, {array_height} was given')
806+
807+
noct_adj = noct + _adj_for_mounting_standoff(mount_standoff)
808+
tau_alpha = transmittance_absorptance * irr_ratio
809+
810+
# [1] Eq. 10.37 isn't clear on exactly what "G" is. SAM SSC code uses
811+
# poa_global where G appears
812+
cell_temp_init = poa_global / 800. * (noct_adj - 20.)
813+
heat_loss = 1 - eta_m_ref / tau_alpha
814+
wind_loss = 9.5 / (5.7 + 3.8 * wind_adj)
815+
return temp_air + cell_temp_init * heat_loss * wind_loss

pvlib/tests/test_temperature.py

Lines changed: 74 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from conftest import DATA_DIR, assert_series_equal
66
from numpy.testing import assert_allclose
77

8-
from pvlib import temperature
8+
from pvlib import temperature, tools
99

1010

1111
@pytest.fixture
@@ -212,3 +212,76 @@ def test_fuentes_timezone(tz):
212212

213213
assert_series_equal(out, pd.Series([47.85, 50.85, 50.85], index=index,
214214
name='tmod'))
215+
216+
217+
def test_noct_sam():
218+
poa_global, temp_air, wind_speed, noct, eta_m_ref = (1000., 25., 1., 45.,
219+
0.2)
220+
expected = 55.230790492
221+
result = temperature.noct_sam(poa_global, temp_air, wind_speed, noct,
222+
eta_m_ref)
223+
assert_allclose(result, expected)
224+
# test with different types
225+
result = temperature.noct_sam(np.array(poa_global), np.array(temp_air),
226+
np.array(wind_speed), np.array(noct),
227+
np.array(eta_m_ref))
228+
assert_allclose(result, expected)
229+
dr = pd.date_range(start='2020-01-01 12:00:00', end='2020-01-01 13:00:00',
230+
freq='1H')
231+
result = temperature.noct_sam(pd.Series(index=dr, data=poa_global),
232+
pd.Series(index=dr, data=temp_air),
233+
pd.Series(index=dr, data=wind_speed),
234+
pd.Series(index=dr, data=noct),
235+
eta_m_ref)
236+
assert_series_equal(result, pd.Series(index=dr, data=expected))
237+
238+
239+
def test_noct_sam_against_sam():
240+
# test is constructed to reproduce output from SAM v2020.11.29.
241+
# SAM calculation is the default Detailed PV System model (CEC diode model,
242+
# NOCT cell temperature model), with the only change being the soiling
243+
# loss is set to 0. Weather input is TMY3 for Phoenix AZ.
244+
# Values are taken from the Jan 1 12:00:00 timestamp.
245+
poa_total, temp_air, wind_speed, noct, eta_m_ref = (
246+
860.673, 25, 3, 46.4, 0.20551)
247+
poa_total_after_refl = 851.458 # from SAM output
248+
# compute effective irradiance
249+
# spectral loss coefficients fixed in lib_cec6par.cpp
250+
a = np.flipud([0.918093, 0.086257, -0.024459, 0.002816, -0.000126])
251+
# reproduce SAM air mass calculation
252+
zen = 56.4284
253+
elev = 358
254+
air_mass = 1. / (tools.cosd(zen) + 0.5057 * (96.080 - zen)**-1.634)
255+
air_mass *= np.exp(-0.0001184 * elev)
256+
f1 = np.polyval(a, air_mass)
257+
effective_irradiance = f1 * poa_total_after_refl
258+
transmittance_absorptance = 0.9
259+
array_height = 1
260+
mount_standoff = 4.0
261+
result = temperature.noct_sam(poa_total, temp_air, wind_speed, noct,
262+
eta_m_ref, effective_irradiance,
263+
transmittance_absorptance, array_height,
264+
mount_standoff)
265+
expected = 43.0655
266+
# rtol from limited SAM output precision
267+
assert_allclose(result, expected, rtol=1e-5)
268+
269+
270+
def test_noct_sam_options():
271+
poa_global, temp_air, wind_speed, noct, eta_m_ref = (1000., 25., 1., 45.,
272+
0.2)
273+
effective_irradiance = 1100.
274+
transmittance_absorbtance = 0.8
275+
array_height = 2
276+
mount_standoff = 2.0
277+
result = temperature.noct_sam(poa_global, temp_air, wind_speed, noct,
278+
eta_m_ref, effective_irradiance,
279+
transmittance_absorbtance, array_height,
280+
mount_standoff)
281+
expected = 60.477703576
282+
assert_allclose(result, expected)
283+
284+
285+
def test_noct_sam_errors():
286+
with pytest.raises(ValueError):
287+
temperature.noct_sam(1000., 25., 1., 34., 0.2, array_height=3)

0 commit comments

Comments
 (0)