Skip to content

Add optional arguments temperature_ref and irradiance_ref to pvsystem.sapm #2434

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 16 commits into from
Apr 21, 2025
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/sphinx/source/whatsnew/v0.12.1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ Bug fixes
Enhancements
~~~~~~~~~~~~
* ``pvlib.ivtools.sdm`` is now a subpackage. (:issue:`2252`, :pull:`2256`)

* Add optional arguments `reference_temperature` and `reference_irradiance` to
:py:func:`~pvlib.pvsystem.sapm`(:issue:`2432`, :pull:`2434`)

Documentation
~~~~~~~~~~~~~
Expand Down
31 changes: 17 additions & 14 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -2195,7 +2195,8 @@ def _parse_raw_sam_df(csvdata):
return df


def sapm(effective_irradiance, temp_cell, module):
def sapm(effective_irradiance, temp_cell, module, reference_temperature=25,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def sapm(effective_irradiance, temp_cell, module, reference_temperature=25,
def sapm(effective_irradiance, temp_cell, module, *, reference_temperature=25,

Consider using kwarg-only parameters to enforce readability of code. For future issues debugging, ...
Completely optional suggestion, it's not a common trend in pvlib.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with what this is doing actually... could you elaborate or forward a link for me to review?
You noted it's optional / uncommon in pvlib currently, so I'll just let others comment whether to go ahead with this as I have no idea 😅

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Quick lookup on the internet and you will have a ton of information more extensive, but here is a short example, where all statements are expected to print Hola Dax. The star means the following arguments are only allowed to be passed in as keyword-only (parameter_name=value):

def hola_dax(greet, name):
    print(f"{greet} {name}")

# allowed uses
hola_dax("Hola", "Dax")
hola_dax("Hola", name="Dax")
hola_dax(greet="Hola", name="Dax")
hola_dax(name="Dax", greet="Hola")

# however
def hola_dax(greet, *, name):
    print(f"{greet} {name}")
# only allows using the forms:
hola_dax("Hola", name="Dax")
hola_dax(greet="Hola", name="Dax")
# this raises an error:
hola_dax("Hola", "Dax")  # TypeError: hola_dax() takes 1 positional argument but 2 were given

# and the form
def hola_dax(*, greet, name):
    print(f"{greet} {name}")
# only allows:
hola_dax(greet="Hola", name="Dax")
hola_dax(name="Dax", greet="Hola")

This way, you can always enforce people to make readable code.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right ok got it! Seems like a good suggestion to me. Thanks @echedey-ls

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://pvlib-python--2434.org.readthedocs.build/en/2434/reference/generated/pvlib.pvsystem.sapm.html#pvlib.pvsystem.sapm
Should * show here in the function, and if so is there any need to document that below in the parameter descriptions...?

reference_irradiance=1000):
'''
The Sandia PV Array Performance Model (SAPM) generates 5 points on a
PV module's I-V curve (Voc, Isc, Ix, Ixx, Vmp/Imp) according to
Expand All @@ -2205,7 +2206,7 @@ def sapm(effective_irradiance, temp_cell, module):
----------
effective_irradiance : numeric
Irradiance reaching the module's cells, after reflections and
adjustment for spectrum. [W/m2]
adjustment for spectrum. [Wm⁻²]

temp_cell : numeric
Cell temperature [C].
Expand All @@ -2214,6 +2215,12 @@ def sapm(effective_irradiance, temp_cell, module):
A dict or Series defining the SAPM parameters. See the notes section
for more details.

reference_temperature : numeric, optional
Reference temperature [C]

reference_irradiance : numeric, optional
Reference irradiance [Wm⁻²]

Returns
-------
A DataFrame with the columns:
Expand Down Expand Up @@ -2284,16 +2291,11 @@ def sapm(effective_irradiance, temp_cell, module):
pvlib.temperature.sapm_module
'''

# TODO: someday, change temp_ref and irrad_ref to reference_temperature and
# reference_irradiance and expose
temp_ref = 25
irrad_ref = 1000

q = constants.e # Elementary charge in units of coulombs
kb = constants.k # Boltzmann's constant in units of J/K

# avoid problem with integer input
Ee = np.array(effective_irradiance, dtype='float64') / irrad_ref
Ee = np.array(effective_irradiance, dtype='float64') / reference_irradiance

# set up masking for 0, positive, and nan inputs
Ee_gt_0 = np.full_like(Ee, False, dtype='bool')
Expand All @@ -2316,31 +2318,32 @@ def sapm(effective_irradiance, temp_cell, module):
out = OrderedDict()

out['i_sc'] = (
module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell - temp_ref)))
module['Isco'] * Ee * (1 + module['Aisc']*(temp_cell -
reference_temperature)))

out['i_mp'] = (
module['Impo'] * (module['C0']*Ee + module['C1']*(Ee**2)) *
(1 + module['Aimp']*(temp_cell - temp_ref)))
(1 + module['Aimp']*(temp_cell - reference_temperature)))

out['v_oc'] = np.maximum(0, (
module['Voco'] + cells_in_series * delta * logEe +
Bvoco*(temp_cell - temp_ref)))
Bvoco*(temp_cell - reference_temperature)))

out['v_mp'] = np.maximum(0, (
module['Vmpo'] +
module['C2'] * cells_in_series * delta * logEe +
module['C3'] * cells_in_series * ((delta * logEe) ** 2) +
Bvmpo*(temp_cell - temp_ref)))
Bvmpo*(temp_cell - reference_temperature)))

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)))
(1 + module['Aisc']*(temp_cell - reference_temperature)))

out['i_xx'] = (
module['IXXO'] * (module['C6']*Ee + module['C7']*(Ee**2)) *
(1 + module['Aimp']*(temp_cell - temp_ref)))
(1 + module['Aimp']*(temp_cell - reference_temperature)))

if isinstance(out['i_sc'], pd.Series):
out = pd.DataFrame(out)
Expand Down