Skip to content

Commit 03d6180

Browse files
authored
Expose temperature.faiman in PVSystem and ModelChain (#897)
* remove unused pvsyst_celltemp parameter docs * fix up temperature.faiman docstring formatting * add pvsystem.PVSystem.faiman_celltemp * add faiman model to ModelChain * fix copy/paste error * add test * whatsnew * fix whatsnew * add GH links to whatsnew entry * split up MC cell temp tests; add faiman test * add faiman to MC infer_temp_model test; change test to check inferred model * add MC.faiman_temp to api.rst * delete duplicate pvsystem test
1 parent 99bf42f commit 03d6180

File tree

7 files changed

+119
-41
lines changed

7 files changed

+119
-41
lines changed

docs/sphinx/source/api.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -524,6 +524,7 @@ ModelChain model definitions.
524524
modelchain.ModelChain.no_spectral_loss
525525
modelchain.ModelChain.sapm_temp
526526
modelchain.ModelChain.pvsyst_temp
527+
modelchain.ModelChain.faiman_temp
527528
modelchain.ModelChain.pvwatts_losses
528529
modelchain.ModelChain.no_extra_losses
529530

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ Enhancements
1313
* TMY3 dataframe returned by :py:func:`~pvlib.iotools.read_tmy3` now contains
1414
the original ``Date (MM/DD/YYYY)`` and ``Time (HH:MM)`` columns that the
1515
indices were parsed from (:pull:`866`)
16+
* Add :py:func:`~pvlib.pvsystem.PVSystem.faiman` and added
17+
``temperature_model='faiman'`` option to :py:class:`~pvlib.modelchain.ModelChain`
18+
(:pull:`897`) (:issue:`836`).
1619
* Add Kimber soiling model :py:func:`pvlib.losses.soiling_kimber` (:pull:`860`)
1720

1821
Bug fixes

pvlib/modelchain.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,8 +282,9 @@ class ModelChain(object):
282282
as the first argument to a user-defined function.
283283
284284
temperature_model: None, str or function, default None
285-
Valid strings are 'sapm' and 'pvsyst'. The ModelChain instance will be
286-
passed as the first argument to a user-defined function.
285+
Valid strings are 'sapm', 'pvsyst', and 'faiman'. The ModelChain
286+
instance will be passed as the first argument to a user-defined
287+
function.
287288
288289
losses_model: str or function, default 'no_loss'
289290
Valid strings are 'pvwatts', 'no_loss'. The ModelChain instance
@@ -660,6 +661,8 @@ def temperature_model(self, model):
660661
self._temperature_model = self.sapm_temp
661662
elif model == 'pvsyst':
662663
self._temperature_model = self.pvsyst_temp
664+
elif model == 'faiman':
665+
self._temperature_model = self.faiman_temp
663666
else:
664667
raise ValueError(model + ' is not a valid temperature model')
665668
# check system.temperature_model_parameters for consistency
@@ -679,6 +682,8 @@ def infer_temperature_model(self):
679682
return self.sapm_temp
680683
elif set(['u_c', 'u_v']) <= params:
681684
return self.pvsyst_temp
685+
elif set(['u0', 'u1']) <= params:
686+
return self.faiman_temp
682687
else:
683688
raise ValueError('could not infer temperature model from '
684689
'system.temperature_module_parameters {}.'
@@ -696,6 +701,12 @@ def pvsyst_temp(self):
696701
self.weather['wind_speed'])
697702
return self
698703

704+
def faiman_temp(self):
705+
self.cell_temperature = self.system.faiman_celltemp(
706+
self.total_irrad['poa_global'], self.weather['temp_air'],
707+
self.weather['wind_speed'])
708+
return self
709+
699710
@property
700711
def losses_model(self):
701712
return self._losses_model

pvlib/pvsystem.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -600,13 +600,6 @@ def pvsyst_celltemp(self, poa_global, temp_air, wind_speed=1.0):
600600
loss factor was determined. The default value is 1.0, which is
601601
the wind speed at module height used to determine NOCT.
602602
603-
eta_m : numeric, default 0.1
604-
Module external efficiency as a fraction, i.e.,
605-
DC power / poa_global.
606-
607-
alpha_absorption : numeric, default 0.9
608-
Absorption coefficient
609-
610603
Returns
611604
-------
612605
numeric, values in degrees C.
@@ -618,6 +611,32 @@ def pvsyst_celltemp(self, poa_global, temp_air, wind_speed=1.0):
618611
return temperature.pvsyst_cell(poa_global, temp_air, wind_speed,
619612
**kwargs)
620613

614+
def faiman_celltemp(self, poa_global, temp_air, wind_speed=1.0):
615+
"""
616+
Use :py:func:`temperature.faiman` to calculate cell temperature.
617+
618+
Parameters
619+
----------
620+
poa_global : numeric
621+
Total incident irradiance [W/m^2].
622+
623+
temp_air : numeric
624+
Ambient dry bulb temperature [C].
625+
626+
wind_speed : numeric, default 1.0
627+
Wind speed in m/s measured at the same height for which the wind
628+
loss factor was determined. The default value 1.0 m/s is the wind
629+
speed at module height used to determine NOCT. [m/s]
630+
631+
Returns
632+
-------
633+
numeric, values in degrees C.
634+
"""
635+
kwargs = _build_kwargs(['u0', 'u1'],
636+
self.temperature_model_parameters)
637+
return temperature.faiman(poa_global, temp_air, wind_speed,
638+
**kwargs)
639+
621640
def first_solar_spectral_loss(self, pw, airmass_absolute):
622641

623642
"""

pvlib/temperature.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -274,8 +274,8 @@ def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
274274
def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):
275275
'''
276276
Calculate cell or module temperature using an empirical heat loss factor
277-
model as proposed by Faiman [1] and adopted in the IEC 61853
278-
standards [2] and [3].
277+
model as proposed by Faiman [1]_ and adopted in the IEC 61853
278+
standards [2]_ and [3]_.
279279
280280
Usage of this model in the IEC 61853 standard does not distinguish
281281
between cell and module temperature.
@@ -312,15 +312,15 @@ def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):
312312
313313
References
314314
----------
315-
[1] Faiman, D. (2008). "Assessing the outdoor operating temperature of
316-
photovoltaic modules." Progress in Photovoltaics 16(4): 307-315.
315+
.. [1] Faiman, D. (2008). "Assessing the outdoor operating temperature of
316+
photovoltaic modules." Progress in Photovoltaics 16(4): 307-315.
317317
318-
[2] "IEC 61853-2 Photovoltaic (PV) module performance testing and energy
319-
rating - Part 2: Spectral responsivity, incidence angle and module
320-
operating temperature measurements". IEC, Geneva, 2018.
318+
.. [2] "IEC 61853-2 Photovoltaic (PV) module performance testing and energy
319+
rating - Part 2: Spectral responsivity, incidence angle and module
320+
operating temperature measurements". IEC, Geneva, 2018.
321321
322-
[3] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy
323-
rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018.
322+
.. [3] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy
323+
rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018.
324324
325325
'''
326326
# Contributed by Anton Driesse (@adriesse), PV Performance Labs. Dec., 2019

pvlib/tests/test_modelchain.py

Lines changed: 56 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,31 @@ def pvwatts_dc_pvwatts_ac_system(sapm_temperature_cs5p_220m):
113113
return system
114114

115115

116+
@pytest.fixture(scope="function")
117+
def pvwatts_dc_pvwatts_ac_faiman_temp_system():
118+
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
119+
temp_model_params = {'u0': 25.0, 'u1': 6.84}
120+
inverter_parameters = {'pdc0': 220, 'eta_inv_nom': 0.95}
121+
system = PVSystem(surface_tilt=32.2, surface_azimuth=180,
122+
module_parameters=module_parameters,
123+
temperature_model_parameters=temp_model_params,
124+
inverter_parameters=inverter_parameters)
125+
return system
126+
127+
128+
@pytest.fixture(scope="function")
129+
def pvwatts_dc_pvwatts_ac_pvsyst_temp_system():
130+
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
131+
temp_model_params = {'u_c': 29.0, 'u_v': 0.0, 'eta_m': 0.1,
132+
'alpha_absorption': 0.9}
133+
inverter_parameters = {'pdc0': 220, 'eta_inv_nom': 0.95}
134+
system = PVSystem(surface_tilt=32.2, surface_azimuth=180,
135+
module_parameters=module_parameters,
136+
temperature_model_parameters=temp_model_params,
137+
inverter_parameters=inverter_parameters)
138+
return system
139+
140+
116141
@pytest.fixture(scope="function")
117142
def system_no_aoi(cec_module_cs5p_220m, sapm_temperature_cs5p_220m,
118143
cec_inverter_parameters):
@@ -230,10 +255,10 @@ def test_run_model_gueymard_perez(system, location):
230255
assert_series_equal(ac, expected)
231256

232257

233-
def test_run_model_with_weather(system, location, weather, mocker):
258+
def test_run_model_with_weather_sapm_temp(system, location, weather, mocker):
259+
# test with sapm cell temperature model
234260
weather['wind_speed'] = 5
235261
weather['temp_air'] = 10
236-
# test with sapm cell temperature model
237262
system.racking_model = 'open_rack'
238263
system.module_type = 'glass_glass'
239264
mc = ModelChain(system, location)
@@ -246,7 +271,12 @@ def test_run_model_with_weather(system, location, weather, mocker):
246271
assert_series_equal(m_sapm.call_args[0][1], weather['temp_air']) # temp
247272
assert_series_equal(m_sapm.call_args[0][2], weather['wind_speed']) # wind
248273
assert not mc.ac.empty
274+
275+
276+
def test_run_model_with_weather_pvsyst_temp(system, location, weather, mocker):
249277
# test with pvsyst cell temperature model
278+
weather['wind_speed'] = 5
279+
weather['temp_air'] = 10
250280
system.racking_model = 'freestanding'
251281
system.temperature_model_parameters = \
252282
temperature._temperature_model_params('pvsyst', 'freestanding')
@@ -260,6 +290,21 @@ def test_run_model_with_weather(system, location, weather, mocker):
260290
assert not mc.ac.empty
261291

262292

293+
def test_run_model_with_weather_faiman_temp(system, location, weather, mocker):
294+
# test with faiman cell temperature model
295+
weather['wind_speed'] = 5
296+
weather['temp_air'] = 10
297+
system.temperature_model_parameters = {'u0': 25.0, 'u1': 6.84}
298+
mc = ModelChain(system, location)
299+
mc.temperature_model = 'faiman'
300+
m_faiman = mocker.spy(system, 'faiman_celltemp')
301+
mc.run_model(weather)
302+
assert m_faiman.call_count == 1
303+
assert_series_equal(m_faiman.call_args[0][1], weather['temp_air'])
304+
assert_series_equal(m_faiman.call_args[0][2], weather['wind_speed'])
305+
assert not mc.ac.empty
306+
307+
263308
def test_run_model_tracker(system, location, weather, mocker):
264309
system = SingleAxisTracker(
265310
module_parameters=system.module_parameters,
@@ -341,15 +386,20 @@ def test_infer_spectral_model(location, system, cec_dc_snl_ac_system,
341386

342387

343388
@pytest.mark.parametrize('temp_model', [
344-
'sapm', pytest.param('pvsyst', marks=requires_scipy)])
345-
def test_infer_temp_model(location, system, pvsyst_dc_snl_ac_system,
389+
'sapm_temp', 'faiman_temp',
390+
pytest.param('pvsyst_temp', marks=requires_scipy)])
391+
def test_infer_temp_model(location, system,
392+
pvwatts_dc_pvwatts_ac_pvsyst_temp_system,
393+
pvwatts_dc_pvwatts_ac_faiman_temp_system,
346394
temp_model):
347-
dc_systems = {'sapm': system,
348-
'pvsyst': pvsyst_dc_snl_ac_system}
395+
dc_systems = {'sapm_temp': system,
396+
'pvsyst_temp': pvwatts_dc_pvwatts_ac_pvsyst_temp_system,
397+
'faiman_temp': pvwatts_dc_pvwatts_ac_faiman_temp_system}
349398
system = dc_systems[temp_model]
350399
mc = ModelChain(system, location,
351400
orientation_strategy='None', aoi_model='physical',
352401
spectral_model='no_loss')
402+
assert temp_model == mc.temperature_model.__name__
353403
assert isinstance(mc, ModelChain)
354404

355405

pvlib/tests/test_pvsystem.py

Lines changed: 11 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -415,23 +415,17 @@ def test_PVSystem_pvsyst_celltemp(mocker):
415415
assert (out < 90) and (out > 70)
416416

417417

418-
def test_PVSystem_pvsyst_celltemp_kwargs(mocker):
419-
temp_model_params = temperature.TEMPERATURE_MODEL_PARAMETERS['pvsyst'][
420-
'insulated']
421-
alpha_absorption = 0.85
422-
eta_m = 0.17
423-
module_parameters = {'alpha_absorption': alpha_absorption, 'eta_m': eta_m}
424-
system = pvsystem.PVSystem(module_parameters=module_parameters,
425-
temperature_model_parameters=temp_model_params)
426-
mocker.spy(temperature, 'pvsyst_cell')
427-
irrad = 800
428-
temp = 45
429-
wind = 0.5
430-
out = system.pvsyst_celltemp(irrad, temp, wind_speed=wind)
431-
temperature.pvsyst_cell.assert_called_once_with(
432-
irrad, temp, wind, temp_model_params['u_c'], temp_model_params['u_v'],
433-
eta_m, alpha_absorption)
434-
assert (out < 90) and (out > 70)
418+
def test_PVSystem_faiman_celltemp(mocker):
419+
u0, u1 = 25.0, 6.84 # default values
420+
temp_model_params = {'u0': u0, 'u1': u1}
421+
system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params)
422+
mocker.spy(temperature, 'faiman')
423+
temps = 25
424+
irrads = 1000
425+
winds = 1
426+
out = system.faiman_celltemp(irrads, temps, winds)
427+
temperature.faiman.assert_called_once_with(irrads, temps, winds, u0, u1)
428+
assert_allclose(out, 56.4, atol=1)
435429

436430

437431
def test__infer_temperature_model_params():

0 commit comments

Comments
 (0)