From 74800b0129e436d98ba4c19b03b7f3d2697eaeb8 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 12 Mar 2021 15:57:16 -0700 Subject: [PATCH 01/15] add ModelChain, PVSystem methods --- pvlib/modelchain.py | 14 ++++++-- pvlib/pvsystem.py | 63 +++++++++++++++++++++++++++++++++++- pvlib/tests/test_pvsystem.py | 13 ++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 990598ae1e..88b4632af6 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -352,7 +352,7 @@ class ModelChain: as the first argument to a user-defined function. temperature_model: None, str or function, default None - Valid strings are 'sapm', 'pvsyst', 'faiman', and 'fuentes'. + Valid strings are: 'sapm', 'pvsyst', 'faiman', 'fuentes', 'noct_sam'. The ModelChain instance will be passed as the first argument to a user-defined function. @@ -935,6 +935,8 @@ def temperature_model(self, model): self._temperature_model = self.faiman_temp elif model == 'fuentes': self._temperature_model = self.fuentes_temp + elif model == 'noct_sam': + self._temperature_model = self.noct_sam_temp else: raise ValueError(model + ' is not a valid temperature model') # check system.temperature_model_parameters for consistency @@ -965,6 +967,8 @@ def infer_temperature_model(self): return self.faiman_temp elif {'noct_installed'} <= params: return self.fuentes_temp + elif {'noct', 'eta_m_ref'} <= params: + return self.noct_sam_temp else: raise ValueError(f'could not infer temperature model from ' f'system.temperature_model_parameters. Check ' @@ -994,7 +998,10 @@ def _set_celltemp(self, model): self.results.effective_irradiance) temp_air = _tuple_from_dfs(self.weather, 'temp_air') wind_speed = _tuple_from_dfs(self.weather, 'wind_speed') - self.results.cell_temperature = model(poa, temp_air, wind_speed) + arg_list = [poa, temp_air, wind_speed] + if model == 'noct_sam': + arg_list += [self.results.effective_irradiance] + self.results.cell_temperature = model(*tuple(arg_list)) return self def sapm_temp(self): @@ -1009,6 +1016,9 @@ def faiman_temp(self): def fuentes_temp(self): return self._set_celltemp(self.system.fuentes_celltemp) + def noct_sam_temp(self): + return self._set_celltemp(self.system.noct_sam_celltemp) + @property def losses_model(self): return self._losses_model diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index a727673265..f4b19f4527 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -143,7 +143,8 @@ class PVSystem: Module parameters as defined by the SAPM, CEC, or other. temperature_model_parameters : None, dict or Series, default None. - Temperature model parameters as defined by the SAPM, Pvsyst, or other. + Temperature model parameters as required by one of the models in + pvlib.temperature (excluding poa_global, temp_air and wind_speed). modules_per_string: int or float, default 1 See system topology discussion above. @@ -781,6 +782,66 @@ def _build_kwargs_fuentes(array): ) ) + @_unwrap_single_value + def noct_sam_celltemp(self, poa_global, temp_air, wind_speed, + effective_irradiance=None): + """ + Use :py:func:`temperature.noct_sam` to calculate cell temperature. + + Parameters + ---------- + poa_global : pandas Series or tuple of Series + Total incident irradiance [W/m^2] + + temp_air : pandas Series or tuple of Series + Ambient dry bulb temperature [C] + + wind_speed : pandas Series or tuple of Series + Wind speed [m/s] + + effective_irradiance : pandas Series, tuple of Series or None. + The irradiance that is converted to photocurrent. If None, + assumed equal to poa_global. [W/m^2] + + Returns + ------- + temperature_cell : Series or tuple of Series + The modeled cell temperature [C] + + Notes + ----- + The `temp_air` and `wind_speed` parameters may be passed as tuples + to provide different values for each Array in the system. If not + passed as a tuple then the same value is used for input to each Array. + If passed as a tuple the length must be the same as the number of + Arrays. + """ + # default to using the Array attribute, but allow user to + # override with a custom surface_tilt value + poa_global = self._validate_per_array(poa_global) + temp_air = self._validate_per_array(temp_air, system_wide=True) + wind_speed = self._validate_per_array(wind_speed, system_wide=True) + + if effective_irradiance is None: + effective_irradiance = poa_global + + def _build_kwargs_noct_sam(array): + temp_model_kwargs = _build_kwargs([ + 'noct', 'eta_m_ref', 'transmittance_absorptance', + 'array_height', 'mount_standoff'], + array.temperature_model_parameters) + return temp_model_kwargs + return tuple( + temperature.noct_sam( + poa_global, temp_air, wind_speed, + effective_irradiance=eff_irrad, + **_build_kwargs_noct_sam(array)) + for array, poa_global, temp_air, wind_speed, eff_irrad in zip( + self.arrays, poa_global, temp_air, wind_speed, + effective_irradiance + ) + ) + @_unwrap_single_value def first_solar_spectral_loss(self, pw, airmass_absolute): diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 5c0ef7b4a2..9b30f0bb3d 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -495,6 +495,19 @@ def test_PVSystem_faiman_celltemp(mocker): assert_allclose(out, 56.4, atol=1) +def test_PVSystem_noct_celltemp(mocker): + poa_global, temp_air, wind_speed, noct, eta_m_ref = (1000., 25., 1., 45., + 0.2) + expected = 55.230790492 + temp_model_params = {'noct': noct, 'eta_m_ref': eta_m_ref} + system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) + mocker.spy(temperature, 'noct_sam') + out = system.noct_sam_celltemp(poa_global, temp_air, wind_speed) + temperature.noct_sam.assert_called_once_with(poa_global, temp_air, + wind_speed, noct, eta_m_ref) + assert_allclose(out, expected) + + @pytest.mark.parametrize("celltemp", [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, From 2fbd3040de5c80cfa73a336ffcbf51d5864f04e4 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 12 Mar 2021 16:10:45 -0700 Subject: [PATCH 02/15] first cut at pvsystem tests --- pvlib/tests/test_pvsystem.py | 33 +++++++++++++++++++++++++-------- 1 file changed, 25 insertions(+), 8 deletions(-) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 9b30f0bb3d..bb4f1153ad 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -506,13 +506,23 @@ def test_PVSystem_noct_celltemp(mocker): temperature.noct_sam.assert_called_once_with(poa_global, temp_air, wind_speed, noct, eta_m_ref) assert_allclose(out, expected) + # now use optional arguments + temp_model_params.update({'effective_irradiance': 1100., + 'transmittance_absorbtance': 0.8, + 'array_height': 2, + 'mount_standoff': 2.0}) + expected = 60.477703576 + system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) + out = system.noct_sam_celltemp(poa_global, temp_air, wind_speed) + assert_allclose(out, expected) @pytest.mark.parametrize("celltemp", [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, pvsystem.PVSystem.sapm_celltemp, - pvsystem.PVSystem.fuentes_celltemp]) + pvsystem.PVSystem.fuentes_celltemp, + pvsystem.PVSystem.noct_sam_celltemp]) def test_PVSystem_multi_array_celltemp_functions(celltemp, two_array_system): times = pd.date_range(start='2020-08-25 11:00', freq='H', periods=3) irrad_one = pd.Series(1000, index=times) @@ -528,7 +538,8 @@ def test_PVSystem_multi_array_celltemp_functions(celltemp, two_array_system): [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, pvsystem.PVSystem.sapm_celltemp, - pvsystem.PVSystem.fuentes_celltemp]) + pvsystem.PVSystem.fuentes_celltemp, + pvsystem.PVSystem.noct_sam_celltemp]) def test_PVSystem_multi_array_celltemp_multi_temp(celltemp, two_array_system): times = pd.date_range(start='2020-08-25 11:00', freq='H', periods=3) irrad = pd.Series(1000, index=times) @@ -556,7 +567,8 @@ def test_PVSystem_multi_array_celltemp_multi_temp(celltemp, two_array_system): [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, pvsystem.PVSystem.sapm_celltemp, - pvsystem.PVSystem.fuentes_celltemp]) + pvsystem.PVSystem.fuentes_celltemp, + pvsystem.PVSystem.noct_sam_celltemp]) def test_PVSystem_multi_array_celltemp_multi_wind(celltemp, two_array_system): times = pd.date_range(start='2020-08-25 11:00', freq='H', periods=3) irrad = pd.Series(1000, index=times) @@ -584,7 +596,8 @@ def test_PVSystem_multi_array_celltemp_multi_wind(celltemp, two_array_system): [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, pvsystem.PVSystem.sapm_celltemp, - pvsystem.PVSystem.fuentes_celltemp]) + pvsystem.PVSystem.fuentes_celltemp, + pvsystem.PVSystem.noct_sam_celltemp]) def test_PVSystem_multi_array_celltemp_temp_too_short( celltemp, two_array_system): with pytest.raises(ValueError, @@ -596,7 +609,8 @@ def test_PVSystem_multi_array_celltemp_temp_too_short( [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, pvsystem.PVSystem.sapm_celltemp, - pvsystem.PVSystem.fuentes_celltemp]) + pvsystem.PVSystem.fuentes_celltemp, + pvsystem.PVSystem.noct_sam_celltemp]) def test_PVSystem_multi_array_celltemp_temp_too_long( celltemp, two_array_system): with pytest.raises(ValueError, @@ -608,7 +622,8 @@ def test_PVSystem_multi_array_celltemp_temp_too_long( [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, pvsystem.PVSystem.sapm_celltemp, - pvsystem.PVSystem.fuentes_celltemp]) + pvsystem.PVSystem.fuentes_celltemp, + pvsystem.PVSystem.noct_sam_celltemp]) def test_PVSystem_multi_array_celltemp_wind_too_short( celltemp, two_array_system): with pytest.raises(ValueError, @@ -620,7 +635,8 @@ def test_PVSystem_multi_array_celltemp_wind_too_short( [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, pvsystem.PVSystem.sapm_celltemp, - pvsystem.PVSystem.fuentes_celltemp]) + pvsystem.PVSystem.fuentes_celltemp, + pvsystem.PVSystem.noct_sam_celltemp]) def test_PVSystem_multi_array_celltemp_wind_too_long( celltemp, two_array_system): with pytest.raises(ValueError, @@ -631,8 +647,9 @@ def test_PVSystem_multi_array_celltemp_wind_too_long( @pytest.mark.parametrize("celltemp", [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, + pvsystem.PVSystem.sapm_celltemp, pvsystem.PVSystem.fuentes_celltemp, - pvsystem.PVSystem.sapm_celltemp]) + pvsystem.PVSystem.noct_sam_celltemp]) def test_PVSystem_multi_array_celltemp_poa_length_mismatch( celltemp, two_array_system): with pytest.raises(ValueError, From c3175b04c921bf40a7caf40cdc10e8421039582b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 12 Mar 2021 17:01:42 -0700 Subject: [PATCH 03/15] fix iterable, called_once_with in test --- pvlib/pvsystem.py | 3 ++- pvlib/tests/test_pvsystem.py | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index f4b19f4527..f4eefad173 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -822,8 +822,9 @@ def noct_sam_celltemp(self, poa_global, temp_air, wind_speed, temp_air = self._validate_per_array(temp_air, system_wide=True) wind_speed = self._validate_per_array(wind_speed, system_wide=True) + # need effective_irradiance to be an iterable if effective_irradiance is None: - effective_irradiance = poa_global + effective_irradiance = tuple([None for a in self.num_arrays]) def _build_kwargs_noct_sam(array): temp_model_kwargs = _build_kwargs([ diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index bb4f1153ad..02025ed385 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -503,8 +503,9 @@ def test_PVSystem_noct_celltemp(mocker): system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) mocker.spy(temperature, 'noct_sam') out = system.noct_sam_celltemp(poa_global, temp_air, wind_speed) - temperature.noct_sam.assert_called_once_with(poa_global, temp_air, - wind_speed, noct, eta_m_ref) + temperature.noct_sam.assert_called_once_with( + poa_global, temp_air, wind_speed, effective_irradiance=None, noct=noct, + eta_m_ref=eta_m_ref) assert_allclose(out, expected) # now use optional arguments temp_model_params.update({'effective_irradiance': 1100., From 64b16bf26be575af83aa09e142ffc513703d6ee6 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Fri, 12 Mar 2021 17:27:32 -0700 Subject: [PATCH 04/15] derp --- pvlib/pvsystem.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index f4eefad173..6e93933936 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -824,7 +824,7 @@ def noct_sam_celltemp(self, poa_global, temp_air, wind_speed, # need effective_irradiance to be an iterable if effective_irradiance is None: - effective_irradiance = tuple([None for a in self.num_arrays]) + effective_irradiance = tuple([None] * self.num_arrays) def _build_kwargs_noct_sam(array): temp_model_kwargs = _build_kwargs([ From e176da85952d80fde7b6582c0aa8dcf859f4b268 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 13 Mar 2021 11:02:05 -0700 Subject: [PATCH 05/15] handling of optional argument, misspelling --- pvlib/pvsystem.py | 3 +++ pvlib/tests/test_pvsystem.py | 6 +++--- pvlib/tests/test_temperature.py | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 6e93933936..ee56ca14da 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -825,6 +825,9 @@ def noct_sam_celltemp(self, poa_global, temp_air, wind_speed, # need effective_irradiance to be an iterable if effective_irradiance is None: effective_irradiance = tuple([None] * self.num_arrays) + else: + effective_irradiance = self._validate_per_array( + effective_irradiance) def _build_kwargs_noct_sam(array): temp_model_kwargs = _build_kwargs([ diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 02025ed385..294e876731 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -508,13 +508,13 @@ def test_PVSystem_noct_celltemp(mocker): eta_m_ref=eta_m_ref) assert_allclose(out, expected) # now use optional arguments - temp_model_params.update({'effective_irradiance': 1100., - 'transmittance_absorbtance': 0.8, + temp_model_params.update({'transmittance_absorptance': 0.8, 'array_height': 2, 'mount_standoff': 2.0}) expected = 60.477703576 system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) - out = system.noct_sam_celltemp(poa_global, temp_air, wind_speed) + out = system.noct_sam_celltemp(poa_global, temp_air, wind_speed, + effective_irradiance=1100.) assert_allclose(out, expected) diff --git a/pvlib/tests/test_temperature.py b/pvlib/tests/test_temperature.py index 1ce2e69a47..4c8574ffe6 100644 --- a/pvlib/tests/test_temperature.py +++ b/pvlib/tests/test_temperature.py @@ -271,12 +271,12 @@ def test_noct_sam_options(): poa_global, temp_air, wind_speed, noct, eta_m_ref = (1000., 25., 1., 45., 0.2) effective_irradiance = 1100. - transmittance_absorbtance = 0.8 + transmittance_absorptance = 0.8 array_height = 2 mount_standoff = 2.0 result = temperature.noct_sam(poa_global, temp_air, wind_speed, noct, eta_m_ref, effective_irradiance, - transmittance_absorbtance, array_height, + transmittance_absorptance, array_height, mount_standoff) expected = 60.477703576 assert_allclose(result, expected) From 5608f9af160efb83420c8a1915faa1e03a063110 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 13 Mar 2021 11:10:01 -0700 Subject: [PATCH 06/15] mimic ModelChain tests to noct_sam --- pvlib/tests/test_modelchain.py | 39 +++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index d4cb14814e..00c8bf108c 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -223,6 +223,19 @@ def pvwatts_dc_pvwatts_ac_fuentes_temp_system(): return system + +@pytest.fixture(scope="function") +def pvwatts_dc_pvwatts_ac_noct_sam_temp_system(): + module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003} + temp_model_params = {'noct': 45, 'eta_m_ref': 0.2} + 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): @@ -693,6 +706,23 @@ def test_run_model_with_weather_fuentes_temp(sapm_dc_snl_ac_system, location, assert not mc.results.ac.empty +def test_run_model_with_weather_noct_temp_temp(sapm_dc_snl_ac_system, location, + weather, mocker): + weather['wind_speed'] = 5 + weather['temp_air'] = 10 + sapm_dc_snl_ac_system.temperature_model_parameters = { + 'noct': 45, 'eta_m_ref': 0.2 + } + mc = ModelChain(sapm_dc_snl_ac_system, location) + mc.temperature_model = 'noct_sam' + m_noct_sam = mocker.spy(sapm_dc_snl_ac_system, 'noct_sam_celltemp') + mc.run_model(weather) + assert m_noct_sam.call_count == 1 + assert_series_equal(m_noct_sam.call_args[0][1], weather['temp_air']) + assert_series_equal(m_noct_sam.call_args[0][2], weather['wind_speed']) + assert not mc.results.ac.empty + + def test_run_model_tracker(sapm_dc_snl_ac_system, location, weather, mocker): system = SingleAxisTracker( module_parameters=sapm_dc_snl_ac_system.module_parameters, @@ -907,7 +937,9 @@ def test__prepare_temperature_arrays_weather(sapm_dc_snl_ac_system_same_arrays, ({'u0': 25.0, 'u1': 6.84}, ModelChain.faiman_temp), ({'noct_installed': 45}, - ModelChain.fuentes_temp)]) + ModelChain.fuentes_temp), + ({'noct': 45, 'eta_m_ref': 0.2}, + ModelChain.noct_sam_temp)]) def test_temperature_models_arrays_multi_weather( temp_params, temp_model, sapm_dc_snl_ac_system_same_arrays, @@ -1256,7 +1288,7 @@ def test_infer_spectral_model(location, sapm_dc_snl_ac_system, @pytest.mark.parametrize('temp_model', [ - 'sapm_temp', 'faiman_temp', 'pvsyst_temp', 'fuentes_temp']) + 'sapm_temp', 'faiman_temp', 'pvsyst_temp', 'fuentes_temp', 'noct_sam']) def test_infer_temp_model(location, sapm_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_pvsyst_temp_system, pvwatts_dc_pvwatts_ac_faiman_temp_system, @@ -1265,7 +1297,8 @@ def test_infer_temp_model(location, sapm_dc_snl_ac_system, dc_systems = {'sapm_temp': sapm_dc_snl_ac_system, 'pvsyst_temp': pvwatts_dc_pvwatts_ac_pvsyst_temp_system, 'faiman_temp': pvwatts_dc_pvwatts_ac_faiman_temp_system, - 'fuentes_temp': pvwatts_dc_pvwatts_ac_fuentes_temp_system} + 'fuentes_temp': pvwatts_dc_pvwatts_ac_fuentes_temp_system, + 'noct_sam_temp': pvwatts_dc_pvwatts_ac_noct_sam_temp_system} system = dc_systems[temp_model] mc = ModelChain(system, location, aoi_model='physical', spectral_model='no_loss') From 4a1dea3b4088eee5f4ed077a194670c823039886 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 13 Mar 2021 11:14:52 -0700 Subject: [PATCH 07/15] add required params to test fixture --- pvlib/tests/test_pvsystem.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 294e876731..1fc13d8c08 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -388,7 +388,11 @@ def two_array_system(pvsyst_module_params, cec_module_params): # Need u_v to be non-zero so wind-speed changes cell temperature # under the pvsyst model. temperature_model['u_v'] = 1.0 + # parameter for fuentes temperature model temperature_model['noct_installed'] = 45 + # parameters for noct_sam temperature model + temperature_model['noct'] = 45. + temperature_model['eta_m_ref'] = 0.2 module_params = {**pvsyst_module_params, **cec_module_params} return pvsystem.PVSystem( arrays=[ From 37ffb87e684cb471750629462895d1df8544d681 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 13 Mar 2021 11:24:07 -0700 Subject: [PATCH 08/15] fix key value --- pvlib/tests/test_modelchain.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index 00c8bf108c..f71c578361 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -223,7 +223,6 @@ def pvwatts_dc_pvwatts_ac_fuentes_temp_system(): return system - @pytest.fixture(scope="function") def pvwatts_dc_pvwatts_ac_noct_sam_temp_system(): module_parameters = {'pdc0': 220, 'gamma_pdc': -0.003} @@ -1288,7 +1287,8 @@ def test_infer_spectral_model(location, sapm_dc_snl_ac_system, @pytest.mark.parametrize('temp_model', [ - 'sapm_temp', 'faiman_temp', 'pvsyst_temp', 'fuentes_temp', 'noct_sam']) + 'sapm_temp', 'faiman_temp', 'pvsyst_temp', 'fuentes_temp', + 'noct_sam_temp']) def test_infer_temp_model(location, sapm_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_pvsyst_temp_system, pvwatts_dc_pvwatts_ac_faiman_temp_system, From aab7d96d6e1249d93e462e8dbad0bb7fbc2c3b99 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 13 Mar 2021 11:56:52 -0700 Subject: [PATCH 09/15] complete test spec --- pvlib/tests/test_modelchain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index f71c578361..a7a2f259c5 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -1293,6 +1293,7 @@ def test_infer_temp_model(location, sapm_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_pvsyst_temp_system, pvwatts_dc_pvwatts_ac_faiman_temp_system, pvwatts_dc_pvwatts_ac_fuentes_temp_system, + pvwatts_dc_pvwatts_ac_noct_sam_temp_system, temp_model): dc_systems = {'sapm_temp': sapm_dc_snl_ac_system, 'pvsyst_temp': pvwatts_dc_pvwatts_ac_pvsyst_temp_system, From 3b50f1af78392de8ac59b1faff23fc59201d4666 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 13 Mar 2021 12:32:12 -0700 Subject: [PATCH 10/15] complete test coverage, whatsnew --- docs/sphinx/source/whatsnew/v0.9.0.rst | 2 +- pvlib/modelchain.py | 2 +- pvlib/tests/test_modelchain.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.9.0.rst b/docs/sphinx/source/whatsnew/v0.9.0.rst index cb8a34756e..2c8e178eba 100644 --- a/docs/sphinx/source/whatsnew/v0.9.0.rst +++ b/docs/sphinx/source/whatsnew/v0.9.0.rst @@ -102,7 +102,7 @@ Enhancements from DC power. Use parameter ``model`` to specify which inverter model to use. (:pull:`1147`, :issue:`998`, :pull:`1150`) * Added :py:func:`~pvlib.temperature.noct_sam`, a cell temperature model - implemented in SAM (:pull:`1177`) + implemented in SAM (:pull:`1177`, :pull:`1195`) Bug fixes ~~~~~~~~~ diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 88b4632af6..b53464a308 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -999,7 +999,7 @@ def _set_celltemp(self, model): temp_air = _tuple_from_dfs(self.weather, 'temp_air') wind_speed = _tuple_from_dfs(self.weather, 'wind_speed') arg_list = [poa, temp_air, wind_speed] - if model == 'noct_sam': + if model == self.system.noct_sam_celltemp: arg_list += [self.results.effective_irradiance] self.results.cell_temperature = model(*tuple(arg_list)) return self diff --git a/pvlib/tests/test_modelchain.py b/pvlib/tests/test_modelchain.py index a7a2f259c5..28e7889414 100644 --- a/pvlib/tests/test_modelchain.py +++ b/pvlib/tests/test_modelchain.py @@ -705,8 +705,8 @@ def test_run_model_with_weather_fuentes_temp(sapm_dc_snl_ac_system, location, assert not mc.results.ac.empty -def test_run_model_with_weather_noct_temp_temp(sapm_dc_snl_ac_system, location, - weather, mocker): +def test_run_model_with_weather_noct_sam_temp(sapm_dc_snl_ac_system, location, + weather, mocker): weather['wind_speed'] = 5 weather['temp_air'] = 10 sapm_dc_snl_ac_system.temperature_model_parameters = { From 3616e82074b72383c2b33cad37e5a44cc5b23974 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 13 Mar 2021 12:45:05 -0700 Subject: [PATCH 11/15] docstring work --- pvlib/pvsystem.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index ee56ca14da..78086ea32d 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -739,7 +739,7 @@ def fuentes_celltemp(self, poa_global, temp_air, wind_speed): Returns ------- - temperature_cell : Series or tuple of Series + numeric or tuple of numeric The modeled cell temperature [C] Notes @@ -751,8 +751,6 @@ def fuentes_celltemp(self, poa_global, temp_air, wind_speed): if you want to match the PVWatts behavior, you can override it by including a ``surface_tilt`` value in ``temperature_model_parameters``. - Notes - ----- The `temp_air` and `wind_speed` parameters may be passed as tuples to provide different values for each Array in the system. If not passed as a tuple then the same value is used for input to each Array. @@ -801,11 +799,11 @@ def noct_sam_celltemp(self, poa_global, temp_air, wind_speed, effective_irradiance : pandas Series, tuple of Series or None. The irradiance that is converted to photocurrent. If None, - assumed equal to poa_global. [W/m^2] + assumed equal to ``poa_global``. [W/m^2] Returns ------- - temperature_cell : Series or tuple of Series + numeric or tuple of numeric The modeled cell temperature [C] Notes From dcb6fbc0b46c17c90814582a9920ab8dfa7a509b Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 13 Mar 2021 12:58:59 -0700 Subject: [PATCH 12/15] additions to api --- docs/sphinx/source/api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index a82471c713..37bb09113b 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -242,6 +242,8 @@ PV temperature models pvsystem.PVSystem.sapm_celltemp pvsystem.PVSystem.pvsyst_celltemp pvsystem.PVSystem.faiman_celltemp + pvsystem.PVSystem.fuentes_celltemp + pvsystem.PVSystem.noct_sam_celltemp Temperature Model Parameters ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 84a452c27302d44bf317af8b964393fa02992973 Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sat, 13 Mar 2021 19:59:03 -0700 Subject: [PATCH 13/15] docstring reverts, raise if missing arg --- pvlib/pvsystem.py | 30 ++++++++++++++++++++---------- pvlib/tests/test_pvsystem.py | 8 ++++++++ 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 78086ea32d..fb3531971b 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -739,7 +739,7 @@ def fuentes_celltemp(self, poa_global, temp_air, wind_speed): Returns ------- - numeric or tuple of numeric + temperature_cell : Series or tuple of Series The modeled cell temperature [C] Notes @@ -788,22 +788,22 @@ def noct_sam_celltemp(self, poa_global, temp_air, wind_speed, Parameters ---------- - poa_global : pandas Series or tuple of Series - Total incident irradiance [W/m^2] + poa_global : numeric or tuple of numeric + Total incident irradiance in W/m^2. - temp_air : pandas Series or tuple of Series - Ambient dry bulb temperature [C] + temp_air : numeric or tuple of numeric + Ambient dry bulb temperature in degrees C. - wind_speed : pandas Series or tuple of Series - Wind speed [m/s] + wind_speed : numeric or tuple of numeric + Wind speed in m/s at a height of 10 meters. - effective_irradiance : pandas Series, tuple of Series or None. + effective_irradiance : numeric, tuple of numeric or None. The irradiance that is converted to photocurrent. If None, assumed equal to ``poa_global``. [W/m^2] Returns ------- - numeric or tuple of numeric + temperature_cell : numeric or tuple of numeric The modeled cell temperature [C] Notes @@ -829,9 +829,19 @@ def noct_sam_celltemp(self, poa_global, temp_air, wind_speed, def _build_kwargs_noct_sam(array): temp_model_kwargs = _build_kwargs([ - 'noct', 'eta_m_ref', 'transmittance_absorptance', + 'transmittance_absorptance', 'array_height', 'mount_standoff'], array.temperature_model_parameters) + try: + temp_model_kwargs['noct'] = \ + array.temperature_model_parameters['noct'] + temp_model_kwargs['eta_m_ref'] = \ + array.temperature_model_parameters['eta_m_ref'] + except KeyError: + msg = ('Parameter noct and eta_m_ref are required.' + ' Found {} in temperature_model_parameters.' + .format(array.temperature_model_parameters)) + raise KeyError(msg) return temp_model_kwargs return tuple( temperature.noct_sam( diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 1fc13d8c08..9ad6fc2b15 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -522,6 +522,14 @@ def test_PVSystem_noct_celltemp(mocker): assert_allclose(out, expected) +def test_PVSystem_noct_celltemp_error(): + poa_global, temp_air, wind_speed, eta_m_ref = (1000., 25., 1., 0.2) + temp_model_params = {'eta_m_ref': eta_m_ref} + system = pvsystem.PVSystem(temperature_model_parameters=temp_model_params) + with pytest.raises(KeyError): + system.noct_sam_celltemp(poa_global, temp_air, wind_speed) + + @pytest.mark.parametrize("celltemp", [pvsystem.PVSystem.faiman_celltemp, pvsystem.PVSystem.pvsyst_celltemp, From 26647833afcbfd2c632492bfb286e5e0f391d67f Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Sun, 14 Mar 2021 18:07:53 -0600 Subject: [PATCH 14/15] add types to method test --- pvlib/tests/test_pvsystem.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pvlib/tests/test_pvsystem.py b/pvlib/tests/test_pvsystem.py index 9ad6fc2b15..1c54402039 100644 --- a/pvlib/tests/test_pvsystem.py +++ b/pvlib/tests/test_pvsystem.py @@ -511,6 +511,16 @@ def test_PVSystem_noct_celltemp(mocker): poa_global, temp_air, wind_speed, effective_irradiance=None, noct=noct, eta_m_ref=eta_m_ref) assert_allclose(out, expected) + # dufferent types + out = system.noct_sam_celltemp(np.array(poa_global), np.array(temp_air), + np.array(wind_speed)) + assert_allclose(out, expected) + dr = pd.date_range(start='2020-01-01 12:00:00', end='2020-01-01 13:00:00', + freq='1H') + out = system.noct_sam_celltemp(pd.Series(index=dr, data=poa_global), + pd.Series(index=dr, data=temp_air), + pd.Series(index=dr, data=wind_speed)) + assert_series_equal(out, pd.Series(index=dr, data=expected)) # now use optional arguments temp_model_params.update({'transmittance_absorptance': 0.8, 'array_height': 2, From 9613bb8dc071efc40b3c77f3e42bb6a830e6a60e Mon Sep 17 00:00:00 2001 From: Cliff Hansen Date: Mon, 15 Mar 2021 12:18:17 -0600 Subject: [PATCH 15/15] edits from review --- pvlib/modelchain.py | 3 ++- pvlib/pvsystem.py | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index b53464a308..1547df18e6 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -999,8 +999,9 @@ def _set_celltemp(self, model): temp_air = _tuple_from_dfs(self.weather, 'temp_air') wind_speed = _tuple_from_dfs(self.weather, 'wind_speed') arg_list = [poa, temp_air, wind_speed] + kwargs = {} if model == self.system.noct_sam_celltemp: - arg_list += [self.results.effective_irradiance] + kwargs['effective_irradiance'] = self.results.effective_irradiance self.results.cell_temperature = model(*tuple(arg_list)) return self diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index fb3531971b..3314887a2b 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -797,7 +797,7 @@ def noct_sam_celltemp(self, poa_global, temp_air, wind_speed, wind_speed : numeric or tuple of numeric Wind speed in m/s at a height of 10 meters. - effective_irradiance : numeric, tuple of numeric or None. + effective_irradiance : numeric, tuple of numeric, or None. The irradiance that is converted to photocurrent. If None, assumed equal to ``poa_global``. [W/m^2] @@ -833,12 +833,14 @@ def _build_kwargs_noct_sam(array): 'array_height', 'mount_standoff'], array.temperature_model_parameters) try: + # noct_sam required args + # bundled with kwargs for simplicity temp_model_kwargs['noct'] = \ array.temperature_model_parameters['noct'] temp_model_kwargs['eta_m_ref'] = \ array.temperature_model_parameters['eta_m_ref'] except KeyError: - msg = ('Parameter noct and eta_m_ref are required.' + msg = ('Parameters noct and eta_m_ref are required.' ' Found {} in temperature_model_parameters.' .format(array.temperature_model_parameters)) raise KeyError(msg)