diff --git a/docs/sphinx/source/whatsnew/v0.12.1.rst b/docs/sphinx/source/whatsnew/v0.12.1.rst index 54ddcc00d..8520d227c 100644 --- a/docs/sphinx/source/whatsnew/v0.12.1.rst +++ b/docs/sphinx/source/whatsnew/v0.12.1.rst @@ -17,6 +17,10 @@ Enhancements * :py:mod:`pvlib.ivtools.sdm` is now a subpackage. (:issue:`2252`, :pull:`2256`) * Add a function for estimating PVsyst SDM parameters from IEC 61853-1 matrix data (:py:func:`~pvlib.ivtools.sdm.fit_pvsyst_iec61853_sandia_2025`). (:issue:`2185`, :pull:`2429`) +* The parameters for the Ix and Ixx points are now optional when using + :py:func:`pvlib.pvsystem.sapm` directly and through + :py:class:`~pvlib.pvsystem.PVSystem` and :py:class:`~pvlib.modelchain.ModelChain`. + (:issue:`2402`, :pull:`2433`) * Add optional arguments ``temperature_ref`` and ``irradiance_ref`` to :py:func:`~pvlib.pvsystem.sapm`(:issue:`2432`, :pull:`2434`) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 0f58da533..0f5fc5f36 100644 --- a/pvlib/modelchain.py +++ b/pvlib/modelchain.py @@ -611,7 +611,7 @@ def infer_dc_model(self): """Infer DC power model from Array module parameters.""" params = _common_keys( tuple(array.module_parameters for array in self.system.arrays)) - if {'A0', 'A1', 'C7'} <= params: + if {'A0', 'A1', 'C3'} <= params: return self.sapm, 'sapm' elif {'a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s', 'Adjust'} <= params: diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 9e96111af..b6a388a59 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -27,10 +27,10 @@ # a dict of required parameter names for each DC power model _DC_MODEL_PARAMS = { 'sapm': { - 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', + # i_x and i_xx params (IXO, IXXO, C4-C7) not required + 'C0', 'C1', 'C2', 'C3', 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco', - 'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series', - 'IXO', 'IXXO'}, + 'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series'}, 'desoto': { 'alpha_sc', 'a_ref', 'I_L_ref', 'I_o_ref', 'R_sh_ref', 'R_s'}, @@ -2229,9 +2229,11 @@ def sapm(effective_irradiance, temp_cell, module, *, temperature_ref=25, * v_mp : Voltage at maximum-power point (V) * p_mp : Power at maximum-power point (W) * i_x : Current at module V = 0.5Voc, defines 4th point on I-V - curve for modeling curve shape + curve for modeling curve shape. Omitted if ``IXO``, ``C4``, and + ``C5`` parameters are not supplied. * i_xx : Current at module V = 0.5(Voc+Vmp), defines 5th point on - I-V curve for modeling curve shape + I-V curve for modeling curve shape. Omitted if ``IXXO``, ``C6``, + and ``C7`` parameters are not supplied. Notes ----- @@ -2335,13 +2337,15 @@ def sapm(effective_irradiance, temp_cell, module, *, temperature_ref=25, out['p_mp'] = out['i_mp'] * out['v_mp'] - out['i_x'] = ( - module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) * - (1 + module['Aisc']*(temp_cell - temperature_ref))) + if 'IXO' in module and 'C4' in module and 'C5' in module: + out['i_x'] = ( + module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) * + (1 + module['Aisc']*(temp_cell - temperature_ref))) - out['i_xx'] = ( - module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) * - (1 + module['Aimp']*(temp_cell - temperature_ref))) + if 'IXXO' in module and 'C6' in module and 'C7' in module: + out['i_xx'] = ( + module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) * + (1 + module['Aimp']*(temp_cell - temperature_ref))) if isinstance(out['i_sc'], pd.Series): out = pd.DataFrame(out) diff --git a/tests/test_modelchain.py b/tests/test_modelchain.py index 1bdcb87f9..51b401830 100644 --- a/tests/test_modelchain.py +++ b/tests/test_modelchain.py @@ -1762,6 +1762,16 @@ def test_invalid_dc_model_params(sapm_dc_snl_ac_system, cec_dc_snl_ac_system, ModelChain(pvwatts_dc_pvwatts_ac_system, location, **kwargs) +def test_sapm_optional_params(sapm_dc_snl_ac_system, location): + # inference works when the optional (i_x, i_xx) SAPM parameters are missing + for array in sapm_dc_snl_ac_system.arrays: + for key in ['IXO', 'IXXO', 'C4', 'C5', 'C6', 'C7']: + array.module_parameters.pop(key) + + # no error: + ModelChain(sapm_dc_snl_ac_system, location) + + @pytest.mark.parametrize('model', [ 'dc_model', 'ac_model', 'aoi_model', 'spectral_model', 'temperature_model', 'losses_model' diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index ebd015d34..4473e6b0e 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -17,7 +17,6 @@ from pvlib.location import Location from pvlib.pvsystem import FixedMount from pvlib import temperature -from pvlib._deprecation import pvlibDeprecationWarning from pvlib.tools import cosd from pvlib.singlediode import VOLTAGE_BUILTIN @@ -197,6 +196,15 @@ def test_sapm(sapm_module_params): pvsystem.sapm(effective_irradiance, temp_cell, pd.Series(sapm_module_params)) + # ensure C4-C7 are optional + optional_keys = ['IXO', 'IXXO', 'C4', 'C5', 'C6', 'C7'] + params_no_c4c7 = { + k: v for k, v in sapm_module_params.items() if k not in optional_keys + } + out = pvsystem.sapm(effective_irradiance, temp_cell, params_no_c4c7) + assert 'i_x' not in out.keys() + assert 'i_xx' not in out.keys() + def test_PVSystem_sapm(sapm_module_params, mocker): mocker.spy(pvsystem, 'sapm')