From 37d3fccc193727ed0e10148d4e4274269bdbfdc1 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Thu, 10 Apr 2025 16:49:48 -0400 Subject: [PATCH 1/6] make C4-C7 optional in pvlib.pvsystem.sapm --- pvlib/pvsystem.py | 20 ++++++++++++-------- tests/test_pvsystem.py | 9 +++++++++ 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 896791d86f..01e2c06820 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -2224,9 +2224,11 @@ def sapm(effective_irradiance, temp_cell, module): * 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 ``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 ``C6`` and ``C7`` + parameters are not supplied. Notes ----- @@ -2334,13 +2336,15 @@ def sapm(effective_irradiance, temp_cell, module): 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 - temp_ref))) + if 'C4' in module and 'C5' in module: + out['i_x'] = ( + module['IXO'] * (module['C4']*Ee + module['C5']*(Ee**2)) * + (1 + module['Aisc']*(temp_cell - temp_ref))) - out['i_xx'] = ( - module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) * - (1 + module['Aimp']*(temp_cell - temp_ref))) + if 'C6' in module and 'C7' in module: + out['i_xx'] = ( + module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) * + (1 + module['Aimp']*(temp_cell - temp_ref))) if isinstance(out['i_sc'], pd.Series): out = pd.DataFrame(out) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index ebd015d348..f22daffc4c 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -197,6 +197,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 = ['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') From a7731309a7d020b909836ccb835501b04ee6768f Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Thu, 10 Apr 2025 16:50:27 -0400 Subject: [PATCH 2/6] make optional in PVSystem and ModelChain as well --- pvlib/modelchain.py | 2 +- pvlib/pvsystem.py | 2 +- tests/test_modelchain.py | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/pvlib/modelchain.py b/pvlib/modelchain.py index 0f58da533a..0f5fc5f363 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 01e2c06820..1791cac1c2 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -29,7 +29,7 @@ # a dict of required parameter names for each DC power model _DC_MODEL_PARAMS = { 'sapm': { - 'C0', 'C1', 'C2', 'C3', 'C4', 'C5', 'C6', 'C7', + 'C0', 'C1', 'C2', 'C3', # C4-C7 (i_x and i_xx params) not required 'Isco', 'Impo', 'Voco', 'Vmpo', 'Aisc', 'Aimp', 'Bvoco', 'Mbvoc', 'Bvmpo', 'Mbvmp', 'N', 'Cells_in_Series', 'IXO', 'IXXO'}, diff --git a/tests/test_modelchain.py b/tests/test_modelchain.py index 1bdcb87f90..8ebd7892b5 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 ['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' From 98cdcea31c78a20d74db917df2f5cf23ba1ee11b Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Thu, 10 Apr 2025 16:50:44 -0400 Subject: [PATCH 3/6] incidental cleanup --- pvlib/pvsystem.py | 2 -- tests/test_pvsystem.py | 1 - 2 files changed, 3 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index 1791cac1c2..fd17a8b6ee 100644 --- a/pvlib/pvsystem.py +++ b/pvlib/pvsystem.py @@ -17,8 +17,6 @@ from abc import ABC, abstractmethod from typing import Optional, Union -from pvlib._deprecation import deprecated - import pvlib # used to avoid albedo name collision in the Array class from pvlib import (atmosphere, iam, inverter, irradiance, singlediode as _singlediode, spectrum, temperature) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index f22daffc4c..df448d25b9 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 From c8532ccb6e9b78e5f417f9d6df69dcf84b0d46cc Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Thu, 10 Apr 2025 16:50:52 -0400 Subject: [PATCH 4/6] whatsnew --- docs/sphinx/source/whatsnew/v0.12.1.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/sphinx/source/whatsnew/v0.12.1.rst b/docs/sphinx/source/whatsnew/v0.12.1.rst index 927cf1ede7..2dec97006b 100644 --- a/docs/sphinx/source/whatsnew/v0.12.1.rst +++ b/docs/sphinx/source/whatsnew/v0.12.1.rst @@ -15,7 +15,10 @@ Bug fixes Enhancements ~~~~~~~~~~~~ * ``pvlib.ivtools.sdm`` is now a subpackage. (:issue:`2252`, :pull:`2256`) - +* 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`) Documentation ~~~~~~~~~~~~~ From 0103952235befbcdf72f3fefde06ab523c672c0a Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Thu, 10 Apr 2025 16:54:32 -0400 Subject: [PATCH 5/6] lint --- tests/test_modelchain.py | 2 +- tests/test_pvsystem.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_modelchain.py b/tests/test_modelchain.py index 8ebd7892b5..569bc66266 100644 --- a/tests/test_modelchain.py +++ b/tests/test_modelchain.py @@ -1767,7 +1767,7 @@ def test_sapm_optional_params(sapm_dc_snl_ac_system, location): for array in sapm_dc_snl_ac_system.arrays: for key in ['C4', 'C5', 'C6', 'C7']: array.module_parameters.pop(key) - + # no error: ModelChain(sapm_dc_snl_ac_system, location) diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index df448d25b9..879dadf1f9 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -203,7 +203,7 @@ def test_sapm(sapm_module_params): } out = pvsystem.sapm(effective_irradiance, temp_cell, params_no_c4c7) assert 'i_x' not in out.keys() - assert 'i_xx' not in out.keys() + assert 'i_xx' not in out.keys() def test_PVSystem_sapm(sapm_module_params, mocker): From cf551e7d9053470ed5fea6f3d1cd7212cb2520f1 Mon Sep 17 00:00:00 2001 From: Kevin Anderson Date: Fri, 11 Apr 2025 08:27:34 -0400 Subject: [PATCH 6/6] make IXO and IXXO optional too --- pvlib/pvsystem.py | 18 +++++++++--------- tests/test_modelchain.py | 2 +- tests/test_pvsystem.py | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pvlib/pvsystem.py b/pvlib/pvsystem.py index fd17a8b6ee..f8c3d3521f 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-C7 (i_x and i_xx params) not required + # 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'}, @@ -2222,11 +2222,11 @@ def sapm(effective_irradiance, temp_cell, module): * 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. Omitted if ``C4`` and ``C5`` - parameters are not supplied. + 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. Omitted if ``C6`` and ``C7`` - parameters are not supplied. + I-V curve for modeling curve shape. Omitted if ``IXXO``, ``C6``, + and ``C7`` parameters are not supplied. Notes ----- @@ -2334,12 +2334,12 @@ def sapm(effective_irradiance, temp_cell, module): out['p_mp'] = out['i_mp'] * out['v_mp'] - if 'C4' in module and 'C5' in module: + 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 - temp_ref))) - if 'C6' in module and 'C7' in module: + 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 - temp_ref))) diff --git a/tests/test_modelchain.py b/tests/test_modelchain.py index 569bc66266..51b401830d 100644 --- a/tests/test_modelchain.py +++ b/tests/test_modelchain.py @@ -1765,7 +1765,7 @@ def test_invalid_dc_model_params(sapm_dc_snl_ac_system, cec_dc_snl_ac_system, 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 ['C4', 'C5', 'C6', 'C7']: + for key in ['IXO', 'IXXO', 'C4', 'C5', 'C6', 'C7']: array.module_parameters.pop(key) # no error: diff --git a/tests/test_pvsystem.py b/tests/test_pvsystem.py index 879dadf1f9..4473e6b0e7 100644 --- a/tests/test_pvsystem.py +++ b/tests/test_pvsystem.py @@ -197,7 +197,7 @@ def test_sapm(sapm_module_params): pd.Series(sapm_module_params)) # ensure C4-C7 are optional - optional_keys = ['C4', 'C5', 'C6', 'C7'] + 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 }