Skip to content

Expose temperature.faiman in PVSystem and ModelChain #897

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
Feb 25, 2020
Merged
1 change: 1 addition & 0 deletions docs/sphinx/source/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -524,6 +524,7 @@ ModelChain model definitions.
modelchain.ModelChain.no_spectral_loss
modelchain.ModelChain.sapm_temp
modelchain.ModelChain.pvsyst_temp
modelchain.ModelChain.faiman_temp
modelchain.ModelChain.pvwatts_losses
modelchain.ModelChain.no_extra_losses

Expand Down
3 changes: 3 additions & 0 deletions docs/sphinx/source/whatsnew/v0.7.2.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ Enhancements
* TMY3 dataframe returned by :py:func:`~pvlib.iotools.read_tmy3` now contains
the original ``Date (MM/DD/YYYY)`` and ``Time (HH:MM)`` columns that the
indices were parsed from (:pull:`866`)
* Add :py:func:`~pvlib.pvsystem.PVSystem.faiman` and added
``temperature_model='faiman'`` option to :py:class:`~pvlib.modelchain.ModelChain`
(:pull:`897`) (:issue:`836`).
* Add Kimber soiling model :py:func:`pvlib.losses.soiling_kimber` (:pull:`860`)

Bug fixes
Expand Down
15 changes: 13 additions & 2 deletions pvlib/modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,8 +282,9 @@ class ModelChain(object):
as the first argument to a user-defined function.

temperature_model: None, str or function, default None
Valid strings are 'sapm' and 'pvsyst'. The ModelChain instance will be
passed as the first argument to a user-defined function.
Valid strings are 'sapm', 'pvsyst', and 'faiman'. The ModelChain
instance will be passed as the first argument to a user-defined
function.

losses_model: str or function, default 'no_loss'
Valid strings are 'pvwatts', 'no_loss'. The ModelChain instance
Expand Down Expand Up @@ -660,6 +661,8 @@ def temperature_model(self, model):
self._temperature_model = self.sapm_temp
elif model == 'pvsyst':
self._temperature_model = self.pvsyst_temp
elif model == 'faiman':
self._temperature_model = self.faiman_temp
else:
raise ValueError(model + ' is not a valid temperature model')
# check system.temperature_model_parameters for consistency
Expand All @@ -679,6 +682,8 @@ def infer_temperature_model(self):
return self.sapm_temp
elif set(['u_c', 'u_v']) <= params:
return self.pvsyst_temp
elif set(['u0', 'u1']) <= params:
return self.faiman_temp
else:
raise ValueError('could not infer temperature model from '
'system.temperature_module_parameters {}.'
Expand All @@ -696,6 +701,12 @@ def pvsyst_temp(self):
self.weather['wind_speed'])
return self

def faiman_temp(self):
self.cell_temperature = self.system.faiman_celltemp(
self.total_irrad['poa_global'], self.weather['temp_air'],
self.weather['wind_speed'])
return self

@property
def losses_model(self):
return self._losses_model
Expand Down
33 changes: 26 additions & 7 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -600,13 +600,6 @@ def pvsyst_celltemp(self, poa_global, temp_air, wind_speed=1.0):
loss factor was determined. The default value is 1.0, which is
the wind speed at module height used to determine NOCT.

eta_m : numeric, default 0.1
Module external efficiency as a fraction, i.e.,
DC power / poa_global.

alpha_absorption : numeric, default 0.9
Absorption coefficient

Returns
-------
numeric, values in degrees C.
Expand All @@ -618,6 +611,32 @@ def pvsyst_celltemp(self, poa_global, temp_air, wind_speed=1.0):
return temperature.pvsyst_cell(poa_global, temp_air, wind_speed,
**kwargs)

def faiman_celltemp(self, poa_global, temp_air, wind_speed=1.0):
"""
Use :py:func:`temperature.faiman` to calculate cell temperature.

Parameters
----------
poa_global : numeric
Total incident irradiance [W/m^2].

temp_air : numeric
Ambient dry bulb temperature [C].

wind_speed : numeric, default 1.0
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]
Copy link
Member

Choose a reason for hiding this comment

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

I see N0CT is used in other docstrings, but I don't see a definition anywhere. Could be a follow up issue.

Copy link
Member

Choose a reason for hiding this comment

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

NOCT = Nominal Cell Operating Temperature. Maybe spell out the acronym?

We could add a Note with the definition: NOCT is cell temperature in conditions of irradiance 800 W/m2, ambient air temperature 20C, wind speed 1m/s and cell at Voc, reference to IEC 61215-2 but I'm guessing on the part, I can check when in the office tomorrow.

Copy link
Member Author

Choose a reason for hiding this comment

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

@cwhanse I'm happy to copy/paste a Note snippet from you into this PR, but I'll point out that NOCT is also used in the docstrings of PVSystem.pvsyst_celltemp, temperature.pvsyst_cell, and temperature.faiman. I wonder if instead of including definitions in docstrings, it might be better to have a central list of definitions somewhere in the sphinx docs? Or just rely on the PVPMC website or PVCDROM (which as a page on NOCT already)?

Copy link
Member

Choose a reason for hiding this comment

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

Ok. Let’s address it separately if at all.

Copy link
Member

Choose a reason for hiding this comment

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

@CameronTStark I re-approve this PR for merge :)


Returns
-------
numeric, values in degrees C.
"""
kwargs = _build_kwargs(['u0', 'u1'],
self.temperature_model_parameters)
return temperature.faiman(poa_global, temp_air, wind_speed,
**kwargs)

def first_solar_spectral_loss(self, pw, airmass_absolute):

"""
Expand Down
18 changes: 9 additions & 9 deletions pvlib/temperature.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,8 @@ def pvsyst_cell(poa_global, temp_air, wind_speed=1.0, u_c=29.0, u_v=0.0,
def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):
'''
Calculate cell or module temperature using an empirical heat loss factor
model as proposed by Faiman [1] and adopted in the IEC 61853
standards [2] and [3].
model as proposed by Faiman [1]_ and adopted in the IEC 61853
standards [2]_ and [3]_.

Usage of this model in the IEC 61853 standard does not distinguish
between cell and module temperature.
Expand Down Expand Up @@ -312,15 +312,15 @@ def faiman(poa_global, temp_air, wind_speed=1.0, u0=25.0, u1=6.84):

References
----------
[1] Faiman, D. (2008). "Assessing the outdoor operating temperature of
photovoltaic modules." Progress in Photovoltaics 16(4): 307-315.
.. [1] Faiman, D. (2008). "Assessing the outdoor operating temperature of
photovoltaic modules." Progress in Photovoltaics 16(4): 307-315.

[2] "IEC 61853-2 Photovoltaic (PV) module performance testing and energy
rating - Part 2: Spectral responsivity, incidence angle and module
operating temperature measurements". IEC, Geneva, 2018.
.. [2] "IEC 61853-2 Photovoltaic (PV) module performance testing and energy
rating - Part 2: Spectral responsivity, incidence angle and module
operating temperature measurements". IEC, Geneva, 2018.

[3] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy
rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018.
.. [3] "IEC 61853-3 Photovoltaic (PV) module performance testing and energy
rating - Part 3: Energy rating of PV modules". IEC, Geneva, 2018.

'''
# Contributed by Anton Driesse (@adriesse), PV Performance Labs. Dec., 2019
Expand Down
62 changes: 56 additions & 6 deletions pvlib/tests/test_modelchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,31 @@ def pvwatts_dc_pvwatts_ac_system(sapm_temperature_cs5p_220m):
return system


@pytest.fixture(scope="function")
def pvwatts_dc_pvwatts_ac_faiman_temp_system():
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
temp_model_params = {'u0': 25.0, 'u1': 6.84}
inverter_parameters = {'pdc0': 220, 'eta_inv_nom': 0.95}
system = PVSystem(surface_tilt=32.2, surface_azimuth=180,
module_parameters=module_parameters,
temperature_model_parameters=temp_model_params,
inverter_parameters=inverter_parameters)
return system


@pytest.fixture(scope="function")
def pvwatts_dc_pvwatts_ac_pvsyst_temp_system():
module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003}
temp_model_params = {'u_c': 29.0, 'u_v': 0.0, 'eta_m': 0.1,
'alpha_absorption': 0.9}
inverter_parameters = {'pdc0': 220, 'eta_inv_nom': 0.95}
system = PVSystem(surface_tilt=32.2, surface_azimuth=180,
module_parameters=module_parameters,
temperature_model_parameters=temp_model_params,
inverter_parameters=inverter_parameters)
return system


@pytest.fixture(scope="function")
def system_no_aoi(cec_module_cs5p_220m, sapm_temperature_cs5p_220m,
cec_inverter_parameters):
Expand Down Expand Up @@ -230,10 +255,10 @@ def test_run_model_gueymard_perez(system, location):
assert_series_equal(ac, expected)


def test_run_model_with_weather(system, location, weather, mocker):
def test_run_model_with_weather_sapm_temp(system, location, weather, mocker):
# test with sapm cell temperature model
weather['wind_speed'] = 5
weather['temp_air'] = 10
# test with sapm cell temperature model
system.racking_model = 'open_rack'
system.module_type = 'glass_glass'
mc = ModelChain(system, location)
Expand All @@ -246,7 +271,12 @@ def test_run_model_with_weather(system, location, weather, mocker):
assert_series_equal(m_sapm.call_args[0][1], weather['temp_air']) # temp
assert_series_equal(m_sapm.call_args[0][2], weather['wind_speed']) # wind
assert not mc.ac.empty


def test_run_model_with_weather_pvsyst_temp(system, location, weather, mocker):
# test with pvsyst cell temperature model
weather['wind_speed'] = 5
weather['temp_air'] = 10
system.racking_model = 'freestanding'
system.temperature_model_parameters = \
temperature._temperature_model_params('pvsyst', 'freestanding')
Expand All @@ -260,6 +290,21 @@ def test_run_model_with_weather(system, location, weather, mocker):
assert not mc.ac.empty


def test_run_model_with_weather_faiman_temp(system, location, weather, mocker):
# test with faiman cell temperature model
weather['wind_speed'] = 5
weather['temp_air'] = 10
system.temperature_model_parameters = {'u0': 25.0, 'u1': 6.84}
mc = ModelChain(system, location)
mc.temperature_model = 'faiman'
m_faiman = mocker.spy(system, 'faiman_celltemp')
mc.run_model(weather)
assert m_faiman.call_count == 1
assert_series_equal(m_faiman.call_args[0][1], weather['temp_air'])
assert_series_equal(m_faiman.call_args[0][2], weather['wind_speed'])
assert not mc.ac.empty


def test_run_model_tracker(system, location, weather, mocker):
system = SingleAxisTracker(
module_parameters=system.module_parameters,
Expand Down Expand Up @@ -341,15 +386,20 @@ def test_infer_spectral_model(location, system, cec_dc_snl_ac_system,


@pytest.mark.parametrize('temp_model', [
'sapm', pytest.param('pvsyst', marks=requires_scipy)])
def test_infer_temp_model(location, system, pvsyst_dc_snl_ac_system,
'sapm_temp', 'faiman_temp',
pytest.param('pvsyst_temp', marks=requires_scipy)])
def test_infer_temp_model(location, system,
pvwatts_dc_pvwatts_ac_pvsyst_temp_system,
pvwatts_dc_pvwatts_ac_faiman_temp_system,
temp_model):
dc_systems = {'sapm': system,
'pvsyst': pvsyst_dc_snl_ac_system}
dc_systems = {'sapm_temp': system,
'pvsyst_temp': pvwatts_dc_pvwatts_ac_pvsyst_temp_system,
'faiman_temp': pvwatts_dc_pvwatts_ac_faiman_temp_system}
system = dc_systems[temp_model]
mc = ModelChain(system, location,
orientation_strategy='None', aoi_model='physical',
spectral_model='no_loss')
assert temp_model == mc.temperature_model.__name__
assert isinstance(mc, ModelChain)


Expand Down
28 changes: 11 additions & 17 deletions pvlib/tests/test_pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -415,23 +415,17 @@ def test_PVSystem_pvsyst_celltemp(mocker):
assert (out < 90) and (out > 70)


def test_PVSystem_pvsyst_celltemp_kwargs(mocker):
temp_model_params = temperature.TEMPERATURE_MODEL_PARAMETERS['pvsyst'][
'insulated']
alpha_absorption = 0.85
eta_m = 0.17
module_parameters = {'alpha_absorption': alpha_absorption, 'eta_m': eta_m}
system = pvsystem.PVSystem(module_parameters=module_parameters,
temperature_model_parameters=temp_model_params)
mocker.spy(temperature, 'pvsyst_cell')
irrad = 800
temp = 45
wind = 0.5
out = system.pvsyst_celltemp(irrad, temp, wind_speed=wind)
temperature.pvsyst_cell.assert_called_once_with(
irrad, temp, wind, temp_model_params['u_c'], temp_model_params['u_v'],
eta_m, alpha_absorption)
assert (out < 90) and (out > 70)
def test_PVSystem_faiman_celltemp(mocker):
u0, u1 = 25.0, 6.84 # default values
temp_model_params = {'u0': u0, 'u1': u1}
system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params)
mocker.spy(temperature, 'faiman')
temps = 25
irrads = 1000
winds = 1
out = system.faiman_celltemp(irrads, temps, winds)
temperature.faiman.assert_called_once_with(irrads, temps, winds, u0, u1)
assert_allclose(out, 56.4, atol=1)


def test__infer_temperature_model_params():
Expand Down