Skip to content

Commit e2b7f19

Browse files
kandersolarcwhansewholmgren
authored
Expose temperature.fuentes in PVSystem and ModelChain (#1073)
* add methods, some tests * some more tests * api.rst and whatsnew * add surface_tilt override, note, and test * Update pvlib/pvsystem.py Co-authored-by: Cliff Hansen <[email protected]> * reflow docstring text for 80 char limit * Update docs/sphinx/source/whatsnew/v0.8.1.rst Co-authored-by: Will Holmgren <[email protected]> Co-authored-by: Cliff Hansen <[email protected]> Co-authored-by: Will Holmgren <[email protected]>
1 parent b105021 commit e2b7f19

File tree

6 files changed

+133
-5
lines changed

6 files changed

+133
-5
lines changed

docs/sphinx/source/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -627,6 +627,7 @@ ModelChain model definitions.
627627
modelchain.ModelChain.sapm_temp
628628
modelchain.ModelChain.pvsyst_temp
629629
modelchain.ModelChain.faiman_temp
630+
modelchain.ModelChain.fuentes_temp
630631
modelchain.ModelChain.pvwatts_losses
631632
modelchain.ModelChain.no_extra_losses
632633

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Deprecations
1313

1414
Enhancements
1515
~~~~~~~~~~~~
16+
* Create :py:func:`~pvlib.pvsystem.PVSystem.fuentes_celltemp` and add ``temperature_model='fuentes'``
17+
option to :py:class:`~pvlib.modelchain.ModelChain`. (:pull:`1042`) (:issue:`1073`)
1618
* Added :py:func:`pvlib.temperature.ross` for cell temperature modeling using
1719
only NOCT. (:pull:`1045`)
1820

pvlib/modelchain.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -323,9 +323,9 @@ class ModelChain:
323323
as the first argument to a user-defined function.
324324
325325
temperature_model: None, str or function, default None
326-
Valid strings are 'sapm', 'pvsyst', and 'faiman'. The ModelChain
327-
instance will be passed as the first argument to a user-defined
328-
function.
326+
Valid strings are 'sapm', 'pvsyst', 'faiman', and 'fuentes'.
327+
The ModelChain instance will be passed as the first argument to a
328+
user-defined function.
329329
330330
losses_model: str or function, default 'no_loss'
331331
Valid strings are 'pvwatts', 'no_loss'. The ModelChain instance
@@ -866,6 +866,8 @@ def temperature_model(self, model):
866866
self._temperature_model = self.pvsyst_temp
867867
elif model == 'faiman':
868868
self._temperature_model = self.faiman_temp
869+
elif model == 'fuentes':
870+
self._temperature_model = self.fuentes_temp
869871
else:
870872
raise ValueError(model + ' is not a valid temperature model')
871873
# check system.temperature_model_parameters for consistency
@@ -891,6 +893,8 @@ def infer_temperature_model(self):
891893
return self.pvsyst_temp
892894
elif {'u0', 'u1'} <= params:
893895
return self.faiman_temp
896+
elif {'noct_installed'} <= params:
897+
return self.fuentes_temp
894898
else:
895899
raise ValueError('could not infer temperature model from '
896900
'system.temperature_module_parameters {}.'
@@ -914,6 +918,12 @@ def faiman_temp(self):
914918
self.weather['wind_speed'])
915919
return self
916920

921+
def fuentes_temp(self):
922+
self.cell_temperature = self.system.fuentes_celltemp(
923+
self.total_irrad['poa_global'], self.weather['temp_air'],
924+
self.weather['wind_speed'])
925+
return self
926+
917927
@property
918928
def losses_model(self):
919929
return self._losses_model

pvlib/pvsystem.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -609,6 +609,46 @@ def faiman_celltemp(self, poa_global, temp_air, wind_speed=1.0):
609609
return temperature.faiman(poa_global, temp_air, wind_speed,
610610
**kwargs)
611611

612+
def fuentes_celltemp(self, poa_global, temp_air, wind_speed):
613+
"""
614+
Use :py:func:`temperature.fuentes` to calculate cell temperature.
615+
616+
Parameters
617+
----------
618+
poa_global : pandas Series
619+
Total incident irradiance [W/m^2]
620+
621+
temp_air : pandas Series
622+
Ambient dry bulb temperature [C]
623+
624+
wind_speed : pandas Series
625+
Wind speed [m/s]
626+
627+
Returns
628+
-------
629+
temperature_cell : pandas Series
630+
The modeled cell temperature [C]
631+
632+
Notes
633+
-----
634+
The Fuentes thermal model uses the module surface tilt for convection
635+
modeling. The SAM implementation of PVWatts hardcodes the surface tilt
636+
value at 30 degrees, ignoring whatever value is used for irradiance
637+
transposition. This method defaults to using ``self.surface_tilt``, but
638+
if you want to match the PVWatts behavior, you can override it by
639+
including a ``surface_tilt`` value in ``temperature_model_parameters``.
640+
"""
641+
# default to using the PVSystem attribute, but allow user to
642+
# override with a custom surface_tilt value
643+
kwargs = {'surface_tilt': self.surface_tilt}
644+
temp_model_kwargs = _build_kwargs([
645+
'noct_installed', 'module_height', 'wind_height', 'emissivity',
646+
'absorption', 'surface_tilt', 'module_width', 'module_length'],
647+
self.temperature_model_parameters)
648+
kwargs.update(temp_model_kwargs)
649+
return temperature.fuentes(poa_global, temp_air, wind_speed,
650+
**kwargs)
651+
612652
def first_solar_spectral_loss(self, pw, airmass_absolute):
613653

614654
"""

pvlib/tests/test_modelchain.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,18 @@ def pvwatts_dc_pvwatts_ac_pvsyst_temp_system():
138138
return system
139139

140140

141+
@pytest.fixture(scope="function")
142+
def pvwatts_dc_pvwatts_ac_fuentes_temp_system():
143+
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
144+
temp_model_params = {'noct_installed': 45}
145+
inverter_parameters = {'pdc0': 220, 'eta_inv_nom': 0.95}
146+
system = PVSystem(surface_tilt=32.2, surface_azimuth=180,
147+
module_parameters=module_parameters,
148+
temperature_model_parameters=temp_model_params,
149+
inverter_parameters=inverter_parameters)
150+
return system
151+
152+
141153
@pytest.fixture(scope="function")
142154
def system_no_aoi(cec_module_cs5p_220m, sapm_temperature_cs5p_220m,
143155
cec_inverter_parameters):
@@ -317,6 +329,23 @@ def test_run_model_with_weather_faiman_temp(sapm_dc_snl_ac_system, location,
317329
assert not mc.ac.empty
318330

319331

332+
def test_run_model_with_weather_fuentes_temp(sapm_dc_snl_ac_system, location,
333+
weather, mocker):
334+
weather['wind_speed'] = 5
335+
weather['temp_air'] = 10
336+
sapm_dc_snl_ac_system.temperature_model_parameters = {
337+
'noct_installed': 45
338+
}
339+
mc = ModelChain(sapm_dc_snl_ac_system, location)
340+
mc.temperature_model = 'fuentes'
341+
m_fuentes = mocker.spy(sapm_dc_snl_ac_system, 'fuentes_celltemp')
342+
mc.run_model(weather)
343+
assert m_fuentes.call_count == 1
344+
assert_series_equal(m_fuentes.call_args[0][1], weather['temp_air'])
345+
assert_series_equal(m_fuentes.call_args[0][2], weather['wind_speed'])
346+
assert not mc.ac.empty
347+
348+
320349
def test_run_model_tracker(sapm_dc_snl_ac_system, location, weather, mocker):
321350
system = SingleAxisTracker(
322351
module_parameters=sapm_dc_snl_ac_system.module_parameters,
@@ -479,14 +508,16 @@ def test_infer_spectral_model(location, sapm_dc_snl_ac_system,
479508

480509

481510
@pytest.mark.parametrize('temp_model', [
482-
'sapm_temp', 'faiman_temp', 'pvsyst_temp'])
511+
'sapm_temp', 'faiman_temp', 'pvsyst_temp', 'fuentes_temp'])
483512
def test_infer_temp_model(location, sapm_dc_snl_ac_system,
484513
pvwatts_dc_pvwatts_ac_pvsyst_temp_system,
485514
pvwatts_dc_pvwatts_ac_faiman_temp_system,
515+
pvwatts_dc_pvwatts_ac_fuentes_temp_system,
486516
temp_model):
487517
dc_systems = {'sapm_temp': sapm_dc_snl_ac_system,
488518
'pvsyst_temp': pvwatts_dc_pvwatts_ac_pvsyst_temp_system,
489-
'faiman_temp': pvwatts_dc_pvwatts_ac_faiman_temp_system}
519+
'faiman_temp': pvwatts_dc_pvwatts_ac_faiman_temp_system,
520+
'fuentes_temp': pvwatts_dc_pvwatts_ac_fuentes_temp_system}
490521
system = dc_systems[temp_model]
491522
mc = ModelChain(system, location,
492523
orientation_strategy='None', aoi_model='physical',

pvlib/tests/test_pvsystem.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,50 @@ def test_PVSystem_faiman_celltemp(mocker):
360360
assert_allclose(out, 56.4, atol=1)
361361

362362

363+
def test_PVSystem_fuentes_celltemp(mocker):
364+
noct_installed = 45
365+
temp_model_params = {'noct_installed': noct_installed}
366+
system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params)
367+
spy = mocker.spy(temperature, 'fuentes')
368+
index = pd.date_range('2019-01-01 11:00', freq='h', periods=3)
369+
temps = pd.Series(25, index)
370+
irrads = pd.Series(1000, index)
371+
winds = pd.Series(1, index)
372+
out = system.fuentes_celltemp(irrads, temps, winds)
373+
assert_series_equal(spy.call_args[0][0], irrads)
374+
assert_series_equal(spy.call_args[0][1], temps)
375+
assert_series_equal(spy.call_args[0][2], winds)
376+
assert spy.call_args[1]['noct_installed'] == noct_installed
377+
assert_series_equal(out, pd.Series([52.85, 55.85, 55.85], index,
378+
name='tmod'))
379+
380+
381+
def test_PVSystem_fuentes_celltemp_override(mocker):
382+
# test that the surface_tilt value in the cell temp calculation can be
383+
# overridden but defaults to the surface_tilt attribute of the PVSystem
384+
spy = mocker.spy(temperature, 'fuentes')
385+
386+
noct_installed = 45
387+
index = pd.date_range('2019-01-01 11:00', freq='h', periods=3)
388+
temps = pd.Series(25, index)
389+
irrads = pd.Series(1000, index)
390+
winds = pd.Series(1, index)
391+
392+
# uses default value
393+
temp_model_params = {'noct_installed': noct_installed}
394+
system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params,
395+
surface_tilt=20)
396+
system.fuentes_celltemp(irrads, temps, winds)
397+
assert spy.call_args[1]['surface_tilt'] == 20
398+
399+
# can be overridden
400+
temp_model_params = {'noct_installed': noct_installed, 'surface_tilt': 30}
401+
system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params,
402+
surface_tilt=20)
403+
system.fuentes_celltemp(irrads, temps, winds)
404+
assert spy.call_args[1]['surface_tilt'] == 30
405+
406+
363407
def test__infer_temperature_model_params():
364408
system = pvsystem.PVSystem(module_parameters={},
365409
racking_model='open_rack',

0 commit comments

Comments
 (0)