Skip to content

Support diffuse irradiance components and component-wise IAM in ModelChain #2811

Description

@cbcrespo

This issue is part of my GSoC 2026 project. The first goal was to add support for return_components to all diffuse transposition models in pvlib, which was previously supported by some models (e.g. perez) but not others. This parameter allows the user to choose whether they want only the total diffuse irradiance, or are interested in the split between different diffuse components (sky diffuse, circumsolar, horizon brightening).

This has now been completed for all diffuse transposition models in pvlib except for klucher, which does not inherently support different components. This means that we can now implement component-wise IAM calculations in ModelChain - which is the second part of my GSoC project. In practice, this means treating circumsolar as direct, and calculating a separate IAM for direct and each of the available diffuse components out of sky isotropic, horizon, and ground.

The end goal is to enable ModelChain to calculate effective irradiance using component-wise incidence angle modifiers (IAM), rather than applying a single IAM to direct irradiance and assuming no diffuse optical losses (current behavior).

The first step is to make sure that the ModelChain workflow also supports diffuse components, which is currently not the case. After that, component-wise IAM can be introduced. Both of these steps require changes to the three classes involved in the ModelChain framework: Array, PVSystem, and ModelChain itself. The ModelChain workflow relies on the PVSystem class, which in turn relies on the Array class.

The goal of this overarching issue is to discuss the overall plan. Other issues will be created for more in-depth discussions of more specific points.

Currently, the plan is as follows:

  • Array class:
    • Array.get_irradiance(): add a boolean diffuse_components argument (which gets passed to irradiance.get_total_irradiance())
    • Array.get_iam(): currently only returns IAM for direct irradiance. Support for diffuse component-specific IAM must be added. Issue Support diffuse component-specific IAM in Array.get_iam() #2812 discusses how to implement this.
  • PVSystem class:
    • PVSystem.get_irradiance(): add a boolean diffuse_components argument (which gets passed to Array.get_irradiance())
    • PVSystem.get_iam(): currently only returns IAM for direct irradiance. Support for diffuse component-specific IAM must be added. How this should be done depends on the decision about the architecture of Array.get_iam.
  • ModelChain class:
    • diffuse_components parameter should be added on init
      • passing diffuse_components=True and transposition_model='klucher' should throw an error
    • aoi_model is a parameter defined on init.
      • currently all direct IAM models are supported except for schlick. Is there a reason for this?
    • ModelChain.prepare_inputs():
      • calls PVSystem.get_irradiance(). Would need a small change to support diffuse_components.
    • ModelChain.aoi_model is a property. It has a setter that attributes different functions to it via user choice (defined on init via the aoi_model parameter) or via infer_aoi_model() if necessary. One possible approach is to rename the current aoi_model to aoi_model_direct and create a new aoi_model_diffuse, but the implementation details of this should probably be discussed in a separate issue.
      • Direct: all existing direct IAM models are supported except for schlick. Is there a reason for this?
      • Diffuse: currently not implemented in ModelChain. martin_ruiz_diffuse, marion_diffuse, and schlick_diffuse should all be allowed. One caveat is that marion_diffuse is an integration method which uses one of the direct IAM models from ‘ashrae’, ‘physical’, ‘martin_ruiz’, ‘sapm’, and ‘schlick’. This probably requires adding an additional optional aoi_model_marion parameter, which is only used if aoi_model_diffuse is marion.
        • Note: as noted by @markcampanelli on GSoC 2026: Standardizing Diffuse Irradiance Components and Extending ModelChain Optical Loss Modeling #2750, marion_diffuse has a high memory use on trackers, due to integration over a solid angle for a timeseries of surface_tilt as opposed to a single value. Over a long timeseries, this can generate redundant calculations (if the same angle is repeated twice). One possible solution is to first do an interpolation of the marion_diffuse integral results for each diffuse components and then use that to get the IAM for the timeseries of tilt angles, as suggested by @markcampanelli. I think this deserves a more in-depth discussion in a separate issue.
    • ModelChain.effective_irradiance_model()
      • calculates effective irradiance as:
        • spect_mod * (total_irrad['poa_direct'] * aoi_mod + fd * total_irrad['poa_diffuse'])
        • fd is 1 by default, meaning no diffuse optical losses.
      • should be changed to something more like:
        • spect_mod * ((total_irrad['poa_direct'] + total_irrad['poa_circumsolar']) * aoi_mod + iam_isotropic * total_irrad['poa_isotropic'] + iam_horizon * total_irrad['poa_horizon'] + iam_ground * total_irrad['poa_ground'])
        • but while being mindful of which components are indeed present.

Finally, as you can see, this plan requires extensive changes, but all those changes are interdependent. I am aware it is generally not good to create huge PRs with many changes, but in this case, I was wondering how you all would advise me to split the work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions