diff --git a/docs/sphinx/source/api.rst b/docs/sphinx/source/api.rst index 2c9504a091..0b9a8e20e4 100644 --- a/docs/sphinx/source/api.rst +++ b/docs/sphinx/source/api.rst @@ -212,6 +212,7 @@ Functions relevant for single diode models. .. autosummary:: :toctree: generated/ + pvsystem.calcparams_cec pvsystem.calcparams_desoto pvsystem.calcparams_pvsyst pvsystem.i_from_v @@ -419,6 +420,7 @@ ModelChain model definitions. :toctree: generated/ modelchain.ModelChain.sapm + modelchain.ModelChain.cec modelchain.ModelChain.desoto modelchain.ModelChain.pvsyst modelchain.ModelChain.pvwatts_dc diff --git a/docs/sphinx/source/whatsnew/v0.6.0.rst b/docs/sphinx/source/whatsnew/v0.6.0.rst index 6aef71b600..a7457a6487 100644 --- a/docs/sphinx/source/whatsnew/v0.6.0.rst +++ b/docs/sphinx/source/whatsnew/v0.6.0.rst @@ -53,8 +53,13 @@ API Changes dirindex functions. (:issue:`311`, :issue:`396`) * Method ModelChain.infer_dc_model now returns a tuple (function handle, model name string) instead of only the function handle (:issue:`417`) -* Add DC model methods desoto and pvsyst to ModelChain, and deprecates DC model method singlediode - (singlediode defaults to desoto until v0.7.0) (:issue:`487`) +* Add DC model methods desoto and pvsyst to ModelChain, and deprecates DC model method + singlediode (singlediode defaults to desoto until v0.7.0) (:issue:`487`) +* Add the CEC module model in pvsystem.calcparams_cec and ModelChain.cec. The CEC model + differs from the desoto model by using the parameter Adjust. Modules selected from + the SAM CEC library sam-library-cec-modules-2017-6-5.csv include the Adjust parameter + and ModelChain.infer_dc_model will now select the cec model rather than the desoto model. + (:issue:`463`) * The behavior of irradiance.perez(return_components=True) has changed. The function previously returned a tuple of total sky diffuse and an OrderedDict/DataFrame of components. The function now returns @@ -111,6 +116,8 @@ Enhancements * Add irradiance.clearness_index function. (:issue:`396`) * Add irradiance.clearness_index_zenith_independent function. (:issue:`396`) * Add checking for consistency between module_parameters and dc_model. (:issue:`417`) +* Add DC model methods ``'desoto'`` and ``'pvsyst'`` to ModelChain (:issue:`487`) +* Add the CEC module model in `pvsystem.calcparams_cec` and `ModelChain.cec`. (:issue:`463`) * Add DC model methods desoto and pvsyst to ModelChain (:issue:`487`) * Set default alpha to 1.14 in :func:`~pvlib.atmosphere.angstrom_aod_at_lambda` (:issue:`563`) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index eb381b0ce9..b1e5f07b82 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -247,7 +247,7 @@ class ModelChain(object): dc_model: None, str, or function, default None If None, the model will be inferred from the contents of system.module_parameters. Valid strings are 'sapm', - 'desoto', 'pvsyst', 'pvwatts'. The ModelChain instance will + 'desoto', 'cec', 'pvsyst', 'pvwatts'. The ModelChain instance will be passed as the first argument to a user-defined function. ac_model: None, str, or function, default None @@ -376,6 +376,8 @@ def dc_model(self, model): self._dc_model = self.sapm elif model == 'desoto': self._dc_model = self.desoto + elif model == 'cec': + self._dc_model = self.cec elif model == 'pvsyst': self._dc_model = self.pvsyst elif model == 'pvwatts': @@ -396,7 +398,11 @@ def infer_dc_model(self): params = set(self.system.module_parameters.keys()) if set(['A0', 'A1', 'C7']) <= params: return self.sapm, 'sapm' - elif set(['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s']) <= params: + elif set(['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', + 'R_s', 'Adjust']) <= params: + return self.cec, 'cec' + elif set(['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', + 'R_s']) <= params: return self.desoto, 'desoto' elif set(['gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_sh_0', 'R_sh_exp', 'R_s']) <= params: @@ -433,6 +439,24 @@ def desoto(self): return self + def cec(self): + (photocurrent, saturation_current, resistance_series, + resistance_shunt, nNsVth) = ( + self.system.calcparams_cec(self.effective_irradiance, + self.temps['temp_cell'])) + + self.diode_params = (photocurrent, saturation_current, + resistance_series, + resistance_shunt, nNsVth) + + self.dc = self.system.singlediode( + photocurrent, saturation_current, resistance_series, + resistance_shunt, nNsVth) + + self.dc = self.system.scale_voltage_current_power(self.dc).fillna(0) + + return self + def pvsyst(self): (photocurrent, saturation_current, resistance_series, resistance_shunt, nNsVth) = ( diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 7750347489..58f05c2089 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -23,23 +23,26 @@ # a dict of required parameter names for each DC power model DC_MODEL_PARAMS = { - 'sapm' : set([ + 'sapm': set([ 'A0', 'A1', 'A2', 'A3', 'A4', 'B0', 'B1', 'B2', 'B3', 'B4', 'B5', 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', 'Isco', 'Impo', 'Aisc', 'Aimp', 'Bvoco', 'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series', 'IXO', 'IXXO', 'FD']), - 'desoto' : set([ + 'desoto': set([ 'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s']), - 'pvsyst' : set([ + 'cec': set([ + 'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref', + 'R_sh_ref', 'R_s', 'Adjust']), + 'pvsyst': set([ 'gamma_ref', 'mu_gamma', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_sh_0', 'R_s', 'alpha_sc', 'EgRef', 'cells_in_series']), - 'singlediode' : set([ + 'singlediode': set([ 'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s']), - 'pvwatts' : set(['pdc0', 'gamma_pdc']) + 'pvwatts': set(['pdc0', 'gamma_pdc']) } @@ -336,6 +339,35 @@ def calcparams_desoto(self, effective_irradiance, temp_cell, **kwargs): return calcparams_desoto(effective_irradiance, temp_cell, **kwargs) + def calcparams_cec(self, effective_irradiance, temp_cell, **kwargs): + """ + Use the :py:func:`calcparams_cec` function, the input + parameters and ``self.module_parameters`` to calculate the + module currents and resistances. + + Parameters + ---------- + effective_irradiance : numeric + The irradiance (W/m2) that is converted to photocurrent. + + temp_cell : float or Series + The average cell temperature of cells within a module in C. + + **kwargs + See pvsystem.calcparams_cec for details + + Returns + ------- + See pvsystem.calcparams_cec for details + """ + + kwargs = _build_kwargs(['a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', + 'R_s', 'alpha_sc', 'Adjust', 'EgRef', 'dEgdT', + 'irrad_ref', 'temp_ref'], + self.module_parameters) + + return calcparams_cec(effective_irradiance, temp_cell, **kwargs) + def calcparams_pvsyst(self, effective_irradiance, temp_cell): """ Use the :py:func:`calcparams_pvsyst` function, the input @@ -1223,6 +1255,122 @@ def calcparams_desoto(effective_irradiance, temp_cell, return IL, I0, Rs, Rsh, nNsVth +def calcparams_cec(effective_irradiance, temp_cell, + alpha_sc, a_ref, I_L_ref, I_o_ref, R_sh_ref, R_s, + Adjust, 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 CEC + model described in [1]. The CEC model differs from the De soto et al. + model [3] by the parameter Adjust. The five values returned by + calcparams_cec can be used by singlediode to calculate an IV curve. + + Parameters + ---------- + effective_irradiance : numeric + The irradiance (W/m2) that is converted to photocurrent. + + temp_cell : numeric + The average cell temperature of cells within a module in C. + + alpha_sc : float + The short-circuit current temperature coefficient of the + module in units of A/C. + + a_ref : float + 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. + + Adjust : float + The adjustment to the temperature coefficient for short circuit + current, in percent + + EgRef : float + The energy bandgap at reference temperature in units of eV. + 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 [3]) 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. + + irrad_ref : float (optional, default=1000) + Reference irradiance in W/m^2. + + temp_ref : float (optional, default=25) + Reference cell temperature in C. + + Returns + ------- + Tuple of the following results: + + photocurrent : numeric + Light-generated current in amperes + + saturation_current : numeric + Diode saturation curent in amperes + + resistance_series : float + Series resistance in ohms + + resistance_shunt : numeric + Shunt resistance in ohms + + nNsVth : numeric + The product of the usual diode ideality factor (n, unitless), + number of cells in series (Ns), and cell thermal voltage at + specified effective irradiance and cell temperature. + + References + ---------- + [1] A. Dobos, "An Improved Coefficient Calculator for the California + Energy Commission 6 Parameter Photovoltaic Module Model", Journal of + Solar Energy Engineering, vol 134, 2012. + + [2] System Advisor Model web page. https://sam.nrel.gov. + + [3] W. De Soto et al., "Improvement and validation of a model for + photovoltaic array performance", Solar Energy, vol 80, pp. 78-88, + 2006. + + See Also + -------- + calcparams_desoto + singlediode + retrieve_sam + + ''' + + # pass adjusted temperature coefficient to desoto + return calcparams_desoto(effective_irradiance, temp_cell, + alpha_sc*(1.0 - Adjust/100), + 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) + + def calcparams_pvsyst(effective_irradiance, temp_cell, alpha_sc, gamma_ref, mu_gamma, I_L_ref, I_o_ref, diff --git a/pvlib/test/test_modelchain.py b/pvlib/test/test_modelchain.py index dac2dd5fd6..e280735db3 100644 --- a/pvlib/test/test_modelchain.py +++ b/pvlib/test/test_modelchain.py @@ -219,6 +219,7 @@ def poadc(mc): @pytest.mark.parametrize('dc_model', [ 'sapm', + pytest.param('cec', marks=requires_scipy), pytest.param('desoto', marks=requires_scipy), pytest.param('pvsyst', marks=requires_scipy), pytest.param('singlediode', marks=requires_scipy), @@ -227,16 +228,21 @@ def test_infer_dc_model(system, cec_dc_snl_ac_system, pvsyst_dc_snl_ac_system, pvwatts_dc_pvwatts_ac_system, location, dc_model, weather, mocker): dc_systems = {'sapm': system, + 'cec': cec_dc_snl_ac_system, 'desoto': cec_dc_snl_ac_system, 'pvsyst': pvsyst_dc_snl_ac_system, 'singlediode': cec_dc_snl_ac_system, 'pvwatts_dc': pvwatts_dc_pvwatts_ac_system} dc_model_function = {'sapm': 'sapm', + 'cec': 'calcparams_cec', 'desoto': 'calcparams_desoto', 'pvsyst': 'calcparams_pvsyst', 'singlediode': 'calcparams_desoto', 'pvwatts_dc': 'pvwatts_dc'} system = dc_systems[dc_model] + # remove Adjust from model parameters for desoto, singlediode + if dc_model in ['desoto', 'singlediode']: + system.module_parameters.pop('Adjust') m = mocker.spy(system, dc_model_function[dc_model]) mc = ModelChain(system, location, aoi_model='no_loss', spectral_model='no_loss') diff --git a/pvlib/test/test_pvsystem.py b/pvlib/test/test_pvsystem.py index 9315d73774..42f9505a65 100644 --- a/pvlib/test/test_pvsystem.py +++ b/pvlib/test/test_pvsystem.py @@ -379,9 +379,9 @@ def test_PVSystem_sapm_effective_irradiance(sapm_module_params, mocker): def test_calcparams_desoto(cec_module_params): - times = pd.DatetimeIndex(start='2015-01-01', periods=2, freq='12H') - effective_irradiance = pd.Series([0.0, 800.0], index=times) - temp_cell = pd.Series([25, 25], index=times) + times = pd.DatetimeIndex(start='2015-01-01', periods=3, freq='12H') + effective_irradiance = pd.Series([0.0, 800.0, 800.0], index=times) + temp_cell = pd.Series([25, 25, 50], index=times) IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_desoto( effective_irradiance, @@ -395,12 +395,44 @@ def test_calcparams_desoto(cec_module_params): EgRef=1.121, dEgdT=-0.0002677) - assert_series_equal(np.round(IL, 3), pd.Series([0.0, 6.036], index=times)) - # changed value in GH 444 for 2017-6-5 module file - assert_allclose(I0, 1.94e-9) + assert_series_equal(IL, pd.Series([0.0, 6.036, 6.096], index=times), + check_less_precise=3) + assert_series_equal(I0, pd.Series([0.0, 1.94e-9, 7.419e-8], index=times), + check_less_precise=3) + assert_allclose(Rs, 0.094) + assert_series_equal(Rsh, pd.Series([np.inf, 19.65, 19.65], index=times), + check_less_precise=3) + assert_series_equal(nNsVth, pd.Series([0.473, 0.473, 0.5127], index=times), + check_less_precise=3) + + +def test_calcparams_cec(cec_module_params): + times = pd.DatetimeIndex(start='2015-01-01', periods=3, freq='12H') + effective_irradiance = pd.Series([0.0, 800.0, 800.0], index=times) + temp_cell = pd.Series([25, 25, 50], index=times) + + IL, I0, Rs, Rsh, nNsVth = pvsystem.calcparams_cec( + effective_irradiance, + temp_cell, + alpha_sc=cec_module_params['alpha_sc'], + a_ref=cec_module_params['a_ref'], + I_L_ref=cec_module_params['I_L_ref'], + I_o_ref=cec_module_params['I_o_ref'], + R_sh_ref=cec_module_params['R_sh_ref'], + R_s=cec_module_params['R_s'], + Adjust=cec_module_params['Adjust'], + EgRef=1.121, + dEgdT=-0.0002677) + + assert_series_equal(IL, pd.Series([0.0, 6.036, 6.0896], index=times), + check_less_precise=3) + assert_series_equal(I0, pd.Series([0.0, 1.94e-9, 7.419e-8], index=times), + check_less_precise=3) assert_allclose(Rs, 0.094) - assert_series_equal(np.round(Rsh, 3), pd.Series([np.inf, 19.65], index=times)) - assert_allclose(nNsVth, 0.473) + assert_series_equal(Rsh, pd.Series([np.inf, 19.65, 19.65], index=times), + check_less_precise=3) + assert_series_equal(nNsVth, pd.Series([0.473, 0.473, 0.5127], index=times), + check_less_precise=3) def test_calcparams_pvsyst(pvsyst_module_params):