From 4143e2ae93ee6f4970c566c7823c141cc42a4cf0 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Thu, 18 Dec 2025 23:11:55 +0000 Subject: [PATCH 01/27] Adds faiman_rad and ross models to get_cell_temperature(). --- pvlib/pvsystem.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index fe85359b99..76f53b3e74 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1217,11 +1217,12 @@ 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 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. @@ -1267,6 +1268,12 @@ 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 = temperature.faiman_rad + required = () + optional = _build_kwargs(['ir_down','u0','u1', + 'sky_view','emissivity'], + self.temperature_model_parameters) elif model == 'fuentes': func = temperature.fuentes required = _build_tcell_args(['noct_installed']) @@ -1283,11 +1290,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': + 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): From 70bb9483bb890433b3afb2142d84258afaaf5f2e Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Thu, 18 Dec 2025 23:24:47 +0000 Subject: [PATCH 02/27] Addresses flake8 formatting issues. --- pvlib/pvsystem.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 76f53b3e74..98e30a168a 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1271,8 +1271,8 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, elif model == 'faiman_rad': func = temperature.faiman_rad required = () - optional = _build_kwargs(['ir_down','u0','u1', - 'sky_view','emissivity'], + optional = _build_kwargs(['ir_down', 'u0', 'u1', + 'sky_view', 'emissivity'], self.temperature_model_parameters) elif model == 'fuentes': func = temperature.fuentes @@ -1294,8 +1294,8 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, func = temperature.ross required = () # either noct or k must be defined - optional = _build_kwargs(['noct','k'], - self.temperature_model_parameters) + optional = _build_kwargs(['noct', 'k'], + self.temperature_model_parameters) else: raise ValueError(f'{model} is not a valid cell temperature model') From acc43b45c60bade542ee36c53418c846468e5434 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Sun, 21 Dec 2025 10:44:12 +0000 Subject: [PATCH 03/27] Updates contribution md file. --- docs/sphinx/source/whatsnew/v0.13.2.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sphinx/source/whatsnew/v0.13.2.rst b/docs/sphinx/source/whatsnew/v0.13.2.rst index b6170a6053..85aab28275 100644 --- a/docs/sphinx/source/whatsnew/v0.13.2.rst +++ b/docs/sphinx/source/whatsnew/v0.13.2.rst @@ -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`) +* Includes `ross` and `faiman_rad` in the allowed models within + `pvlib.pvsystem.PVSystem.get_cell_temperature` (:issue:`2625`, :pull:`2631`) Documentation ~~~~~~~~~~~~~ @@ -87,3 +89,4 @@ Contributors * Anton Driesse (:ghuser:`adriesse`) * Rajiv Daxini (:ghuser:`RDaxini`) * Kevin Anderson (:ghuser:`kandersolar`) +* Rodrigo Amaro e Silva (:ghuser:`ramaroesilva`) \ No newline at end of file From 69c7f434a207d17972795e930aeeed8c72d06f59 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Sun, 21 Dec 2025 13:52:52 +0000 Subject: [PATCH 04/27] Adds test for ross option, placeholder for faiman_rad. --- tests/test_pvsystem.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 4fbd782e65..852e84d602 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -494,6 +494,40 @@ def test_PVSystem_faiman_celltemp(mocker): assert_allclose(out, 56.4, atol=1e-1) +def test_PVSystem_faiman_rad_celltemp(mocker): + # default values (need to account for a certain ir_down and adjust u0, u1) + ir_down = None + u0, u1 = 25.0, 6.84 + sky_view = 1.0 + emissivity = 0.88 + + temp_model_params = {'ir_down': ir_down, '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, model='faiman_rad') + temperature.faiman_rad.assert_called_once_with(irrads, temps, winds, u0, u1, + sky_view, emissivity) + assert_allclose(out, 56.4, atol=1e-1) + + +def test_PVSystem_ross_celltemp(mocker): + # example value + 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 + out = system.get_cell_temperature(irrads, temps, model='ross') + temperature.faiman_rad.assert_called_once_with(irrads, temps, 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) From 5998725732f7af84c7778f41d483604f6155c837 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Sun, 21 Dec 2025 13:55:57 +0000 Subject: [PATCH 05/27] Corrects flake8 issues. --- tests/test_pvsystem.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 852e84d602..c103f9cff5 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -500,7 +500,7 @@ def test_PVSystem_faiman_rad_celltemp(mocker): u0, u1 = 25.0, 6.84 sky_view = 1.0 emissivity = 0.88 - + temp_model_params = {'ir_down': ir_down, 'u0': u0, 'u1': u1, 'sky_view': sky_view, 'emissivity': emissivity} system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) @@ -509,15 +509,16 @@ def test_PVSystem_faiman_rad_celltemp(mocker): irrads = 1000 winds = 1 out = system.get_cell_temperature(irrads, temps, winds, model='faiman_rad') - temperature.faiman_rad.assert_called_once_with(irrads, temps, winds, u0, u1, + temperature.faiman_rad.assert_called_once_with(irrads, temps, winds, + u0, u1, sky_view, emissivity) assert_allclose(out, 56.4, atol=1e-1) def test_PVSystem_ross_celltemp(mocker): # example value - k = 0.0208 # free-standing system - + k = 0.0208 # free-standing system + temp_model_params = {'k': k} system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) mocker.spy(temperature, 'ross') From 61e8163be1a86c9bd455eedff6b040be242e6b83 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Sun, 21 Dec 2025 14:06:06 +0000 Subject: [PATCH 06/27] Adds missing wind_speed=None when testing get_cell_temperature for ross. --- tests/test_pvsystem.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index c103f9cff5..a135932631 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -524,7 +524,8 @@ def test_PVSystem_ross_celltemp(mocker): mocker.spy(temperature, 'ross') temps = 25 irrads = 1000 - out = system.get_cell_temperature(irrads, temps, model='ross') + winds = None + out = system.get_cell_temperature(irrads, temps, winds, model='ross') temperature.faiman_rad.assert_called_once_with(irrads, temps, k) assert_allclose(out, 45.8, atol=1e-1) From 22f94a786d8b0056d1e6839bbaafd2359f1d09b6 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Sun, 21 Dec 2025 15:34:56 +0000 Subject: [PATCH 07/27] Corrects wrong function naming. --- tests/test_pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index a135932631..a4e7dcc285 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -526,7 +526,7 @@ def test_PVSystem_ross_celltemp(mocker): irrads = 1000 winds = None out = system.get_cell_temperature(irrads, temps, winds, model='ross') - temperature.faiman_rad.assert_called_once_with(irrads, temps, k) + temperature.ross.assert_called_once_with(irrads, temps, k) assert_allclose(out, 45.8, atol=1e-1) From aaa52e32255b7e004c4dffca22aebd51b6226ce9 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Sun, 21 Dec 2025 15:45:58 +0000 Subject: [PATCH 08/27] Adds missing input to functions within tests. --- tests/test_pvsystem.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index a4e7dcc285..f9619b79db 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -510,7 +510,7 @@ def test_PVSystem_faiman_rad_celltemp(mocker): winds = 1 out = system.get_cell_temperature(irrads, temps, winds, model='faiman_rad') temperature.faiman_rad.assert_called_once_with(irrads, temps, winds, - u0, u1, + ir_down, u0, u1, sky_view, emissivity) assert_allclose(out, 56.4, atol=1e-1) @@ -518,6 +518,7 @@ def test_PVSystem_faiman_rad_celltemp(mocker): def test_PVSystem_ross_celltemp(mocker): # example value k = 0.0208 # free-standing system + noct = None # function expects only one of two, the other as None temp_model_params = {'k': k} system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) @@ -526,7 +527,7 @@ def test_PVSystem_ross_celltemp(mocker): irrads = 1000 winds = None out = system.get_cell_temperature(irrads, temps, winds, model='ross') - temperature.ross.assert_called_once_with(irrads, temps, k) + temperature.ross.assert_called_once_with(irrads, temps, noct, k) assert_allclose(out, 45.8, atol=1e-1) From c6d8ab9601cee7cb1cb579f652452d81d73b6368 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Sun, 21 Dec 2025 15:53:25 +0000 Subject: [PATCH 09/27] Corrects test + flake8 issue. --- tests/test_pvsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index f9619b79db..54a7b3759b 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -518,7 +518,7 @@ def test_PVSystem_faiman_rad_celltemp(mocker): def test_PVSystem_ross_celltemp(mocker): # example value k = 0.0208 # free-standing system - noct = None # function expects only one of two, the other as None + noct = None # function expects only one of two, the other as None temp_model_params = {'k': k} system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) @@ -527,7 +527,7 @@ def test_PVSystem_ross_celltemp(mocker): irrads = 1000 winds = None out = system.get_cell_temperature(irrads, temps, winds, model='ross') - temperature.ross.assert_called_once_with(irrads, temps, noct, k) + temperature.ross.assert_called_once_with(irrads, temps, k=k) assert_allclose(out, 45.8, atol=1e-1) From 78eb4c3e64a0f2ae8beb012212c462074ab828a9 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Sun, 21 Dec 2025 15:56:00 +0000 Subject: [PATCH 10/27] Minor update in documentation + flake8 issue. --- tests/test_pvsystem.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 54a7b3759b..c373216c43 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -516,10 +516,9 @@ def test_PVSystem_faiman_rad_celltemp(mocker): def test_PVSystem_ross_celltemp(mocker): - # example value + # example value (could use equivalent noct as alternative input) k = 0.0208 # free-standing system - noct = None # function expects only one of two, the other as None - + temp_model_params = {'k': k} system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) mocker.spy(temperature, 'ross') From 402b53caab321d11e0d68d2764c54b0e2b063568 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Sun, 21 Dec 2025 15:57:12 +0000 Subject: [PATCH 11/27] Another flake8 issue handled. --- tests/test_pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index c373216c43..3445253370 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -518,7 +518,7 @@ def test_PVSystem_faiman_rad_celltemp(mocker): 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') From 9461e013af32d00cab0aa3d23df609e111111344 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Mon, 22 Dec 2025 08:09:02 +0000 Subject: [PATCH 12/27] Improve faiman_rad test and completes get_cell_temperature doc. --- pvlib/pvsystem.py | 8 +++++--- tests/test_pvsystem.py | 9 +++++---- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 98e30a168a..22da2f5f2a 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -431,7 +431,8 @@ 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. @@ -1236,8 +1237,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 ----- diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 3445253370..1cf4f87ed3 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -495,9 +495,10 @@ def test_PVSystem_faiman_celltemp(mocker): def test_PVSystem_faiman_rad_celltemp(mocker): - # default values (need to account for a certain ir_down and adjust u0, u1) - ir_down = None - u0, u1 = 25.0, 6.84 + 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 @@ -512,7 +513,7 @@ def test_PVSystem_faiman_rad_celltemp(mocker): temperature.faiman_rad.assert_called_once_with(irrads, temps, winds, ir_down, u0, u1, sky_view, emissivity) - assert_allclose(out, 56.4, atol=1e-1) + assert_allclose(out, 48.6, atol=1e-1) def test_PVSystem_ross_celltemp(mocker): From 90350669fb02d0b7fe64394169c8cb25bb4a9427 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Mon, 22 Dec 2025 08:12:05 +0000 Subject: [PATCH 13/27] Addresses flake8. --- tests/test_pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 1cf4f87ed3..109db1859c 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -498,7 +498,7 @@ 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 + u0, u1 = 25.0*0.86, 6.84*0.88 sky_view = 1.0 emissivity = 0.88 From 6f5bc0d1efdb54bea50e146369ea2c970078cb45 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Mon, 22 Dec 2025 16:04:44 +0000 Subject: [PATCH 14/27] Update whatsnew doc. --- docs/sphinx/source/whatsnew/v0.13.2.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.13.2.rst b/docs/sphinx/source/whatsnew/v0.13.2.rst index 85aab28275..72dce0efb0 100644 --- a/docs/sphinx/source/whatsnew/v0.13.2.rst +++ b/docs/sphinx/source/whatsnew/v0.13.2.rst @@ -46,8 +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`) -* Includes `ross` and `faiman_rad` in the allowed models within - `pvlib.pvsystem.PVSystem.get_cell_temperature` (:issue:`2625`, :pull:`2631`) +* Include `ross` and `faiman_rad` in the allowed models within + :py:meth:`pvlib.pvsystem.PVSystem.get_cell_temperature` (:issue:`2625`, :pull:`2631`) Documentation ~~~~~~~~~~~~~ From a536cb27745f38047bbd4d613a6c192c42166bc7 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Mon, 22 Dec 2025 16:23:47 +0000 Subject: [PATCH 15/27] Defines ir_down as weather variable Within faiman_rad. --- pvlib/pvsystem.py | 23 +++++++++++++++++------ tests/test_pvsystem.py | 7 ++++--- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 22da2f5f2a..cfc4f0d6b4 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -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``. @@ -437,6 +437,10 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, 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 ------- @@ -460,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 ) ) @@ -1205,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``. @@ -1229,6 +1235,10 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, 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 @@ -1271,7 +1281,8 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, optional = _build_kwargs(['u0', 'u1'], self.temperature_model_parameters) elif model == 'faiman_rad': - func = temperature.faiman_rad + func = functools.partial(temperature.faiman_rad, + ir_down=ir_down) required = () optional = _build_kwargs(['ir_down', 'u0', 'u1', 'sky_view', 'emissivity'], diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 109db1859c..50e338fa00 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -502,14 +502,15 @@ def test_PVSystem_faiman_rad_celltemp(mocker): sky_view = 1.0 emissivity = 0.88 - temp_model_params = {'ir_down': ir_down, 'u0': u0, 'u1': u1, - 'sky_view': sky_view, 'emissivity': emissivity} + 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, model='faiman_rad') + out = system.get_cell_temperature(irrads, temps, winds, ir_down, + model='faiman_rad') temperature.faiman_rad.assert_called_once_with(irrads, temps, winds, ir_down, u0, u1, sky_view, emissivity) From bc625789412e211ab1a1ec7d28e75fd22d736777 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Mon, 22 Dec 2025 16:29:06 +0000 Subject: [PATCH 16/27] Handles flake8 issues. --- pvlib/pvsystem.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index cfc4f0d6b4..d307c57613 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -437,9 +437,9 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, 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 + Downwelling infrared radiation from the sky, measured on a horizontal surface in W/m^2. Only used in ``'faiman_rad'`` model. Returns @@ -1236,7 +1236,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, Only used for some models. ir_down: numeric, optional - Downwelling infrared radiation from the sky, measured on a + Downwelling infrared radiation from the sky, measured on a horizontal surface in W/m^2. Only used in ``'faiman_rad'`` model. Returns From 7612e56f9a5b28ab82fb5b909241de26f77923e2 Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Mon, 22 Dec 2025 22:55:29 +0000 Subject: [PATCH 17/27] Corrects bug in test. --- tests/test_pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 50e338fa00..ab74566c92 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -509,7 +509,7 @@ def test_PVSystem_faiman_rad_celltemp(mocker): temps = 25 irrads = 1000 winds = 1 - out = system.get_cell_temperature(irrads, temps, winds, ir_down, + 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, From 534e2f6f43cd825ffce5d03993d9af2123667acd Mon Sep 17 00:00:00 2001 From: ramaroesilva Date: Mon, 22 Dec 2025 23:00:26 +0000 Subject: [PATCH 18/27] Handles flake8. --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index d307c57613..bf4dcb685a 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -437,7 +437,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, 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. From 628c15b419da483d7a1ab520cdbd17acba588223 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 31 Mar 2026 15:53:23 -0700 Subject: [PATCH 19/27] move whatsnew note --- docs/sphinx/source/whatsnew/v0.15.1.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.15.1.rst b/docs/sphinx/source/whatsnew/v0.15.1.rst index 75ab242c94..d6d5625405 100644 --- a/docs/sphinx/source/whatsnew/v0.15.1.rst +++ b/docs/sphinx/source/whatsnew/v0.15.1.rst @@ -29,7 +29,8 @@ Enhancements ~~~~~~~~~~~~ * Use ``k`` and ``cap_adjustment`` from :py:func:`pvlib.pvsystem.Array.module_parameters` in :py:func:`pvlib.pvsystem.PVSystem.pvwatts_dc` (:issue:`2714`, :pull:`2715`) - +* Include `ross` and `faiman_rad` in the allowed models within + :py:meth:`pvlib.pvsystem.PVSystem.get_cell_temperature` (:issue:`2625`, :pull:`2631`) Documentation ~~~~~~~~~~~~~ @@ -84,4 +85,4 @@ Contributors * Rohan Saxena (:ghuser:`r0hansaxena`) * Marco Fumagalli (:ghuser:`fuma900`) * Jean-Baptiste Pasquier (:ghuser:`pasquierjb`) - +* Rodrigo Amaro e Silva (:ghuser:`ramaroesilva`) From 747f7b993e9191cbea939ffc0660510fa0e5b266 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 31 Mar 2026 15:54:27 -0700 Subject: [PATCH 20/27] undo edit to v0.14.0.rst --- docs/sphinx/source/whatsnew/v0.14.0.rst | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.14.0.rst b/docs/sphinx/source/whatsnew/v0.14.0.rst index ab29570360..4d7c4b1a50 100644 --- a/docs/sphinx/source/whatsnew/v0.14.0.rst +++ b/docs/sphinx/source/whatsnew/v0.14.0.rst @@ -41,8 +41,6 @@ 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 ~~~~~~~~~~~~~ @@ -74,7 +72,6 @@ Contributors * Anton Driesse (:ghuser:`adriesse`) * Rajiv Daxini (:ghuser:`RDaxini`) * Kevin Anderson (:ghuser:`kandersolar`) -* Rodrigo Amaro e Silva (:ghuser:`ramaroesilva`) * Mark Mikofski (:ghuser:`mikofski`) * Will Holmgren (:ghuser:`wholmgren`) * Ioannis Sifnaios (:ghuser:`IoannisSifnaios`) From 080a84b2323d5653bc4e5a3f585043f8fa27b61b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Tue, 31 Mar 2026 15:55:50 -0700 Subject: [PATCH 21/27] Apply suggestions from code review Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com> Co-authored-by: Kevin Anderson --- pvlib/pvsystem.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 33878ff85f..54bfc1bdde 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -438,7 +438,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, The irradiance that is converted to photocurrent in W/m^2. Only used for some models. - ir_down: numeric, optional + ir_down: numeric or tuple of numeric, optional Downwelling infrared radiation from the sky, measured on a horizontal surface in W/m^2. Only used in ``'faiman_rad'`` model. @@ -1228,7 +1228,8 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, Ambient dry bulb temperature [C] wind_speed : numeric - Wind speed [m/s], although can be ``None`` for ``'ross'`` model + Wind speed [m/s] + When ``model='ross'``, this input is ignored model : str Supported models include ``'sapm'``, ``'pvsyst'``, @@ -1288,7 +1289,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, func = functools.partial(temperature.faiman_rad, ir_down=ir_down) required = () - optional = _build_kwargs(['ir_down', 'u0', 'u1', + optional = _build_kwargs(['u0', 'u1', 'sky_view', 'emissivity'], self.temperature_model_parameters) elif model == 'fuentes': From d55cf78b3980f85af089b10773fe3695c08c9461 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 1 Apr 2026 09:45:35 -0700 Subject: [PATCH 22/27] replace ir_down with longwave_down --- pvlib/pvsystem.py | 20 +++++++++++--------- tests/test_pvsystem.py | 7 ++++--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 54bfc1bdde..465b7d83dc 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -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, ir_down=None): + effective_irradiance=None, longwave_down=None): """ Determine cell temperature using the method specified by ``model``. @@ -438,7 +438,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, The irradiance that is converted to photocurrent in W/m^2. Only used for some models. - ir_down: numeric or tuple of numeric, optional + longwave_down: numeric or tuple of numeric, optional Downwelling infrared radiation from the sky, measured on a horizontal surface in W/m^2. Only used in ``'faiman_rad'`` model. @@ -464,16 +464,18 @@ 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) + longwave_down = self._validate_per_array(longwave_down, + system_wide=True) return tuple( array.get_cell_temperature(poa_global, temp_air, wind_speed, - model, effective_irradiance, ir_down) + model, effective_irradiance, + longwave_down) for array, poa_global, temp_air, wind_speed, effective_irradiance, - ir_down + longwave_down in zip( self.arrays, poa_global, temp_air, wind_speed, - effective_irradiance, ir_down + effective_irradiance, longwave_down ) ) @@ -1215,7 +1217,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, ir_down=None): + effective_irradiance=None, longwave_down=None): """ Determine cell temperature using the method specified by ``model``. @@ -1240,7 +1242,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, The irradiance that is converted to photocurrent in W/m^2. Only used for some models. - ir_down: numeric, optional + longwave_down: numeric, optional Downwelling infrared radiation from the sky, measured on a horizontal surface in W/m^2. Only used in ``'faiman_rad'`` model. @@ -1287,7 +1289,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, self.temperature_model_parameters) elif model == 'faiman_rad': func = functools.partial(temperature.faiman_rad, - ir_down=ir_down) + longwave_down=longwave_down) required = () optional = _build_kwargs(['u0', 'u1', 'sky_view', 'emissivity'], diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 48b7504bcd..cdd21d17e3 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -495,7 +495,7 @@ def test_PVSystem_faiman_celltemp(mocker): def test_PVSystem_faiman_rad_celltemp(mocker): - ir_down = 50 # arbitrary value + longwave_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 @@ -509,10 +509,11 @@ def test_PVSystem_faiman_rad_celltemp(mocker): temps = 25 irrads = 1000 winds = 1 - out = system.get_cell_temperature(irrads, temps, winds, ir_down=ir_down, + out = system.get_cell_temperature(irrads, temps, winds, + longwave_down=longwave_down, model='faiman_rad') temperature.faiman_rad.assert_called_once_with(irrads, temps, winds, - ir_down, u0, u1, + longwave_down, u0, u1, sky_view, emissivity) assert_allclose(out, 48.6, atol=1e-1) From 1398207dbd5fe8ad254f9f82f05d1473c08d1838 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 1 Apr 2026 11:41:24 -0700 Subject: [PATCH 23/27] finish replacing ir_down with longwave_down --- pvlib/temperature.py | 18 +++++++++--------- tests/test_pvsystem.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index cb487ee77b..c601590e96 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -509,7 +509,7 @@ def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84): return temp_air + temp_difference -def faiman_rad(poa_global, temp_air, wind_speed=1.0, ir_down=None, +def faiman_rad(poa_global, temp_air, wind_speed=1.0, longwave_down=None, u0=25.0, u1=6.84, sky_view=1.0, emissivity=0.88): r''' Calculate cell or module temperature using the Faiman model augmented @@ -534,7 +534,7 @@ def faiman_rad(poa_global, temp_air, wind_speed=1.0, ir_down=None, factor was determined. The default value 1.0 m/s is the wind speed at module height used to determine NOCT. [m/s] - ir_down : numeric, default 0.0 + longwave_down : numeric, default 0.0 Downwelling infrared radiation from the sky, measured on a horizontal surface. [W/m^2] @@ -572,11 +572,11 @@ def faiman_rad(poa_global, temp_air, wind_speed=1.0, ir_down=None, are vectors they must be the same length. When only irradiance, air temperature and wind speed inputs are provided - (`ir_down` is `None`) this function calculates the same device temperature - as the original faiman model. When down-welling long-wave radiation data - are provided as well (`ir_down` is not None) the default u0 and u1 values - from the original model should not be used because a portion of the - radiative losses would be double-counted. + (`longwave_down` is `None`) this function calculates the same device + temperature as the original faiman model. When downwelling long-wave + radiation data are provided (`longwave_down` is not None) the + default `u0` and `u1` values from the original model should not be used + because a portion of the radiative losses would be double-counted. References ---------- @@ -606,11 +606,11 @@ def faiman_rad(poa_global, temp_air, wind_speed=1.0, ir_down=None, abs_zero = -273.15 sigma = scipy.constants.Stefan_Boltzmann - if ir_down is None: + if longwave_down is None: qrad_sky = 0.0 else: ir_up = sigma * ((temp_air - abs_zero)**4) - qrad_sky = emissivity * sky_view * (ir_up - ir_down) + qrad_sky = emissivity * sky_view * (ir_up - longwave_down) heat_input = poa_global - qrad_sky total_loss_factor = u0 + u1 * wind_speed diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index cdd21d17e3..46c07d5b81 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -510,8 +510,8 @@ def test_PVSystem_faiman_rad_celltemp(mocker): irrads = 1000 winds = 1 out = system.get_cell_temperature(irrads, temps, winds, - longwave_down=longwave_down, - model='faiman_rad') + model='faiman_rad', + longwave_down=longwave_down) temperature.faiman_rad.assert_called_once_with(irrads, temps, winds, longwave_down, u0, u1, sky_view, emissivity) From 72b983918db8a3a80f26e5c8a4af7add7d448dac Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 1 Apr 2026 11:52:35 -0700 Subject: [PATCH 24/27] undo edits to temperature.py --- pvlib/temperature.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pvlib/temperature.py b/pvlib/temperature.py index c601590e96..cb487ee77b 100644 --- a/pvlib/temperature.py +++ b/pvlib/temperature.py @@ -509,7 +509,7 @@ def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84): return temp_air + temp_difference -def faiman_rad(poa_global, temp_air, wind_speed=1.0, longwave_down=None, +def faiman_rad(poa_global, temp_air, wind_speed=1.0, ir_down=None, u0=25.0, u1=6.84, sky_view=1.0, emissivity=0.88): r''' Calculate cell or module temperature using the Faiman model augmented @@ -534,7 +534,7 @@ def faiman_rad(poa_global, temp_air, wind_speed=1.0, longwave_down=None, factor was determined. The default value 1.0 m/s is the wind speed at module height used to determine NOCT. [m/s] - longwave_down : numeric, default 0.0 + ir_down : numeric, default 0.0 Downwelling infrared radiation from the sky, measured on a horizontal surface. [W/m^2] @@ -572,11 +572,11 @@ def faiman_rad(poa_global, temp_air, wind_speed=1.0, longwave_down=None, are vectors they must be the same length. When only irradiance, air temperature and wind speed inputs are provided - (`longwave_down` is `None`) this function calculates the same device - temperature as the original faiman model. When downwelling long-wave - radiation data are provided (`longwave_down` is not None) the - default `u0` and `u1` values from the original model should not be used - because a portion of the radiative losses would be double-counted. + (`ir_down` is `None`) this function calculates the same device temperature + as the original faiman model. When down-welling long-wave radiation data + are provided as well (`ir_down` is not None) the default u0 and u1 values + from the original model should not be used because a portion of the + radiative losses would be double-counted. References ---------- @@ -606,11 +606,11 @@ def faiman_rad(poa_global, temp_air, wind_speed=1.0, longwave_down=None, abs_zero = -273.15 sigma = scipy.constants.Stefan_Boltzmann - if longwave_down is None: + if ir_down is None: qrad_sky = 0.0 else: ir_up = sigma * ((temp_air - abs_zero)**4) - qrad_sky = emissivity * sky_view * (ir_up - longwave_down) + qrad_sky = emissivity * sky_view * (ir_up - ir_down) heat_input = poa_global - qrad_sky total_loss_factor = u0 + u1 * wind_speed From d9a808de4f0023d7177f66c0a5b7c1bc1b83dd64 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 1 Apr 2026 11:55:12 -0700 Subject: [PATCH 25/27] temperature.faiman_rad doesn't use longwave_down yet --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 465b7d83dc..f839126da5 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -1289,7 +1289,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, self.temperature_model_parameters) elif model == 'faiman_rad': func = functools.partial(temperature.faiman_rad, - longwave_down=longwave_down) + ir_down=longwave_down) required = () optional = _build_kwargs(['u0', 'u1', 'sky_view', 'emissivity'], From 82fa5030ec714c5b2c5c2e18e27e958933146b80 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Wed, 1 Apr 2026 12:46:27 -0700 Subject: [PATCH 26/27] Apply suggestions from code review Co-authored-by: Anton Driesse --- pvlib/pvsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index f839126da5..e8d3c1d6e9 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -439,7 +439,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, Only used for some models. longwave_down: numeric or tuple of numeric, optional - Downwelling infrared radiation from the sky, measured on a + Downwelling long-wave radiation from the sky, measured on a horizontal surface in W/m^2. Only used in ``'faiman_rad'`` model. Returns @@ -1243,7 +1243,7 @@ def get_cell_temperature(self, poa_global, temp_air, wind_speed, model, Only used for some models. longwave_down: numeric, optional - Downwelling infrared radiation from the sky, measured on a + Downwelling long-wave radiation from the sky, measured on a horizontal surface in W/m^2. Only used in ``'faiman_rad'`` model. Returns From 12e27ffb15a28375d8ba5d133fd7ee2516d0d64b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Thu, 2 Apr 2026 08:08:51 -0700 Subject: [PATCH 27/27] Update tests/test_pvsystem.py Co-authored-by: Echedey Luis <80125792+echedey-ls@users.noreply.github.com> --- tests/test_pvsystem.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 46c07d5b81..9257b769f3 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -524,12 +524,12 @@ def test_PVSystem_ross_celltemp(mocker): temp_model_params = {'k': k} system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) - mocker.spy(temperature, 'ross') + m = 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) + m.assert_called_once_with(irrads, temps, k=k) assert_allclose(out, 45.8, atol=1e-1)