From 961911d080e2123c6ef005443626d39ff8de8334 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sat, 23 Jun 2018 08:51:54 -0700 Subject: [PATCH 1/4] losses_parameters --- docs/sphinx/source/whatsnew/v0.6.0.rst | 3 ++ pvlib/pvsystem.py | 52 ++++++++++++++------------ pvlib/test/test_modelchain.py | 45 +++++++++++++++++++++- pvlib/test/test_pvsystem.py | 5 ++- 4 files changed, 79 insertions(+), 26 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.6.0.rst b/docs/sphinx/source/whatsnew/v0.6.0.rst index 2f205edf04..bfd1896992 100644 --- a/docs/sphinx/source/whatsnew/v0.6.0.rst +++ b/docs/sphinx/source/whatsnew/v0.6.0.rst @@ -6,6 +6,9 @@ v0.6.0 (___, 2018) API Changes ~~~~~~~~~~~ * pvsystem.calcparams_desoto now requires arguments for each module model parameter. +* Add losses_parameters attribute to PVSystem objects and remove the **kwargs + support from PVSystem.pvwatts_losses. Enables custom losses specification + in ModelChain calculations. (:issue:``) Enhancements diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 96ac7d3d90..acba012c00 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -119,8 +119,7 @@ def __init__(self, modules_per_string=1, strings_per_inverter=1, inverter=None, inverter_parameters=None, racking_model='open_rack_cell_glassback', - name=None, - **kwargs): + losses_parameters=None, name=None, **kwargs): self.name = name @@ -150,6 +149,11 @@ def __init__(self, else: self.inverter_parameters = inverter_parameters + if losses_parameters is None: + self.losses_parameters = {} + else: + self.losses_parameters = losses_parameters + self.racking_model = racking_model def __repr__(self): @@ -304,7 +308,7 @@ def calcparams_desoto(self, effective_irradiance, temp_cell, **kwargs): kwargs = _build_kwargs(['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s', 'alpha_sc', 'EgRef', 'dEgdT'], self.module_parameters) - + return calcparams_desoto(effective_irradiance, temp_cell, **kwargs) def sapm(self, effective_irradiance, temp_cell, **kwargs): @@ -462,7 +466,7 @@ def first_solar_spectral_loss(self, pw, airmass_absolute): coefficients = None return atmosphere.first_solar_spectral_correction(pw, - airmass_absolute, + airmass_absolute, module_type, coefficients) @@ -597,15 +601,17 @@ def pvwatts_dc(self, g_poa_effective, temp_cell): self.module_parameters['gamma_pdc'], **kwargs) - def pvwatts_losses(self, **kwargs): + def pvwatts_losses(self): """ Calculates DC power losses according the PVwatts model using - :py:func:`pvwatts_losses`. No attributes are used in this - calculation, but all keyword arguments will be passed to the - function. + :py:func:`pvwatts_losses` and ``self.losses_parameters``.` See :py:func:`pvwatts_losses` for details. """ + kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch', + 'wiring', 'connections', 'lid', + 'nameplate_rating', 'age', 'availability'], + self.losses_parameters) return pvwatts_losses(**kwargs) def pvwatts_ac(self, pdc): @@ -946,12 +952,12 @@ def physicaliam(aoi, n=1.526, K=4., L=0.002): def calcparams_desoto(effective_irradiance, temp_cell, - alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, + alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, EgRef=1.121, dEgdT=-0.0002677, irrad_ref=1000, temp_ref=25): ''' - Calculates five parameter values for the single diode equation at - effective irradiance and cell temperature using the De Soto et al. + Calculates five parameter values for the single diode equation at + effective irradiance and cell temperature using the De Soto et al. model described in [1]. The five values returned by calcparams_desoto can be used by singlediode to calculate an IV curve. @@ -968,34 +974,34 @@ def calcparams_desoto(effective_irradiance, temp_cell, module in units of A/C. a_ref : float - The product of the usual diode ideality factor (n, unitless), + The product of the usual diode ideality factor (n, unitless), number of cells in series (Ns), and cell thermal voltage at reference conditions, in units of V. I_L_ref : float The light-generated current (or photocurrent) at reference conditions, in amperes. - + I_o_ref : float The dark or diode reverse saturation current at reference conditions, in amperes. - + R_sh_ref : float The shunt resistance at reference conditions, in ohms. - + R_s : float The series resistance at reference conditions, in ohms. EgRef : float The energy bandgap at reference temperature in units of eV. - 1.121 eV for crystalline silicon. EgRef must be >0. For parameters + 1.121 eV for crystalline silicon. EgRef must be >0. For parameters from the SAM CEC module database, EgRef=1.121 is implicit for all cell types in the parameter estimation algorithm used by NREL. dEgdT : float The temperature dependence of the energy bandgap at reference - conditions in units of 1/K. May be either a scalar value - (e.g. -0.0002677 as in [1]) or a DataFrame (this may be useful if + conditions in units of 1/K. May be either a scalar value + (e.g. -0.0002677 as in [1]) or a DataFrame (this may be useful if dEgdT is a modeled as a function of temperature). For parameters from the SAM CEC module database, dEgdT=-0.0002677 is implicit for all cell types in the parameter estimation algorithm used by NREL. @@ -1143,7 +1149,7 @@ def calcparams_desoto(effective_irradiance, temp_cell, # Boltzmann constant in eV/K k = 8.617332478e-05 - + # reference temperature Tref_K = temp_ref + 273.15 Tcell_K = temp_cell + 273.15 @@ -1152,9 +1158,9 @@ def calcparams_desoto(effective_irradiance, temp_cell, nNsVth = a_ref * (Tcell_K / Tref_K) - # In the equation for IL, the single factor effective_irradiance is - # used, in place of the product S*M in [1]. effective_irradiance is - # equivalent to the product of S (irradiance reaching a module's cells) * + # In the equation for IL, the single factor effective_irradiance is + # used, in place of the product S*M in [1]. effective_irradiance is + # equivalent to the product of S (irradiance reaching a module's cells) * # M (spectral adjustment factor) as described in [1]. IL = effective_irradiance / irrad_ref * \ (I_L_ref + alpha_sc * (Tcell_K - Tref_K)) @@ -1164,7 +1170,7 @@ def calcparams_desoto(effective_irradiance, temp_cell, # Rsh = Rsh_ref * (S_ref / S) where S is broadband irradiance reaching # the module's cells. If desired this model behavior can be duplicated # by applying reflection and soiling losses to broadband plane of array - # irradiance and not applying a spectral loss modifier, i.e., + # irradiance and not applying a spectral loss modifier, i.e., # spectral_modifier = 1.0. Rsh = R_sh_ref * (irrad_ref / effective_irradiance) Rs = R_s diff --git a/pvlib/test/test_modelchain.py b/pvlib/test/test_modelchain.py index de8ba049c9..b5ed4a91c2 100644 --- a/pvlib/test/test_modelchain.py +++ b/pvlib/test/test_modelchain.py @@ -292,7 +292,7 @@ def test_spectral_models(system, location, spectral_model): columns=['precipitable_water']) mc = ModelChain(system, location, dc_model='sapm', aoi_model='no_loss', spectral_model=spectral_model) - spectral_modifier = mc.run_model(times=times, + spectral_modifier = mc.run_model(times=times, weather=weather).spectral_modifier assert isinstance(spectral_modifier, (pd.Series, float, int)) @@ -320,6 +320,49 @@ def test_losses_models(pvwatts_dc_pvwatts_ac_system, location, losses_model, assert_series_equal(ac, expected, check_less_precise=2) +@requires_scipy +def test_losses_models_pvwatts(pvwatts_dc_pvwatts_ac_system, location, mocker): + age = 1 + pvwatts_dc_pvwatts_ac_system.losses_parameters = dict(age=age) + m = mocker.spy(pvsystem, 'pvwatts_losses') + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', + aoi_model='no_loss', spectral_model='no_loss', + losses_model='pvwatts') + times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') + mc.run_model(times) + + assert m.call_count == 1 + assert pvsystem.pvwatts_losses.assert_called_with(age=age) + assert isinstance(mc.ac, (pd.Series, pd.DataFrame)) + assert not mc.ac.empty + + +@requires_scipy +def test_losses_models_ext_def(pvwatts_dc_pvwatts_ac_system, location, mocker): + m = mocker.spy(__name__, 'constant_losses') + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', + aoi_model='no_loss', spectral_model='no_loss', + losses_model=constant_losses) + times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') + mc.run_model(times) + + assert m.call_count == 1 + assert isinstance(mc.ac, (pd.Series, pd.DataFrame)) + assert not mc.ac.empty + + +@requires_scipy +def test_losses_models_no_loss(pvwatts_dc_pvwatts_ac_system, location): + mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', + aoi_model='no_loss', spectral_model='no_loss', + losses_model='no_loss') + times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') + + assert mc.losses_model == mc.no_extra_losses + mc.run_model(times) + assert mc.losses == 1 + + @pytest.mark.parametrize('model', [ 'dc_model', 'ac_model', 'aoi_model', 'spectral_model', 'losses_model', 'temp_model', 'losses_model' diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 289b0f6577..b0fed46ec9 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -1156,9 +1156,10 @@ def test_PVSystem_pvwatts_dc_kwargs(mocker): def test_PVSystem_pvwatts_losses(mocker): mocker.spy(pvsystem, 'pvwatts_losses') system = make_pvwatts_system_defaults() - expected = 15 age = 1 - out = system.pvwatts_losses(age=age) + system.losses_parameters = dict(age=age) + expected = 15 + out = system.pvwatts_losses() pvsystem.pvwatts_losses.assert_called_once_with(age=age) assert out < expected From ed6ed433ccb44760a3f18a04449ca63f8f9d0458 Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Sat, 23 Jun 2018 14:55:48 -0700 Subject: [PATCH 2/4] add tests --- docs/sphinx/source/whatsnew/v0.6.0.rst | 5 ++- pvlib/test/test_modelchain.py | 61 +++++++++++--------------- 2 files changed, 28 insertions(+), 38 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.6.0.rst b/docs/sphinx/source/whatsnew/v0.6.0.rst index bfd1896992..9bdc912af9 100644 --- a/docs/sphinx/source/whatsnew/v0.6.0.rst +++ b/docs/sphinx/source/whatsnew/v0.6.0.rst @@ -5,10 +5,11 @@ v0.6.0 (___, 2018) API Changes ~~~~~~~~~~~ -* pvsystem.calcparams_desoto now requires arguments for each module model parameter. +* pvsystem.calcparams_desoto now requires arguments for each module model + parameter. (:issue:`462`) * Add losses_parameters attribute to PVSystem objects and remove the **kwargs support from PVSystem.pvwatts_losses. Enables custom losses specification - in ModelChain calculations. (:issue:``) + in ModelChain calculations. (:issue:`484`) Enhancements diff --git a/pvlib/test/test_modelchain.py b/pvlib/test/test_modelchain.py index b5ed4a91c2..f64026020e 100644 --- a/pvlib/test/test_modelchain.py +++ b/pvlib/test/test_modelchain.py @@ -1,3 +1,5 @@ +import sys + import numpy as np import pandas as pd from numpy import nan @@ -78,11 +80,19 @@ def pvwatts_dc_pvwatts_ac_system(sam_data): return system -@pytest.fixture() +@pytest.fixture def location(): return Location(32.2, -111, altitude=700) +@pytest.fixture +def weather(): + times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') + weather = pd.DataFrame({'ghi': [500, 0], 'dni': [800, 0], 'dhi': [100, 0]}, + index=times) + return weather + + def test_ModelChain_creation(system, location): mc = ModelChain(system, location) @@ -302,64 +312,43 @@ def constant_losses(mc): mc.ac *= mc.losses -@requires_scipy -@pytest.mark.parametrize('losses_model, expected', [ - ('pvwatts', [163.280464174, 0]), - ('no_loss', [190.028186986, 0]), - (constant_losses, [171.025368287, 0]) -]) -def test_losses_models(pvwatts_dc_pvwatts_ac_system, location, losses_model, - expected): - mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', - aoi_model='no_loss', spectral_model='no_loss', - losses_model=losses_model) - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - ac = mc.run_model(times).ac - - expected = pd.Series(np.array(expected), index=times) - assert_series_equal(ac, expected, check_less_precise=2) - - -@requires_scipy -def test_losses_models_pvwatts(pvwatts_dc_pvwatts_ac_system, location, mocker): +def test_losses_models_pvwatts(pvwatts_dc_pvwatts_ac_system, location, weather, + mocker): age = 1 pvwatts_dc_pvwatts_ac_system.losses_parameters = dict(age=age) m = mocker.spy(pvsystem, 'pvwatts_losses') mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', aoi_model='no_loss', spectral_model='no_loss', losses_model='pvwatts') - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - mc.run_model(times) - + mc.run_model(weather.index, weather=weather) assert m.call_count == 1 - assert pvsystem.pvwatts_losses.assert_called_with(age=age) + m.assert_called_with(age=age) assert isinstance(mc.ac, (pd.Series, pd.DataFrame)) assert not mc.ac.empty -@requires_scipy -def test_losses_models_ext_def(pvwatts_dc_pvwatts_ac_system, location, mocker): - m = mocker.spy(__name__, 'constant_losses') +def test_losses_models_ext_def(pvwatts_dc_pvwatts_ac_system, location, weather, + mocker): + m = mocker.spy(sys.modules[__name__], 'constant_losses') mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', aoi_model='no_loss', spectral_model='no_loss', losses_model=constant_losses) - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - mc.run_model(times) - + mc.run_model(weather.index, weather=weather) assert m.call_count == 1 assert isinstance(mc.ac, (pd.Series, pd.DataFrame)) + assert mc.losses == 0.9 assert not mc.ac.empty -@requires_scipy -def test_losses_models_no_loss(pvwatts_dc_pvwatts_ac_system, location): +def test_losses_models_no_loss(pvwatts_dc_pvwatts_ac_system, location, weather, + mocker): + m = mocker.spy(pvsystem, 'pvwatts_losses') mc = ModelChain(pvwatts_dc_pvwatts_ac_system, location, dc_model='pvwatts', aoi_model='no_loss', spectral_model='no_loss', losses_model='no_loss') - times = pd.date_range('20160101 1200-0700', periods=2, freq='6H') - assert mc.losses_model == mc.no_extra_losses - mc.run_model(times) + mc.run_model(weather.index, weather=weather) + assert m.call_count == 0 assert mc.losses == 1 From dc066b5ecdb98e9d3d3e252137c3984582bca6ca Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Tue, 26 Jun 2018 10:56:09 -0700 Subject: [PATCH 3/4] style fixes --- docs/sphinx/source/whatsnew/v0.6.0.rst | 2 +- pvlib/pvsystem.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sphinx/source/whatsnew/v0.6.0.rst b/docs/sphinx/source/whatsnew/v0.6.0.rst index 4829f186fa..d79d3702ef 100644 --- a/docs/sphinx/source/whatsnew/v0.6.0.rst +++ b/docs/sphinx/source/whatsnew/v0.6.0.rst @@ -7,7 +7,7 @@ API Changes ~~~~~~~~~~~ * pvsystem.calcparams_desoto now requires arguments for each module model parameter. (:issue:`462`) -* Add losses_parameters attribute to PVSystem objects and remove the **kwargs +* Add losses_parameters attribute to PVSystem objects and remove the kwargs support from PVSystem.pvwatts_losses. Enables custom losses specification in ModelChain calculations. (:issue:`484`) * pvsystem.calcparams_desoto now requires arguments for each module model parameter. diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index acba012c00..dc6dde8369 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -611,7 +611,7 @@ def pvwatts_losses(self): kwargs = _build_kwargs(['soiling', 'shading', 'snow', 'mismatch', 'wiring', 'connections', 'lid', 'nameplate_rating', 'age', 'availability'], - self.losses_parameters) + self.losses_parameters) return pvwatts_losses(**kwargs) def pvwatts_ac(self, pdc): From 16c9c1f742e172680863b20d1cfbfbc645716aab Mon Sep 17 00:00:00 2001 From: Will Holmgren Date: Tue, 26 Jun 2018 17:52:49 -0700 Subject: [PATCH 4/4] fix documentation --- docs/sphinx/source/whatsnew/v0.6.0.rst | 1 - pvlib/pvsystem.py | 3 +++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.6.0.rst b/docs/sphinx/source/whatsnew/v0.6.0.rst index d79d3702ef..180ba3712d 100644 --- a/docs/sphinx/source/whatsnew/v0.6.0.rst +++ b/docs/sphinx/source/whatsnew/v0.6.0.rst @@ -10,7 +10,6 @@ API Changes * Add losses_parameters attribute to PVSystem objects and remove the kwargs support from PVSystem.pvwatts_losses. Enables custom losses specification in ModelChain calculations. (:issue:`484`) -* pvsystem.calcparams_desoto now requires arguments for each module model parameter. * removed irradiance parameter from ModelChain.run_model and ModelChain.prepare_inputs diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index dc6dde8369..fcf29a941b 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -99,6 +99,9 @@ class PVSystem(object): racking_model : None or string, default 'open_rack_cell_glassback' Used for cell and module temperature calculations. + losses_parameters : None, dict or Series, default None + Losses parameters as defined by PVWatts or other. + name : None or string, default None **kwargs