Repository navigation
GSoC 2026: Standardizing Diffuse Irradiance Components and Extending ModelChain Optical Loss Modeling #2750
Description
This issue tracks my work for Google Summer of Code 2026 on improving diffuse irradiance handling and optical loss modeling in pvlib. This project will be carried out under the supervision of @AdamRJensen and @ramaroesilva, and has two main goals:
- Standardizing the outputs of diffuse transposition models
- Extending
ModelChainto support component-specific optical loss calculations, via component-specific incident angle modifiers (IAM)
If you're interested, you can follow regular updates and discussions on my blog. Official project description on the GSoC website here.
Motivation
Currently, diffuse transposition models in pvlib.irradiance provide inconsistent outputs. Some models (e.g., Perez) can return individual components (circumsolar, isotropic, horizon), while others only return total diffuse irradiance. This was previously identified and discussed in issue #1553.
This inconsistency limits the ModelChain workflow, which:
- Applies a single angle of incidence (AOI) model
- Uses one IAM value for total irradiance
- Cannot distinguish between irradiance components. Since optical losses depend strongly on incidence angle, this reduces physical accuracy.
Proposed Changes
Phase 1: Standardize diffuse component outputs
- Introduce a consistent
return_componentsparameter across all diffuse transposition models. PR Adapts irradiance.isotropic to return_components framework. #2527 has begun addressing this by modifyingirradiance.isotropicto support this parameter, as previously implemented inirradiance.perez. - Ensure all models return:
- Circumsolar
- Isotropic
- Horizon
- Total diffuse irradiance
- For models that lack explicit components, return zeros for missing components
- Follow the structure used in
pvlib.irradiance.perez
Phase 2: Extend ModelChain for component-specific IAM
- Consider plane-of-array (POA) components instead of global irradiance
- Add use of component-specific IAM when calculating effective
$G_{POA}$ - Allow users to pass a dictionary to
aoi_model, specifying IAM model for each component - Maintain full backward compatibility
My name is Carolina Crespo, I am a PhD student at the University of Lisbon working on firm power in Iberia using PyPSA-Eur. I am a regular user of pvlib, which I teach (although at a superficial level) to 1st-year Bachelors students.
Any feedback on this plan will be very welcome, especially since this is my first time contributing to an open source project. Looking forward to getting started!
List of issues and PRs
Phase 1
- Adding
return_componentsto existing models, ensuring it returns only the components supported by the model:- Add support for return_components to Reindl #2775
- Add support for return_components to irradiance.isotropic #2787
- Remove empty horizon component from
irradiance.haydavies#2788 - Diffuse components with Klucher #2794
- Add diffuse components support:
get_sky_diffuse,poa_components, andget_total_irradiance#2800
- Minor details:
- Fix wrong reference on irradiance.reindl #2776
- Add deprecation warning for irradiance.king #2783
- Change OrderedDict to dict in
perezandperez_driesse#2789 - Keep or exclude total diffuse irradiance in the output when
return_components=True#2801 - Standardize diffuse irradiance component names and descriptions in docstrings #2827
Phase 2
- Adding support for diffuse components and component-specific IAM calculations to the Array, PVSystem, and ModelChain classes:
- Support diffuse irradiance components and component-wise IAM in ModelChain #2811
- Support diffuse component-specific IAM in
Array.get_iam()#2812 - Add support for diffuse IAM in the
ArrayandPVSystemclasses #2845 - Add support for diffuse irradiance components and component-specific IAM to
ModelChain#2846 - Add support for diffuse irradiance components and component-specific IAM to
ModelChain#2847
- Minor details:
- Add
marion_diffuse_interpolate, to be used instead ofmarion_diffusefor vectorsurface_tiltinputs #2824- Previous discussion of this in pvlib.iam.marion_integrate uses too much memory for vector inputs #1402
- Add support for
iam.schlicktoModelChain#2828 - Add support for
iam.schlicktoModelChain#2832 - Inconsistent output types between diffuse IAM functions #2837
- Set all diffuse IAM outputs to dict #2842
- Add
Activity
markcampanelli commentedon May 6, 2026
Thanks for taking this on @cbcrespo.
I posted this fixed-tilt-focused code on the Google groups page a while back. I am interested in a clean and performant way of extending the IAM corrections for diffuse POA components to trackers.
https://groups.google.com/g/pvlib-python/c/Jue2gKzaXnU/m/ndtf6HQABAAJ
cwhanse commentedon May 6, 2026
We'll need to agree on rules for assigning the missing diffuse irradiance components to the components that are returned. E.g., if a model does not separately quantify circumsolar diffuse, we include that portion of diffuse irradiance with direct normal irradiance.
adriesse commentedon May 6, 2026
Welcome @cbcrespo !!
Another option I would raise for consideration is to do this in a way that does not hard-code these three specific categories of diffuse irradiance. So one or two could simply be missing (rather than set to zero), or a new and different one could be added or substituted by a new transposition model.
At least, evaluate the consequences of the chosen code design if a new model comes along with more diffuse categories.
asdai110 commentedon May 9, 2026
Hello Carolina, your plan to standardize diffuse components and extend IAM support is a massive leap for pvlib's physical accuracy.
A quick thought from a materials perspective: As you extend ModelChain for component-specific IAM, have you considered the 'Spectral Redshift' or 'Frequency Drift' occurring in next-gen thin-film materials?
Specifically, at high incidence angles, the non-linear thermal gradient across the cell (especially in 2nm-structured substrates) can alter the effective IAM beyond traditional geometric models. Since you're already breaking down the G_{POA} components, adding a 'Thermal-Optical Coupling' parameter might be the next frontier for your PhD research. Would love to see how your framework handles these non-linearities."
ramaroesilva commentedon May 13, 2026
Hi everyone, thank you for the great input for @cbcrespo activitiy.
My two cents about the "setting to zero" part (in particular for @cwhanse, @adriesse, @kandersolar):
While this is my point of view, naturally it is open for discussion :-)
cbcrespo commentedon May 13, 2026
@markcampanelli could you expand on what you mean by expanding this to trackers? Would that not already be possible under the current implementation? IAM functions already accept either the AOI or the surface tilt, which can be an array.
cbcrespo commentedon Jun 23, 2026
I added a new blog post last week!
markcampanelli commentedon Jun 23, 2026
@cbcrespo My apologies if I sent you on a bit of a savager hunt to nowhere. I think you are correct that the low-level functions are sufficiently vectorized to allow for the tracker calculations to be made. I think a big part of my original concern was if this could be done more efficiently and compactly (and potentially also with the elimination of duplicate calculations that likely happen between the sky and ground diffuse calculations). Also, the use of the Marion method of computing the IAM-corrected diffuse effective irradiance (which assumes
sky,horizon, andgrounddiffuse components) does not seem to be supported in the ModelChain, seepvlib-python/pvlib/modelchain.py
Line 1089 in 0f7a205
markcampanelli commentedon Jun 28, 2026
It turns out that just doing the "naive" computation here is a huge memory hog (enough to take down my expensive laptop!). Instead, I have found that first doing an interpolation (e.g.,
scipy.interpolate.PchipInterpolator) of the Marion integral results for each diffuse component and then computing using this interpolant for the SAT tilt angles (for 30- or 60-minute data over 20-30 years) is more efficient. (This could probably be further optimized because many of the SAT tilt angles repeat and could be cached.) I haven't looked into yet where the memory sink happens in the integrations.adriesse commentedon Jun 29, 2026
I think I mentioned it somewhere before, but I don't think it's a good idea (and also illogical) to include the sum of the components as a separate component.
markcampanelli commentedon Jul 1, 2026
@cbcrespo I may have missed this, but was there any discussion about indicating if a given model concentrates the circumsolar and/or horizon brightening in the center of the sun disk and horizon ring, respectively.
For example, IIRC at one point the Perez model changed to provide “equivalent” concentrated values for both circumsolar and the horizon, and this affects how subsequent algorithms (like Marion’s) can/should compute the IAMs for the diffuse components.
ramaroesilva commentedon Jul 4, 2026
@markcampanelli could be worth to create a separate issue about this, and even better if in it you could provide some insights on the memory sink source
kandersolar commentedon Jul 4, 2026
Discussion about memory usage in the Marion IAM functions can go to #1402
cwhanse commentedon Jul 10, 2026
The various sky diffuse functions are being modified to return either a numeric output
sky_diffuse, or a DataFrame outputdiffuse_componentscontaining'poa_sky_diffuse','poa_isotropic','poa_circumsolar', and/or'poa_horizon'depending on what components apply for that model.The description of these outputs is not uniform among the current sky diffuse functions. I think we should make it uniform throughout this GSoC project's sequence of PRs. My suggestions below.
sky_diffuse: The sky diffuse component of irradiance on a tilted plane. [Wm⁻²]
poa_sky_diffuse: The sky diffuse component of irradiance on a tilted plane. [Wm⁻²]
poa_isotropic: The portion of sky diffuse irradiance on a tilted plane from the isotropic sky dome. [Wm⁻²]
poa_circumsolar: The circumsolar portion of sky diffuse irradiance on a tilted plane. [Wm⁻²]
poa_horizon: The horizon portion of sky diffuse irradiance on a tilted plane. [Wm⁻²]
ramaroesilva commentedon Jul 12, 2026
Overall seems good to me, but small variation below that would make structure between components more similar:
poa_isotropic: as originally proposedAlso, not sure about having both
sky_diffuseandpoa_sky_diffuse, as the naming differentiation might be confusing. Why not keep onlypoa_sky_diffuse?cwhanse commentedon Jul 12, 2026
sky_diffuseis the name of the returned quantity (array or Series).poa_sky_diffuseis the key (column) in a returned DataFramediffuse_components.From a user's point of view,
sky_diffuseandpoa_sky_diffuseare two different things:I'd be OK dropping "poa_" from all those keys. But we do need to be mindful of prior naming and the complexity of deprecating keys in returned DataFrames. I don't know if those concerns apply here.
adriesse commentedon Jul 13, 2026
I'd be ok with dropping the prefix as well.
Let's not forget #2801, which needs a decision (and perhaps further discussion) as well.
ramaroesilva commentedon Jul 14, 2026
Agree with @adriesse alert on #2801, as it impacts what we're discussing here.
If the decision in #2801 is to keep the total value as a
dictkey, personally, I don't think it's best to differentiate an array variable from a dict key - with the same timeseries associated - based on apoa_prefix. If the decision is to drop the total from thedictkey list, I would suggest addingpoa_to the output of of thereturn_components=Falsecase.adriesse commentedon Jul 14, 2026
In this case the output doesn't actually have a name. A name for the output only exists inside the function and in the documentation.
ramaroesilva commentedon Jul 14, 2026
Agreed, but what's the advantage of having it different of the
dictkey leading to exactly the same timeseries for when users don't want components?adriesse commentedon Jul 14, 2026
No advantage that I can think of.
cwhanse commentedon Jul 14, 2026
I concur with @ramaroesilva edits to the definitions I proposed.
For consistency among names, I'm ok with changing the returned Series name to
poa_sky_diffuse. To avoid breaking changes, I retract my suggestion to droppoa_from the component key names.cbcrespo commentedon Jul 15, 2026
@cwhanse @adriesse @ramaroesilva
In addition to those, for functions like
get_total_irradiance, I propose:AdamRJensen commentedon Jul 23, 2026
@cbcrespo Could you create a PR that updates the naming to be consistent for all the transposition models and in the nomenclature?
I see the issue of whether to keep the total value #2801 as a separate issue. For now, you can follow the current practice, and if we decide to change, then we can update all the functions at that time.
Edit: I see that you already started that here: #2827