Skip to content

Add BrightSun clear-sky detection method #1061

@cwhanse

Description

@cwhanse

A recent paper describes improvements on pvlib.clearsky.detect_clearsky. I've got a port of their Matlab code to a python function detect_clearsky_brightsun, and trying to keep the interface common for the two detect_clearsky functions.

Although the BrightSun method uses the similar statistics to determine clearsky times, the limits aren't constants - they are functions of zenith. And the method needs additional parameters for the duration filters and the optimization of the clear-sky model. The duration filters are different in the ~hour after sunrise/before sunset than in midday.

I have the limit and control arguments coded as dicts and provide constant dicts with the default values (which needs to change, since we don't want mutable defaults).

For the solar position related inputs (zenith and sunrise/sunset times), several options:

  • pass in a pvlib.location.Location and calculate solar position and sunrise/set times internal to detect_clearsky_brightsun. This is what I have done in the draft function, it makes the code straightforward but at the expense of a less-than-explicit interface.
  • pass zenith, sunrise and sunset as separate arguments. This seems awkward and somewhat risky, since calculating sunrise and sunset times with pvlib.solarposition.rise_set_transit_xxx can have different behaviors (e.g., using _spa the first sunset time is after the first sunrise time, even if the data start at midday, _ephem has a kwarg that controls this behavior and returns the first sunset after the first data point).
  • pass zenith only and a kwarg for a zenith angle that defines sunrise and sunset, rather than asking for sunrise/sunset as input or using the solarposition functions.

Interested in feedback about this approach before I open a PR.

Current detect_clearsky function:

def detect_clearsky(measured, clearsky, times, window_length,
                    mean_diff=75, max_diff=75,
                    lower_line_length=-5, upper_line_length=10,
                    var_diff=0.005, slope_dev=8, max_iterations=20,
                    return_components=False):

Draft new function and dicts:

# constants used for BrightSun algorithm
# dict of functions that return limits for each criteria for BrightSun
BRIGHT_SUN_LIMITS = {
    'mean_diff': _mean_max_lim,
    'max_diff': _mean_max_lim,
    'line_length': _line_length_lim,
    'slope_nstd': lambda x: 0.4,
    'slope_diff': _slope_diff_lim}


# dict of parameters for daily clear-sky model optimization for BrightSun
BRIGHT_SUN_OPTIM = {
    'min_ghi': 30, 'min_pts_per_day': 60, 'alpha_limits': (0.7, 1.5),
    'maxiter': 20}


# dict of parameters for duration criteria for BrightSun
BRIGHT_SUN_FILTERS = {'long_window': 90, 'long_fraction': (90 - 80) / 90.,
                      'separation_window': 30,
                      'rise_set_period': 90, 'rise_set_window': 10,
                      'rise_set_fraction': (10 - 2) / 10}

def detect_clearsky_brightsun(measured, clearsky, times,
                              location,
                              window_length,
                              limits=BRIGHT_SUN_LIMITS,
                              optim=BRIGHT_SUN_OPTIM,
                              filters=BRIGHT_SUN_FILTERS,
                              return_components=False):

The values for the BRIGHT_SUN_LIMITS dict are functions:

def _mean_max_lim(x):
    # limit for criteria 1 and 2 of BrightSun algorithm
    return np.piecewise(
        x, condlist=[x < 20, (x >= 20) & (x < 30), (x >= 30) & (x < 90),
                     x >= 90],
        funclist=[0.25, lambda x: np.interp(x, xp=[20, 30], fp=[0.25, 0.125]),
                  lambda x: np.interp(x, xp=[20, 30], fp=[0.125, 0.5]), 0.5])


def _line_length_lim(x):
    # for criteria 3 of BrightSun algorithm
    # limit at zenith >= 30 is 0.5 per email with Jaime Bright and Matlab code
    # J. Bright states he believes the value of 2 in the 2020 paper is
    # incorrect
    return np.piecewise(x, condlist=[x < 30, x >= 30],
        funclist=[lambda x: np.interp(x, xp=[0, 30], fp=[7, 0.5]), 0.5])


def _slope_diff_lim(x):
    # for criteria 5 of BrightSun algorithm
    return np.piecewise(x, condlist=[x < 30, x >= 30],
        funclist=[lambda x: np.interp(x, xp=[0, 30], fp=[45, 15]), 15])

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions