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_sam
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_sam`, a cell temperature model
implemented in SAM (:pull:`1177`)

Bug fixes
~~~~~~~~~
Expand Down
106 changes: 106 additions & 0 deletions pvlib/temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -706,3 +706,109 @@ 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. Except for x > 3.5, 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_sam(poa_global, temp_air, wind_speed, noct, eta_m_ref,
effective_irradiance=None, transmittance_absorptance=0.9,
array_height=1, mount_standoff=4):
r'''
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
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 : float
Nominal operating cell temperature [C], determined at conditions of
800 W/m^2 irradiance, 20 C ambient air temperature and 1 m/s wind.

eta_m_ref : float
Module external efficiency [unitless] at reference conditions of
1000 W/m^2 and 20C. Calculate as
:math:`\eta_{m} = \frac{V_{mp} I_{mp}}{A \times 1000 W/m^2}`
where A is module area [m^2].

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

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.
'''
# in [1] the denominator for irr_ratio isn't precisely clear. From
# reproducing output of the SAM function noct_celltemp_t, we determined
# that:
# - G_total (SAM) is broadband plane-of-array irradiance before
# reflections. Equivalent to pvlib variable poa_global
# - Geff_total (SAM) is POA irradiance after reflections and
# adjustment for spectrum. Equivalent to effective_irradiance
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_absorptance * 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
75 changes: 74 additions & 1 deletion pvlib/tests/test_temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from conftest import DATA_DIR, assert_series_equal
from numpy.testing import assert_allclose

from pvlib import temperature
from pvlib import temperature, tools


@pytest.fixture
Expand Down Expand Up @@ -212,3 +212,76 @@ def test_fuentes_timezone(tz):

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


def test_noct_sam():
poa_global, temp_air, wind_speed, noct, eta_m_ref = (1000., 25., 1., 45.,
0.2)
expected = 55.230790492
result = temperature.noct_sam(poa_global, temp_air, wind_speed, noct,
eta_m_ref)
assert_allclose(result, expected)
# test with different types
result = temperature.noct_sam(np.array(poa_global), np.array(temp_air),
np.array(wind_speed), np.array(noct),
np.array(eta_m_ref))
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_sam(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_sam_against_sam():
# test is constructed to reproduce output from SAM v2020.11.29.
# SAM calculation is the default Detailed PV System model (CEC diode model,
# NOCT cell temperature model), with the only change being the soiling
# loss is set to 0. Weather input is TMY3 for Phoenix AZ.
# Values are taken from the Jan 1 12:00:00 timestamp.
poa_total, temp_air, wind_speed, noct, eta_m_ref = (
860.673, 25, 3, 46.4, 0.20551)
poa_total_after_refl = 851.458 # from SAM output
# compute effective irradiance
# spectral loss coefficients fixed in lib_cec6par.cpp
a = np.flipud([0.918093, 0.086257, -0.024459, 0.002816, -0.000126])
# reproduce SAM air mass calculation
zen = 56.4284
elev = 358
air_mass = 1. / (tools.cosd(zen) + 0.5057 * (96.080 - zen)**-1.634)
air_mass *= np.exp(-0.0001184 * elev)
f1 = np.polyval(a, air_mass)
effective_irradiance = f1 * poa_total_after_refl
transmittance_absorptance = 0.9
array_height = 1
mount_standoff = 4.0
result = temperature.noct_sam(poa_total, temp_air, wind_speed, noct,
eta_m_ref, effective_irradiance,
transmittance_absorptance, array_height,
mount_standoff)
expected = 43.0655
# rtol from limited SAM output precision
assert_allclose(result, expected, rtol=1e-5)


def test_noct_sam_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_sam(poa_global, temp_air, wind_speed, noct,
eta_m_ref, effective_irradiance,
transmittance_absorbtance, array_height,
mount_standoff)
expected = 60.477703576
assert_allclose(result, expected)


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