Skip to content

NOCT cell temperature function #1177

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
Mar 10, 2021
1 change: 1 addition & 0 deletions docs/sphinx/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ PV temperature models
temperature.faiman
temperature.fuentes
temperature.ross
temperature.noct
pvsystem.PVSystem.sapm_celltemp
pvsystem.PVSystem.pvsyst_celltemp
pvsystem.PVSystem.faiman_celltemp
Expand Down
2 changes: 2 additions & 0 deletions docs/sphinx/source/whatsnew/v0.9.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,8 @@ Enhancements
* :py:meth:`~pvlib.pvsystem.PVSystem.get_ac` is added to calculate AC power
from DC power. Use parameter ``model`` to specify which inverter model to use.
(:pull:`1147`, :issue:`998`, :pull:`1150`)
* Added :py:func:`~pvlib.temperature.noct`, a cell temperature model
implemented in SAM (:pull:`1177`)

Bug fixes
~~~~~~~~~
Expand Down
97 changes: 97 additions & 0 deletions pvlib/temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,3 +706,100 @@ def fuentes(poa_global, temp_air, wind_speed, noct_installed, module_height=5,
sun0 = sun

return pd.Series(tmod_array - 273.15, index=poa_global.index, name='tmod')


def _adj_for_mounting_standoff(x):
# supports noct cell temperature function. The SAM code and documentation
# aren't clear on the precise intervals, the choice of < or <= here is
# pvlib's.
return np.piecewise(x, [x <= 0, (x > 0) & (x < 0.5),
(x >= 0.5) & (x < 1.5), (x >= 1.5) & (x < 2.5),
(x >= 2.5) & (x <= 3.5), x > 3.5],
[0., 18., 11., 6., 2., 0.])


def noct(poa_global, temp_air, wind_speed, noct, eta_m_ref,
effective_irradiance=None, transmittance_absorbtance=0.9,
array_height=1, mount_standoff=4):
'''
Cell temperature model from the System Advisor Model (SAM).

The model is described in [1]_, Section 10.6.

Parameters
----------
poa_global : numeric
Total incident irradiance. [W/m^2]

temp_air : numeric
Ambient dry bulb temperature. [C]

wind_speed : numeric, default 1.0
Copy link
Member

Choose a reason for hiding this comment

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

no default value in the function signature

Wind speed in m/s measured at the same height for which the wind loss
factor was determined. The default value 1.0 m/s is the wind
speed at module height used to determine NOCT. [m/s]

noct : numeric
Nominal operating cell temperature [C], determined at conditions of
800 W/m^2 irradiance, 20 C ambient air temperature and 1 m/s wind.

effective_irradiance : numeric, default None.
The irradiance that is converted to photocurrent. If None,
assumed equal to poa_global. [W/m^2]

eta_m_ref : numeric
Module external efficiency at reference conditions of 1000 W/m^2 and
20C. Calculate as P_mp (V_mp x I_mp) divided by 1000 W/m^2. [unitless]

transmittance_absorptance : numeric, default 0.9
Coefficient for combined transmittance and absorptance effects.
[unitless]

array_height : int, default 1
Height of array above ground in stories (one story is about 3m). Must
be either 1 or 2. For systems elevated less than one story, use 1.
If system is elevated more than two stories, use 2.

mount_standoff : numeric, default 4
Distance between array mounting and mounting surface. Use default
if system is ground-mounted. [inches]

Returns
-------
cell_temperature : numeric
Cell temperature. [C]

Raises
------
ValueError
If array_height is an invalid value (must be 1 or 2).

References
----------
.. [1] Gilman, P., Dobos, A., DiOrio, N., Freeman, J., Janzou, S.,
Ryberg, D., 2018, "SAM Photovoltaic Model Technical Reference
Update", National Renewable Energy Laboratory Report
NREL/TP-6A20-67399.
'''
if effective_irradiance is None:
irr_ratio = 1.
else:
irr_ratio = effective_irradiance / poa_global
Copy link
Member

Choose a reason for hiding this comment

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

Not sure this is correct. Eq 10.22 has the ratio as G0/G, where G0 is irradiance after reflections and G is after reflections and airmass.


if array_height == 1:
wind_adj = 0.51 * wind_speed
elif array_height == 2:
wind_adj = 0.61 * wind_speed
else:
raise ValueError(
f'array_height must be 1 or 2, {array_height} was given')

noct_adj = noct + _adj_for_mounting_standoff(mount_standoff)
tau_alpha = transmittance_absorbtance * irr_ratio

# [1] Eq. 10.37 isn't clear on exactly what "G" is. SAM SSC code uses
# poa_global where G appears
Copy link
Member

Choose a reason for hiding this comment

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

At the bottom of page 57 it says "The cell temperature is only calculated when the effective transmitted irradiance from Equation 10.25 is greater than zero, G>0.", and tracing back through to Eq 10.20 shows that G is after accounting for surface reflection and airmass.

cell_temp_init = poa_global / 800. * (noct_adj - 20.)
heat_loss = 1 - eta_m_ref / tau_alpha
wind_loss = 9.5 / (5.7 + 3.8 * wind_adj)
return temp_air + cell_temp_init * heat_loss * wind_loss
42 changes: 42 additions & 0 deletions pvlib/tests/test_temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,45 @@ def test_fuentes_timezone(tz):

assert_series_equal(out, pd.Series([47.85, 50.85, 50.85], index=index,
name='tmod'))


def test_noct():
poa_global, temp_air, wind_speed, noct, eta_m_ref = (1000., 25., 1., 45.,
0.2)
expected = 54.151119403
result = temperature.noct(poa_global, temp_air, wind_speed, noct,
eta_m_ref)
assert assert_allclose(result, expected)
# test with different types
result = temperature.noct(np.array(poa_global), np.array(temp_air),
np.array(wind_speed), np.array(noct),
np.array(eta_m_ref))
assert assert_allclose(result, expected)
dr = pd.date_range(start='2020-01-01 12:00:00', end='2020-01-01 13:00:00',
freq='1H')
result = temperature.noct(pd.Series(index=dr, data=poa_global),
pd.Series(index=dr, data=temp_air),
pd.Series(index=dr, data=wind_speed),
pd.Series(index=dr, data=noct),
eta_m_ref)
assert_series_equal(result, pd.Series(index=dr, data=expected))


def test_noct_options():
poa_global, temp_air, wind_speed, noct, eta_m_ref = (1000., 25., 1., 45.,
0.2)
effective_irradiance = 1100.
transmittance_absorbtance = 0.8
array_height = 2
mount_standoff = 2.0
result = temperature.noct(poa_global, temp_air, wind_speed, noct,
eta_m_ref, effective_irradiance,
transmittance_absorbtance, array_height,
mount_standoff)
expected = 60.477703576
assert assert_allclose(result, expected)


def test_noct_errors():
with pytest.raises(ValueError):
temperature.noct(1000., 25., 1., 34., 0.2, array_height=3)