Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/sphinx/source/whatsnew/v0.13.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Enhancements
MERRA-2 reanalysis data. (:pull:`2572`)
* Add :py:func:`~pvlib.spectrum.spectral_factor_polo`, a function for estimating
spectral mismatch factors for vertical PV façades. (:issue:`2406`, :pull:`2491`)
* Include `ross` and `faiman_rad` in the allowed models within
:py:meth:`pvlib.pvsystem.PVSystem.get_cell_temperature` (:issue:`2625`, :pull:`2631`)

Documentation
~~~~~~~~~~~~~
Expand Down Expand Up @@ -87,3 +89,4 @@ Contributors
* Anton Driesse (:ghuser:`adriesse`)
* Rajiv Daxini (:ghuser:`RDaxini`)
* Kevin Anderson (:ghuser:`kandersolar`)
* Rodrigo Amaro e Silva (:ghuser:`ramaroesilva`)
54 changes: 42 additions & 12 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,7 @@ def get_iam(self, aoi, iam_model='physical'):

@_unwrap_single_value
def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
effective_irradiance=None):
effective_irradiance=None, ir_down=None):
"""
Determine cell temperature using the method specified by ``model``.

Expand All @@ -431,12 +431,17 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,

model : str
Supported models include ``'sapm'``, ``'pvsyst'``,
``'faiman'``, ``'fuentes'``, and ``'noct_sam'``
``'faiman'``, ``'faiman_rad'``, ``'fuentes'``, ``'noct_sam'``,
and ``'ross'``

effective_irradiance : numeric or tuple of numeric, optional
The irradiance that is converted to photocurrent in W/m^2.
Only used for some models.

ir_down: numeric, optional
Downwelling infrared radiation from the sky, measured on a
horizontal surface in W/m^2. Only used in ``'faiman_rad'`` model.

Returns
-------
numeric or tuple of numeric
Expand All @@ -459,14 +464,16 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
# Not used for all models, but Array.get_cell_temperature handles it
effective_irradiance = self._validate_per_array(effective_irradiance,
system_wide=True)
ir_down = self._validate_per_array(ir_down, system_wide=True)

return tuple(
array.get_cell_temperature(poa_global, temp_air, wind_speed,
model, effective_irradiance)
for array, poa_global, temp_air, wind_speed, effective_irradiance
model, effective_irradiance, ir_down)
for array, poa_global, temp_air, wind_speed, effective_irradiance,
ir_down
in zip(
self.arrays, poa_global, temp_air, wind_speed,
effective_irradiance
effective_irradiance, ir_down
)
)

Expand Down Expand Up @@ -1204,7 +1211,7 @@ def get_iam(self, aoi, iam_model='physical'):
raise ValueError(model + ' is not a valid IAM model')

def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
effective_irradiance=None):
effective_irradiance=None, ir_down=None):
"""
Determine cell temperature using the method specified by ``model``.

Expand All @@ -1217,16 +1224,21 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
Ambient dry bulb temperature [C]

wind_speed : numeric
Wind speed [m/s]
Wind speed [m/s], although can be ``None`` for ``'ross'`` model
Copy link
Member

Choose a reason for hiding this comment

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

Should we add this to the corresponding PVSystem description too?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

do you mean within the function below?

@_unwrap_single_value
def get_cell_temperature(...)


model : str
Supported models include ``'sapm'``, ``'pvsyst'``,
``'faiman'``, ``'fuentes'``, and ``'noct_sam'``
``'faiman'``, ``'faiman_rad'``, ``'fuentes'``, ``'noct_sam'``,
and ``'ross'``

effective_irradiance : numeric, optional
The irradiance that is converted to photocurrent in W/m^2.
Only used for some models.

ir_down: numeric, optional
Downwelling infrared radiation from the sky, measured on a
horizontal surface in W/m^2. Only used in ``'faiman_rad'`` model.

Returns
-------
numeric
Expand All @@ -1235,8 +1247,9 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
See Also
--------
pvlib.temperature.sapm_cell, pvlib.temperature.pvsyst_cell,
pvlib.temperature.faiman, pvlib.temperature.fuentes,
pvlib.temperature.noct_sam
pvlib.temperature.faiman, pvlib.temperature.faiman_rad,
pvlib.temperature.fuentes, pvlib.temperature.noct_sam,
pvlib.temperature.ross

Notes
-----
Expand Down Expand Up @@ -1267,6 +1280,13 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
required = tuple()
optional = _build_kwargs(['u0', 'u1'],
self.temperature_model_parameters)
elif model == 'faiman_rad':
func = functools.partial(temperature.faiman_rad,
ir_down=ir_down)
required = ()
optional = _build_kwargs(['ir_down', 'u0', 'u1',
Copy link
Member

Choose a reason for hiding this comment

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

ir_down being a value in temperature_model_parameters seems wrong to me. Shouldn't it be an optional time series input like effective_irradiance is?

Copy link
Contributor Author

@ramaroesilva ramaroesilva Dec 22, 2025

Choose a reason for hiding this comment

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

You're totally right. Already handled this in my latest commits. Thanks!

'sky_view', 'emissivity'],
self.temperature_model_parameters)
elif model == 'fuentes':
func = temperature.fuentes
required = _build_tcell_args(['noct_installed'])
Expand All @@ -1283,11 +1303,21 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
optional = _build_kwargs(['transmittance_absorptance',
'array_height', 'mount_standoff'],
self.temperature_model_parameters)
elif model == 'ross':
func = temperature.ross
required = ()
# either noct or k must be defined
optional = _build_kwargs(['noct', 'k'],
self.temperature_model_parameters)
else:
raise ValueError(f'{model} is not a valid cell temperature model')

temperature_cell = func(poa_global, temp_air, wind_speed,
*required, **optional)
if model == 'ross':
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Although wind speed was kept as an input requested by get_cell_temperature, here such a differentiation is needed since ross is apparently the only model not using wind.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@cwhanse @echedey-ls this if ross can be avoided if for the wind-using functions (all except ross), wind_speed is defined as input as below (example for one function):

# func = temperature.sapm_cell # to deprecate
func = functools.partial(temperature.sapm_cell, wind_speed=wind_speed)

and the if ross with two possibilities is replaced by the single line below

temperature_cell = func(poa_global, temp_air, *required, **optional)

temperature_cell = func(poa_global, temp_air,
*required, **optional)
else:
temperature_cell = func(poa_global, temp_air, wind_speed,
*required, **optional)
return temperature_cell

def dc_ohms_from_percent(self):
Expand Down
38 changes: 38 additions & 0 deletions tests/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,44 @@ def test_PVSystem_faiman_celltemp(mocker):
assert_allclose(out, 56.4, atol=1e-1)


def test_PVSystem_faiman_rad_celltemp(mocker):
ir_down = 50 # arbitrary value
# default values, u0 and u1 being adjusted in same proportion as in
# https://www.osti.gov/servlets/purl/1884890/ (not suggested, just example)
u0, u1 = 25.0*0.86, 6.84*0.88
sky_view = 1.0
emissivity = 0.88

temp_model_params = {'u0': u0, 'u1': u1, 'sky_view': sky_view,
'emissivity': emissivity}
system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params)
mocker.spy(temperature, 'faiman_rad')
temps = 25
irrads = 1000
winds = 1
out = system.get_cell_temperature(irrads, temps, winds, ir_down=ir_down,
model='faiman_rad')
temperature.faiman_rad.assert_called_once_with(irrads, temps, winds,
ir_down, u0, u1,
sky_view, emissivity)
assert_allclose(out, 48.6, atol=1e-1)


def test_PVSystem_ross_celltemp(mocker):
# example value (could use equivalent noct as alternative input)
k = 0.0208 # free-standing system

temp_model_params = {'k': k}
system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params)
mocker.spy(temperature, 'ross')
temps = 25
irrads = 1000
winds = None
out = system.get_cell_temperature(irrads, temps, winds, model='ross')
temperature.ross.assert_called_once_with(irrads, temps, k=k)
assert_allclose(out, 45.8, atol=1e-1)


def test_PVSystem_noct_celltemp(mocker):
poa_global, temp_air, wind_speed, noct, module_efficiency = (
1000., 25., 1., 45., 0.2)
Expand Down