Radiation Conversion#

This page documents how SunPeek converts available radiation measurements into the tilted irradiance components used by collector arrays.

If a Power Check report shows Radiation: modeled instead of Radiation: measured, the required radiation components were calculated by one of these conversion models instead of being used directly from measured real sensors.

The current implementation focuses on practically important cases where collector-plane radiation can be derived from measured global, diffuse, beam, or DNI inputs. In SunPeek, these measurements are mapped to the array input slots in_global, in_beam, in_diffuse and in_dni. Depending on the sensor orientation, they can be interpreted as measurements:

  • in the plane of the array, i.e. POA irradiance;

  • in the horizontal plane, i.e. horizontal irradiance;

  • in an incline plane different from POA.

Terminology#

SunPeek uses the following radiation quantities:

Symbol / slot

Name

Meaning

in_global

measured global irradiance

Hemispherical irradiance measured in the plane of the mapped sensor.

in_diffuse

measured diffuse irradiance

Diffuse irradiance measured in the plane of the mapped sensor.

in_beam

measured beam irradiance

Beam irradiance measured in the plane of the mapped sensor.

in_dni

measured direct normal irradiance

Direct irradiance measure normal to the sun beam.

rd_gti

global tilted irradiance

Global irradiance in the plane of collector array (POA).

rd_bti

beam tilted irradiance

Direct/beam component in the POA.

rd_dti

diffuse tilted irradiance

Diffuse component in the POA.

GHI

global horizontal irradiance

Global irradiance on the horizontal plane.

DHI

diffuse horizontal irradiance

Diffuse irradiance on the horizontal plane.

BHI

beam horizontal irradiance

Beam irradiance on the horizontal plane.

GTI

global tilted irradiance

Global irradiance on a tilted plane (POA or other).

DTI

diffuse tilted irradiance

Diffuse irradiance on a tilted plane (POA or other).

BTI

beam tilted irradiance

Beam irradiance on a tilted plane (POA or other).

DNI

direct normal irradiance

Direct irradiance measure normal to the sun beam.

AOI

angle of incidence

Angle between the sun beam and the surface normal.

The measured in_global value is not automatically the same as rd_gti. They are identical only if the radiation sensor plane and the collector array plane have the same tilt and azimuth.

Input slots and supported strategies#

Array radiation conversion uses four standard input slots:

in_global, in_beam, in_diffuse, in_dni

SunPeek describes available radiation inputs with a four-digit input pattern in this exact slot order. Each digit is 1 if the corresponding sensor slot is available and 0 if it is missing. For example:

1000 = in_global only
1010 = in_global + in_diffuse
1001 = in_global + in_dni
0110 = in_beam + in_diffuse

Same-plane POA cases without radiation modeling#

If the measured radiation components are already in the collector array plane, SunPeek can use them directly or calculate the missing POA component by simple closure. No decomposition or transposition model is required.

Implemented cases include:

0110 = BTI + DTI -> GTI by closure
0111 = BTI + DTI + DNI -> GTI by closure
1110 = GTI + BTI + DTI -> direct use of measured POA components
1111 = GTI + BTI + DTI + DNI -> direct use of measured POA components
1010 = GTI + DTI -> BTI by closure
1001 = GTI + DNI -> BTI by DNI geometry, DTI by closure
0011 = DTI + DNI -> BTI by DNI geometry, GTI by closure

For these cases, in_global, in_beam and in_diffuse must describe the collector-plane quantities GTI, BTI and DTI.

Horizontal global irradiance cases#

If in_global has tilt = 0 deg, SunPeek interprets it as global horizontal irradiance, i.e. GHI. The model first completes the horizontal component set GHI, DNI, DHI and then transposes those components into the collector array plane.

Implemented cases include:

1000 = GHI only -> decompose GHI into DNI and DHI
1010 = GHI + DHI -> calculate DNI from measured GHI and DHI
1001 = GHI + DNI -> calculate DHI from measured GHI and DNI
0011 = DHI + DNI -> calculate GHI from measured DHI and DNI
1100 = GHI + BHI -> calculate DHI and DNI from measured GHI and BHI

For 1010, in_diffuse must also be horizontal and is interpreted as DHI. For 0011, in_diffuse must be horizontal and is interpreted as DHI. For 1100, in_beam must be horizontal and is interpreted as BHI. in_dni is direct normal irradiance and does not need orientation metadata.

Global irradiance in an inclined sensor plane#

If in_global is not horizontal, SunPeek interprets it as global irradiance in the measured sensor plane. The sensor plane may be identical to the collector array plane or may have a different fixed tilt and azimuth.

Implemented modeled case:

1000 = measured global irradiance in sensor plane
0011 = measured diffuse irradiance in sensor plane + DNI

SunPeek reverse-transposes this measured global irradiance into horizontal components and then forward-transposes those components into the collector array plane. For 0011, SunPeek first calculates beam irradiance in the diffuse sensor plane from DNI and closes global irradiance in that sensor plane.

measured global irradiance in sensor plane
   -> GHI, DNI, DHI
   -> rd_gti, rd_bti, rd_dti in collector array plane

If the sensor plane and collector array plane are identical, SunPeek preserves the measured global irradiance as rd_gti and calculates only the missing POA components.

DNI-only input, i.e. input pattern 0001, is not sufficient to calculate the full set of tilted components. DNI defines the direct normal beam component, but it does not define the diffuse irradiance contribution. Therefore SunPeek does not calculate rd_gti, rd_bti and rd_dti from in_dni alone.

Implemented transformations#

Both supported in_global-only strategies use the same intermediate representation: horizontal irradiance components. The same representation is also used for the supported auxiliary-input cases.

GHI, DNI, DHI

This means the radiation conversion can be understood as three separate transformations:

  1. measured global irradiance in an arbitrary sensor plane -> horizontal components;

  2. measured global horizontal irradiance -> horizontal components;

  3. horizontal components -> collector-plane irradiance.

The implemented model chains are built from these transformations:

measured global irradiance in sensor plane
   -> POA-to-horizontal reverse transposition
   -> GHI, DNI, DHI
   -> horizontal-to-POA forward transposition
   -> rd_gti, rd_bti, rd_dti in collector array plane

measured GHI
   -> GHI decomposition
   -> GHI, DNI, DHI
   -> horizontal-to-POA forward transposition
   -> rd_gti, rd_bti, rd_dti in collector array plane

In code this is implemented in:

The core model functions live in sunpeek.core_methods.virtuals.radiation:

get_horizontal_irradiance_from_gti(...)
get_horizontal_irradiance_from_ghi(...)
get_horizontal_irradiance_from_ghi_dhi(...)
get_horizontal_irradiance_from_ghi_dni(...)
get_horizontal_irradiance_from_dhi_dni(...)
get_horizontal_irradiance_from_dti_dni(...)
get_horizontal_irradiance_from_ghi_bhi(...)
get_poa_irradiance_from_horizontal_irradiance(...)
get_poa_irradiance_from_gti(...)
get_poa_irradiance_from_ghi(...)
get_beam_irradiance_from_dni(...)

The virtual sensor strategy StrategyTiltedIrradiance_from_measured_inputs classifies the available inputs by input pattern and sensor plane, then orchestrates the required conversion. Cases that only need direct use, closure, or DNI geometry are handled by the feedthrough strategy.

Transformation 1: POA global to horizontal components#

This transformation is used when in_global is not horizontal. The measured value is interpreted as global irradiance in the sensor plane. The model reverse-transposes this measurement into horizontal components:

measured global irradiance in sensor plane -> GHI, DNI, DHI

SunPeek uses pvlib.irradiance.gti_dirint() for this step.

Required inputs are:

  • measured global irradiance in the sensor plane

  • sensor tilt and azimuth

  • solar zenith and azimuth

  • timestamp index

  • ground albedo (default=0.25 (pvlib default value))

The AOI for the radiation sensor plane is calculated with pvlib.irradiance.aoi().

The implementation uses the perez-driesse model option. The delta_kt_prime stability index is used only when the time index is regular and sufficiently short-spaced. For very short, irregular, or coarse time series, SunPeek disables it so virtual sensor calculations can still run on small data windows.

Transformation 2: horizontal global to horizontal components#

This transformation is used when in_global is horizontal. The measured value is already GHI. Depending on the available auxiliary measurements, SunPeek completes the horizontal component set in one of three ways.

GHI only#

If only in_global is available, the model decomposes this hemispherical horizontal measurement into direct normal and diffuse horizontal components:

measured GHI -> GHI, DNI, DHI

SunPeek uses pvlib.irradiance.erbs_driesse() for this step by default. The implementation also exposes the older pvlib.irradiance.erbs() model as an alternative at helper-function level.

Required inputs are:

  • measured global horizontal irradiance

  • solar zenith

  • timestamp index

The decomposed components are returned as:

ghi = measured in_global
dni = decomposed direct normal irradiance
dhi = decomposed diffuse horizontal irradiance

GHI + DHI#

If in_diffuse is also available and horizontal, SunPeek interprets it as DHI. The missing direct normal component is calculated from the measured horizontal global and diffuse components:

measured GHI, measured DHI -> GHI, DNI, DHI

The implementation uses pvlib.irradiance.dni(), which is equivalent to using the horizontal beam component BHI = GHI - DHI and projecting it onto the sun-normal plane, with pvlib’s low-sun safeguards.

GHI + DNI / DHI + DNI / GHI + BHI#

If in_dni is also available, SunPeek uses the measured DNI and calculates the missing diffuse horizontal component by closure:

BHI = DNI * cos(solar zenith angle)
DHI = GHI - BHI

This avoids empirical decomposition of GHI because the direct normal component is already measured.

If measured DHI + DNI is available, SunPeek calculates GHI by closure. If measured GHI + BHI is available, SunPeek calculates DHI by closure and derives DNI from the horizontal components.

These completed horizontal components are then passed to the same forward-transposition step used by the reverse-transposition chain.

Transformation 3: horizontal components to POA components#

This transformation is shared by both supported model chains. It transposes horizontal components into the collector array plane:

GHI, DNI, DHI -> POA global, POA direct, POA diffuse

SunPeek uses pvlib.irradiance.get_total_irradiance() with the perez-driesse model for this step.

The returned plane-of-array components are mapped to SunPeek virtual sensors:

poa_global  -> rd_gti
poa_direct  -> rd_bti
poa_diffuse -> rd_dti

Special case: same-plane POA global measurement#

If the radiation sensor plane and the collector array plane have the same orientation in the reverse-transposition strategy, SunPeek preserves the measured global irradiance:

\[rd\_gti = in\_global\]

The beam component is still calculated from the modeled horizontal components. The diffuse component is then calculated by closure:

\[rd\_dti = rd\_gti - rd\_bti\]

This keeps the modeled output consistent with the measured global irradiance in the collector plane. In this same-plane case, the following relationship is enforced:

\[rd\_gti = rd\_bti + rd\_dti\]

If the sensor plane and collector plane differ, SunPeek uses the forward transposed poa_global and poa_diffuse values.

The GHI decomposition strategy does not preserve in_global as rd_gti, because in_global is a horizontal-plane measurement and rd_gti is a collector-plane quantity. It always uses forward transposition to calculate rd_gti.

Orientation metadata#

Both in_global-only strategies require orientation metadata for the measured in_global sensor:

  • tilt

  • azim

For fixed-mounted arrays, the collector array orientation is taken from the array mounting configuration. For single-axis tracking arrays, SunPeek uses the time-dependent collector orientation calculated as virtual sensors surface_tilt and surface_azimuth.

The orientation decides which strategy is used:

  • horizontal sensor planes select horizontal-component completion and forward transposition;

  • collector-plane sensor inputs select feedthrough, closure, DNI geometry, or GTI reverse transposition as needed;

  • inclined sensor planes select reverse transposition from the sensor plane, followed by forward transposition to the collector plane.

For single-axis tracking arrays, non-horizontal radiation sensors are interpreted as collector-plane sensors moving with the tracker. Static sensor orientation metadata cannot currently describe a sensor plane that differs from the moving collector plane.

Validity limits#

POA-to-horizontal reverse transposition#

Reverse transposition from measured GTI uses pvlib.irradiance.gti_dirint(). Its validity limits are based on AOI and POA irradiance.

The pvlib documentation for pvlib.irradiance.gti_dirint() warns that model performance is poor for approximately:

AOI > 80 deg
AND
POA irradiance > 200 W/m²

The AOI limit is consistent with Marion’s GTI-DIRINT validation discussion, where increased errors are reported for incidence angles in the 80–90 deg range. The exact 200 W/m² irradiance threshold is taken from the pvlib implementation documentation and should therefore be treated as a pvlib-recommended practical validity flag rather than a separately derived threshold from Marion (2015).

This is not a hard cutoff in pvlib. It is a documented region where GTI-DIRINT results should be treated with caution.

The implemented reverse-transposition path calls gti_dirint with calculate_gt_90=False. According to the pvlib API, this means the model returns NaN for AOI >= 90 deg. This is separate from the softer poor-performance warning at AOI > 80 deg and POA irradiance > 200 W/m².

SunPeek currently uses these limits in validation and diagnostic comparisons. Validation comparisons keep only timestamps with AOI <= 80 deg or POA irradiance <= 200 W/m². Equivalently, they exclude timestamps where both limits are exceeded at the same time. The core model does not yet mask or remove calculated values inside this documented poor-performance region. A future implementation may expose a helper or virtual sensor such as:

rd_conversion_valid

or a function equivalent to:

valid = (aoi <= 80) | (poa_global <= 200)

Horizontal-global decomposition#

The GHI decomposition path uses pvlib.irradiance.erbs_driesse(). Its validity limit is based on solar zenith.

erbs_driesse has max_zenith=87 by default. For timestamps where solar_zenith > max_zenith, pvlib sets the decomposed DNI to 0:

solar_zenith > 87 deg -> DNI = 0

This cutoff is a built-in guard against unstable DNI values at very low sun positions.

Radiation model validation#

The implementation is validated with FHW and Condat operational demo data from the sunpeek_exampledata package.

Radiation validation#

The following figures show an example validation window from the FHW yearly demo data utilizing the reverse-transposition conversion. The model uses only measured rd_gti as input. The measured rd_bti and rd_dti sensors are shown only as independent references.

Comparisons are filtered to timestamps with:

reference AOI <= 80 deg
OR
reference GTI <= 200 W/m²
Radiation conversion validation for a June time window

Modeled and measured tilted radiation components for a selected June time window. The plot compares modeled BTI and DTI against the corresponding measured FHW reference sensors.#

Radiation conversion validation errors for a June time window

Error time series for the same June validation window, calculated as modeled minus measured irradiance.#

Power Check validation#

The radiation conversion also changes the automatic Power Check strategy selection. Since BTI/DTI can be modeled from in_global, the Power Check can be calculated using Formula 2 instead of Formula 1. This applies both when in_global is measured in the collector/sensor plane and when in_global is measured horizontally as GHI.

The numerical impact on Power Check is validated with the yearly FHW demo data for the measured-GTI conversion path. The test compares three Power Check runs:

  • Formula 2 with measured rd_bti and rd_dti. This is used as the reference because it applies the same Power Check formula with independently measured radiation components.

  • Formula 1 with measured rd_gti. This represents the previous fallback when only global tilted irradiance is available.

  • Formula 2 with modeled rd_bti and rd_dti from measured rd_gti. This represents the new radiation-conversion workflow.

The comparison is performed on common ISO Power Check intervals only, because Formula 1 and Formula 2 apply different irradiance restrictions and may therefore select different valid intervals.

Radiation conversion impact on Power Check

Power Check output of (left) Formula 1 using measured GTI and (right) Formula 2 using modeled BTI/DTI, compared to the reference case using Formula 2 with measured BTI/DTI.#

All three approaches have similar performance for the FHW demo data, though Formula 2 using modeled BTI/DTI has a smaller mean absolute error and a smaller absolute bias than Formula 1. For other locations, Formula 2 using modeled BTI/DTI may have a larger advantage over Formula 1 because it uses a time-resolved beam/diffuse split instead of the fixed global-radiation approximation used by Formula 1.

Power Check validation for single-axis tracking arrays#

For single-axis tracking arrays, the GTI-only conversion path was checked with the yearly Condat solar plant demo data. Since no independent measured BTI/DTI reference is available for this case, the comparison is not a radiation model validation. Instead, it compares the resulting Power Check estimates from:

  • Formula 1 using measured tracker-plane GTI;

  • Formula 2 using BTI/DTI modeled from the same measured GTI.

Only common Power Check intervals are compared. The two estimates are very similar for the demo data, indicating that the radiation conversion produces radiation values of acceptable quality for the Power Check for this tracked example.

Power Check comparison for single-axis tracking radiation conversion

Power Check estimates for the single-axis tracking demo data. Formula 1 uses measured tracker-plane GTI. Formula 2 uses BTI/DTI modeled from the same measured GTI utilizing reverse-transposition.#

Limitations#

The current implementation has the following limitations:

  • tracking arrays, non-horizontal radiation sensors are interpreted as collector-plane sensors moving with the tracker

  • tracking sensors in a plane different from the moving collector plane are not supported

  • DNI-only input is not sufficient for full tilted irradiance conversion

  • model quality depends on sensor orientation metadata

  • high-AOI conditions can produce poor reverse-transposition results

  • GHI decomposition accuracy depends on the decomposition model and will be validated separately when suitable measured reference data are available

  • pvlib may emit convergence warnings for some timestamps

  • AOI and irradiance validity limits are currently used in validation, but not applied as hard output masks

References#

The implementation is based on pvlib’s irradiance modeling functions:

Relevant literature:

  • Erbs, D. G., Klein, S. A., and Duffie, J. A. (1982). Estimation of the diffuse radiation fraction for hourly, daily and monthly-average global radiation. Solar Energy 28(4), 293-302. DOI: 10.1016/0038-092X(82)90302-4.

  • Marion, B. (2015). A model for deriving the direct normal and diffuse horizontal irradiance from the global tilted irradiance. Solar Energy 122, 1037-1046. DOI: 10.1016/j.solener.2015.10.024.

  • Driesse, A., Jensen, A. R., and Perez, R. (2024). A continuous form of the Perez diffuse sky model for forward and reverse transposition. Solar Energy 267. DOI: 10.1016/j.solener.2023.112093.