Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
1c459d3
Add return_components to isotropic
cbcrespo Jun 9, 2026
4148c53
Change OrderedDict to dict
cbcrespo Jun 17, 2026
f3e69b3
Add tests for isotropic with return_components=True
cbcrespo Jun 17, 2026
fdde55c
Merge branch 'main' of https://github.com/pvlib/pvlib-python into iso…
cbcrespo Jun 22, 2026
97a34f6
Add what's new entry
cbcrespo Jun 22, 2026
dcd202c
Add support for 'return_components'
cbcrespo Jun 24, 2026
722f6fd
Add tests
cbcrespo Jun 29, 2026
2eb64ca
Raise error if return_components=True used with king or klucher
cbcrespo Jul 1, 2026
8d698e3
Update pvlib/irradiance.py
cbcrespo Jul 8, 2026
fbf1e30
Merge branch 'main' into diffuse-components-support
cbcrespo Jul 8, 2026
4be6430
Adjust docstrings
cbcrespo Jul 17, 2026
41c3ff0
Fix list indentation
cbcrespo Jul 23, 2026
a3b9391
Merge branch 'main' into diffuse-components-support
cbcrespo Jul 23, 2026
9455e83
Merge branch 'main' into pr/2800
AdamRJensen Jul 30, 2026
51c08b3
Merge branch 'diffuse-components-support' of https://github.com/cbcre…
AdamRJensen Jul 30, 2026
6eebc16
Merge branch 'main' into pr/2800
AdamRJensen Jul 30, 2026
d360853
Add more tests
cbcrespo Jul 30, 2026
eddb1c3
Minor fix
cbcrespo Jul 30, 2026
1795658
Add whatsnew entry
cbcrespo Jul 30, 2026
0c5552d
Merge branch 'main' into diffuse-components-support
cbcrespo Aug 4, 2026
4871948
Add get_iam_diffuse to the Array and PVSystem classes
cbcrespo Aug 5, 2026
6cbb1aa
Set all IAM outputs to dict
cbcrespo Aug 11, 2026
8772a7c
Add whatsnew entry
cbcrespo Aug 11, 2026
720c8ea
Minor change to docstrings
cbcrespo Aug 11, 2026
b4270a2
Docstring changes
cbcrespo Aug 12, 2026
f09a971
Add schlick_diffuse
cbcrespo Aug 12, 2026
e3a5a29
Merge branch 'main' of https://github.com/pvlib/pvlib-python
cbcrespo Aug 14, 2026
11375de
Merge branch 'iam-dict-output' into array-iam
cbcrespo Aug 14, 2026
7911c17
Add error test
cbcrespo Aug 14, 2026
d9bf100
Add Dataframe support
cbcrespo Aug 14, 2026
ae7d017
Add whatsnew entry
cbcrespo Aug 14, 2026
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
7 changes: 7 additions & 0 deletions docs/sphinx/source/whatsnew/v0.16.0.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ Breaking Changes
* Removed the deprecated ``server`` keyword argument from
:py:func:`pvlib.iotools.sodapro.get_cams`. Use ``url`` instead.
(:issue:`2767`, :pull:`2766`)
* Changed the output type of :py:func:`pvlib.iam.marion_ruiz_diffuse`
and :py:func:`pvlib.iam.schlick_diffuse` from tuple to ``dict``, to be
consistent with :py:func:`pvlib.iam.marion_diffuse`. (:issue:`2837`,
:pull:`2842`)

Deprecations
~~~~~~~~~~~~
Expand Down Expand Up @@ -66,6 +70,9 @@ Enhancements
(:issue:`2828`, :pull:`2832`)
* Allow variables from multiple datasets to be requested at once in
:py:func:`~pvlib.iotools.get_merra2`. (:pull:`2839`)
* Add support for diffuse IAM in the :py:class:`pvlib.pvsystem.Array` and
:py:class:`pvlib.pvsystem.PVSystem` classes (see `pvlib.pvsystem.Array.get_iam_diffuse`
and `pvlib.pvsystem.PVSystem.get_iam_diffuse`). (:issue:`2812`, :pull:`2845`)


Documentation
Expand Down
40 changes: 25 additions & 15 deletions pvlib/iam.py
Original file line number Diff line number Diff line change
Expand Up @@ -316,9 +316,12 @@ def martin_ruiz(aoi, a_r=0.16):

def martin_ruiz_diffuse(surface_tilt, a_r=0.16, c1=0.4244, c2=None):
'''
Determine the incidence angle modifiers (iam) for diffuse sky and
Determine the incidence angle modifiers (IAM) for sky diffuse and
ground-reflected irradiance using the Martin and Ruiz incident angle model.

As described in [1]_, the IAMs result from integrals that assume the
incoming sky diffuse and ground-reflected irradiance are isotropic.

Parameters
----------
surface_tilt: float or array-like, default 0
Expand Down Expand Up @@ -346,11 +349,12 @@ def martin_ruiz_diffuse(surface_tilt, a_r=0.16, c1=0.4244, c2=None):

Returns
-------
iam_sky : numeric
The incident angle modifier for sky diffuse
iam : dict
Incident Angle Modifier (see :term:`iam`) values for each type of
diffuse irradiance:

iam_ground : numeric
The incident angle modifier for ground-reflected diffuse
* 'sky': radiation from the sky dome
* 'ground': radiation reflected from the ground

Notes
-----
Expand Down Expand Up @@ -419,7 +423,9 @@ def martin_ruiz_diffuse(surface_tilt, a_r=0.16, c1=0.4244, c2=None):
iam_sky = pd.Series(iam_sky, index=out_index, name='iam_sky')
iam_gnd = pd.Series(iam_gnd, index=out_index, name='iam_ground')

return iam_sky, iam_gnd
iam = {'sky': iam_sky, 'ground': iam_gnd}

return iam


def interp(aoi, theta_ref, iam_ref, method='linear', normalize=True):
Expand Down Expand Up @@ -583,8 +589,8 @@ def sapm(aoi, module, upper=None):

def marion_diffuse(model, surface_tilt, **kwargs):
"""
Determine diffuse irradiance incidence angle modifiers using Marion's
method of integrating over solid angle.
Determine diffuse irradiance incidence angle modifiers (IAM) using
Marion's method of integrating over solid angle.

Parameters
----------
Expand All @@ -603,7 +609,8 @@ def marion_diffuse(model, surface_tilt, **kwargs):
Returns
-------
iam : dict
IAM values for each type of diffuse irradiance:
Incident Angle Modifier (see :term:`iam`) values for each type of
diffuse irradiance:

* 'sky': radiation from the sky dome (zenith <= 90)
* 'horizon': radiation from the region of the sky near the horizon
Expand Down Expand Up @@ -859,7 +866,7 @@ def schlick(aoi):

def schlick_diffuse(surface_tilt):
r"""
Determine the incidence angle modifiers (IAM) for diffuse sky and
Determine the incidence angle modifiers (IAM) for sky diffuse and
ground-reflected irradiance on a tilted surface using the Schlick
incident angle model.

Expand Down Expand Up @@ -890,11 +897,12 @@ def schlick_diffuse(surface_tilt):

Returns
-------
iam_sky : numeric
The incident angle modifier for sky diffuse.
iam : dict
Incident Angle Modifier (see :term:`iam`) values for each type of
diffuse irradiance:

iam_ground : numeric
The incident angle modifier for ground-reflected diffuse.
* 'sky': radiation from the sky dome
* 'ground': radiation reflected from the ground

See Also
--------
Expand Down Expand Up @@ -961,7 +969,9 @@ def schlick_diffuse(surface_tilt):
cuk = pd.Series(cuk, surface_tilt.index)
cug = pd.Series(cug, surface_tilt.index)

return cuk, cug
iam = {'sky': cuk, 'ground': cug}

return iam


def _get_model(model_name):
Expand Down
5 changes: 5 additions & 0 deletions pvlib/irradiance.py
Original file line number Diff line number Diff line change
Expand Up @@ -697,6 +697,11 @@ def isotropic(surface_tilt, dhi, return_components=False):
* poa_isotropic: The portion of sky diffuse irradiance on a tilted
plane from the isotropic sky dome. [Wm⁻²]

diffuse_components : Dict (array input) or DataFrame (Series input)
Keys/columns are:
* poa_sky_diffuse: Total sky diffuse
* poa_isotropic

References
----------
.. [1] Loutzenhiser P.G. et al. "Empirical validation of models to
Expand Down
151 changes: 135 additions & 16 deletions pvlib/pvsystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ def get_aoi(self, solar_zenith, solar_azimuth):
@_unwrap_single_value
def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
dni_extra=None, airmass=None, albedo=None,
model='haydavies', **kwargs):
model='haydavies', diffuse_components=False, **kwargs):
"""
Uses :py:func:`pvlib.irradiance.get_total_irradiance` to
calculate the plane of array irradiance components on the tilted
Expand All @@ -334,6 +334,11 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
Ground surface albedo. [unitless]
model : String, default 'haydavies'
Irradiance model.
diffuse_components : bool, default False
If `True`, returns values for the different diffuse irradiance
components available from the selected model
(e.g., isotropic, circumsolar, horizon brightening).
If `False`, only the total diffuse irradiance is returned.

kwargs
Extra parameters passed to
Expand Down Expand Up @@ -373,7 +378,9 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
array.get_irradiance(solar_zenith, solar_azimuth,
dni, ghi, dhi,
dni_extra=dni_extra, airmass=airmass,
albedo=albedo, model=model, **kwargs)
albedo=albedo, model=model,
diffuse_components=diffuse_components,
**kwargs)
for array, dni, ghi, dhi, albedo in zip(
self.arrays, dni, ghi, dhi, albedo
)
Expand All @@ -382,8 +389,8 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
@_unwrap_single_value
def get_iam(self, aoi, iam_model='physical'):
"""
Determine the incidence angle modifier using the method specified by
``iam_model``.
Determine the incidence angle modifier for direct irradiance
using the method specified by ``iam_model``.

Parameters for the selected IAM model are expected to be in
``PVSystem.module_parameters``. Default parameters are available for
Expand Down Expand Up @@ -411,6 +418,48 @@ def get_iam(self, aoi, iam_model='physical'):
return tuple(array.get_iam(aoi, iam_model)
for array, aoi in zip(self.arrays, aoi))

@_unwrap_single_value
def get_iam_diffuse(self, surface_tilt, iam_model='marion_diffuse',
marion_model=None, **kwargs):
"""
Determine the incidence angle modifier for diffuse irradiance using the
method specified by ``iam_model``.

Parameters for the selected IAM model are expected to be in
``Array.module_parameters``. Default parameters are available for
the 'marion_diffuse' and 'martin_ruiz_diffuse' models.

Parameters
----------
surface_tilt : float or Series
The tilt angle of the surface in degrees.
iam_model : string, default 'marion_diffuse'
The IAM model to be used. Valid strings are 'marion_diffuse',
'martin_ruiz_diffuse', and 'schlick_diffuse'.
marion_model : string, default None
The IAM function to evaluate across a solid angle. Only used when
``iam_model='marion_diffuse'``. Must be one of 'ashrae',
'physical', 'martin_ruiz', 'sapm', and 'schlick'.

kwargs : dict, optional
Additional keyword arguments passed to the IAM model function.

Returns
-------
iam_diffuse : dict or DataFrame
The AOI modifiers for different diffuse irradiance components.
Included components depend on the selected ``iam_model``.

Raises
------
ValueError
if `iam_model` is not a valid model name.
"""
surface_tilt = self._validate_per_array(surface_tilt)
return tuple(array.get_iam_diffuse(tilt, iam_model=iam_model,
marion_model=marion_model, **kwargs)
for array, tilt in zip(self.arrays, surface_tilt))

@_unwrap_single_value
def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
effective_irradiance=None, longwave_down=None):
Expand Down Expand Up @@ -1093,7 +1142,7 @@ def get_aoi(self, solar_zenith, solar_azimuth):

def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
dni_extra=None, airmass=None, albedo=None,
model='haydavies', **kwargs):
model='haydavies', diffuse_components=False, **kwargs):
"""
Get plane of array irradiance components.

Expand Down Expand Up @@ -1121,6 +1170,11 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
Ground surface albedo. [unitless]
model : String, default 'haydavies'
Irradiance model.
diffuse_components : bool, default False
If `True`, returns values for the different diffuse irradiance
components available from the selected model
(e.g., isotropic, circumsolar, horizon brightening).
If `False`, only the total diffuse irradiance is returned.

kwargs
Extra parameters passed to
Expand Down Expand Up @@ -1161,20 +1215,23 @@ def get_irradiance(self, solar_zenith, solar_azimuth, dni, ghi, dhi,
airmass = atmosphere.get_relative_airmass(solar_zenith)

orientation = self.mount.get_orientation(solar_zenith, solar_azimuth)
return irradiance.get_total_irradiance(orientation['surface_tilt'],
orientation['surface_azimuth'],
solar_zenith, solar_azimuth,
dni, ghi, dhi,
dni_extra=dni_extra,
airmass=airmass,
albedo=albedo,
model=model,
**kwargs)
return irradiance.get_total_irradiance(
orientation['surface_tilt'],
orientation['surface_azimuth'],
solar_zenith, solar_azimuth,
dni, ghi, dhi,
dni_extra=dni_extra,
airmass=airmass,
albedo=albedo,
model=model,
diffuse_components=diffuse_components,
**kwargs
)

def get_iam(self, aoi, iam_model='physical'):
"""
Determine the incidence angle modifier using the method specified by
``iam_model``.
Determine the incidence angle modifier for direct irradiance
using the method specified by ``iam_model``.

Parameters for the selected IAM model are expected to be in
``Array.module_parameters``. Default parameters are available for
Expand Down Expand Up @@ -1213,6 +1270,68 @@ def get_iam(self, aoi, iam_model='physical'):
else:
raise ValueError(model + ' is not a valid IAM model')

def get_iam_diffuse(self, surface_tilt, iam_model='marion_diffuse',
marion_model=None, **kwargs):
"""
Determine the incidence angle modifier for various diffuse irradiance
components using the method specified by ``iam_model``.

Parameters for the selected IAM model are expected to be in
``Array.module_parameters``. Default parameters are available for
the 'marion_diffuse' and 'martin_ruiz_diffuse' models.

Parameters
----------
surface_tilt : float or Series
The tilt angle of the surface in degrees.
iam_model : string, default 'marion_diffuse'
The IAM model to be used. Valid strings are 'marion_diffuse',
'martin_ruiz_diffuse' and 'schlick_diffuse'.
marion_model : string, default None
The IAM function to evaluate across a solid angle. Only used when
``iam_model='marion_diffuse'``. Must be one of 'ashrae',
'physical', 'martin_ruiz' and 'sapm'.

kwargs : dict, optional
Additional keyword arguments passed to the IAM model function.

Returns
-------
iam_diffuse : dict or DataFrame
The AOI modifiers for different diffuse irradiance components.
Included components depend on the selected ``iam_model``.

Raises
------
ValueError
if `iam_model` is not a valid model name.
ValueError
if `iam_model` is 'marion_diffuse' and `marion_model` is None.
"""
model = iam_model.lower()
if model == 'marion_diffuse' and marion_model is None:
raise ValueError('marion_model must be specified when '
'iam_model="marion_diffuse"')
if model in ['marion_diffuse', 'martin_ruiz_diffuse',
'schlick_diffuse']:
func = getattr(iam, model) # get function at pvlib.iam
# get all parameters from function signature to retrieve them from
# module_parameters if present
params = set(inspect.signature(func).parameters.keys())
kwargs.update(_build_kwargs(params, self.module_parameters))
if iam_model == 'marion_diffuse':
iams = func(model=marion_model, surface_tilt=surface_tilt,
**kwargs)
else:
iams = func(surface_tilt=surface_tilt, **kwargs)
else:
raise ValueError(model + ' is not a valid diffuse IAM model')

if isinstance(surface_tilt, pd.Series):
iams = pd.DataFrame(iams, index=surface_tilt.index)

return iams

def get_cell_temperature(self, poa_global, temp_air, wind_speed, model,
effective_irradiance=None, longwave_down=None):
"""
Expand Down
Loading
Loading