Beispiel #1
0
def fixed_nrel(power_or_irradiance,
               daytime,
               r2_min=0.94,
               min_hours=5,
               peak_min=None):
    """Flag days that match the profile of a fixed PV system on a sunny day.

    This algorithm relies on the observation that the power profile of a
    fixed tilt PV system often resembles a quadratic polynomial on a
    sunny day, with a single peak when the sun is near the system azimuth.

    A day is marked True when the :math:`r^2` for a quadratic fit to the
    power data is greater than `r2_min`.

    Parameters
    ----------
    power_or_irradiance : Series
        Timezone localized series of power or irradiance measurements.
    daytime : Series
        Boolean series with True for times that are during the
        day. For best results this mask should exclude early morning
        and evening as well as night. Data at these times may have
        problems with shadows that interfere with curve fitting.
    r2_min : float, default 0.94
        Minimum :math:`r^2` of a quadratic fit for a day to be marked True.
    min_hours : float, default 5.0
        Minimum number of hours with data to attempt a fit on a day.
    peak_min : float, default None
        The maximum `power_or_irradiance` value for a day must be
        greater than `peak_min` for a fit to be attempted. If the
        maximum for a day is less than `peak_min` then the day will be
        marked False.

    Returns
    -------
    Series
        True for values on days where `power_or_irradiance` matches
        the expected parabolic profile for a fixed PV system on a sunny day.

    Notes
    -----
    This algorithm is based on the PVFleets QA Analysis
    project. Copyright (c) 2020 Alliance for Sustainable Energy, LLC.

    """
    freq = pd.infer_freq(power_or_irradiance.index)
    daily_data = _group.by_day(power_or_irradiance[daytime])
    minutes = pd.Series(power_or_irradiance.index.hour * 60 +
                        power_or_irradiance.index.minute,
                        index=power_or_irradiance.index)
    fixed_days = daily_data.apply(_conditional_fit,
                                  fitfunc=_fit.quadratic_r2,
                                  minutes=minutes,
                                  freq=freq,
                                  min_hours=min_hours,
                                  peak_min=peak_min)
    return (fixed_days > r2_min).reindex(power_or_irradiance.index,
                                         method='pad',
                                         fill_value=False)
Beispiel #2
0
def _peak_times(data):
    minute_of_day = pd.Series(data.index.hour * 60 + data.index.minute,
                              index=data.index)
    peak_minutes = _group.by_day(data).apply(
        lambda day: pd.Timedelta(minutes=round(
            _fit.quadratic_vertex(
                x=minute_of_day[day.index],
                y=day,
            ))))
    return pd.DatetimeIndex(np.unique(data.index.date),
                            tz=data.index.tz) + peak_minutes
def tracking_nrel(power_or_irradiance, daytime, r2_min=0.915,
                  r2_fixed_max=0.96, min_hours=5, peak_min=None,
                  quadratic_mask=None):
    """Flag days that match the profile of a single-axis tracking PV system
    on a sunny day.

    This algorithm relies on the observation that the power profile of
    a single-axis tracking PV system tends to resemble a quartic
    polynomial on a sunny day, I.e., two peaks are observed, one
    before and one after the sun crosses the tracker azimuth. By
    contrast, the power profile for a fixed tilt PV system often
    resembles a quadratic polynomial on a sunny day, with a single
    peak when the sun is near the system azimuth.

    The algorithm fits both a quartic and a quadratic polynomial to
    each day's data.  A day is marked True if the quartic fit has a
    sufficiently high :math:`r^2` and the quadratic fit has a
    sufficiently low :math:`r^2`.  Specifically, a day is marked True
    when three conditions are met:

    1. a restricted quartic [#]_ must fit the data with :math:`r^2`
       greater than `r2_min`
    2. the :math:`r^2` for the restricted quartic fit must be greater
       than the :math:`r^2` for a quadratic fit
    3. the :math:`r^2` for a quadratic fit must be less than
       `r2_fixed_max`

    Values on days where any one of these conditions is not met are
    marked False.

    .. [#] The specific quartic used for this fit is centered within
       70 minutes of 12:00, the y-value at the center must be within
       15% of the median for the day, and it must open downwards.

    Parameters
    ----------
    power_or_irradiance : Series
        Timezone localized series of power or irradiance measurements.
    daytime : Series
        Boolean series with True for times that are during the
        day. For best results this mask should exclude early morning
        and late afternoon as well as night. Data at these times may have
        problems with shadows that interfere with curve fitting.
    r2_min : float, default 0.915
        Minimum :math:`r^2` of a quartic fit for a day to be marked True.
    r2_fixed_max : float, default 0.96
        If the :math:`r^2` of a quadratic fit exceeds
        `r2_fixed_max`, then tracking/fixed cannot be distinguished
        and the day is marked False.
    min_hours : float, default 5.0
        Minimum number of hours with data to attempt a fit on a day.
    peak_min : float, default None
        The maximum `power_or_irradiance` value for a day must be
        greater than `peak_min` for a fit to be attempted. If the
        maximum for a day is less than `peak_min` then the day will be
        marked False.
    quadratic_mask : Series, default None
        If None then `daytime` is used. This Series is used to remove
        morning and afternoon times from the data before applying a
        quadratic fit. The mask should
        typically exclude more data than `daytime` in order to
        eliminate long tails in the morning or afternoon that can
        appear if a tracker is stuck in a West or East orientation.

    Returns
    -------
    Series
        Boolean series with True for every value on a day that has a
        tracking profile (see criteria above).

    Notes
    -----
    This algorithm is based on the PVFleets QA Analysis
    project. Copyright (c) 2020 Alliance for Sustainable Energy, LLC.

    """
    if quadratic_mask is None:
        quadratic_mask = daytime
    freq = pd.infer_freq(power_or_irradiance.index)
    minutes = pd.Series(
        power_or_irradiance.index.hour * 60 + power_or_irradiance.index.minute,
        index=power_or_irradiance.index
    )
    daily_data = _group.by_day(power_or_irradiance[daytime])
    tracking_days = daily_data.apply(
        _conditional_fit,
        fitfunc=_fit.quartic_restricted_r2,
        minutes=minutes,
        freq=freq,
        min_hours=min_hours,
        peak_min=peak_min
    )
    fixed_days = _group.by_day(power_or_irradiance[quadratic_mask]).apply(
        _conditional_fit,
        fitfunc=_fit.quadratic_r2,
        minutes=minutes,
        freq=freq,
        min_hours=min_hours,
        peak_min=peak_min
    )
    return (
        (tracking_days > r2_min)
        & (tracking_days > fixed_days)
        & (fixed_days < r2_fixed_max)
    ).reindex(power_or_irradiance.index, method='pad', fill_value=False)
Beispiel #4
0
def infer_orientation_daily_peak(power_or_poa, sunny, tilts,
                                 azimuths, solar_azimuth,
                                 solar_zenith, ghi, dhi, dni):
    """Determine system azimuth and tilt from power or POA using solar
    azimuth at the daily peak.

    The time of the daily peak is estimated by fitting a quadratic to
    to the data for each day in `power_or_poa` and finding the vertex
    of the fit. A brute force search is performed on clearsky POA
    irradiance for all pairs of candidate azimuths and tilts
    (`azimuths` and `tilts`) to find the pair that results in the
    closest azimuth to the azimuths calculated at the peak times from
    the curve fitting step. Closest is determined by minimizing the
    sum of squared difference between the solar azimuth at the peak
    time in `power_or_poa` and the solar azimuth at maximum clearsky
    POA irradiance.

    The accuracy of the tilt and azimuth returned by this function will
    vary with the time-resolution of the clearsky and solar position
    data. For the best accuracy pass `solar_azimuth`, `solar_zenith`,
    and the clearsky data (`ghi`, `dhi`, and `dni`) with one-minute
    timestamp spacing. If `solar_azimuth` has timestamp spacing less
    than one minute it will be resampled and interpolated to estimate
    azimuth at each minute of the day. Regardless of the timestamp
    spacing these parameters must cover the same days as
    `power_or_poa`.

    Parameters
    ----------
    power_or_poa : Series
        Timezone localized series of power or POA irradiance
        measurements.
    sunny : Series
        Boolean series with True for values during clearsky
        conditions.
    tilts : array-like
        Candidate tilts in degrees.
    azimuths : array-like
        Candidate azimuths in degrees.
    solar_azimuth : Series
        Time series of solar azimuth.
    solar_zenith : Series
        Time series of solar zenith.
    ghi : Series
        Clear sky GHI.
    dhi : Series
        Clear sky DHI.
    dni : Series
        Clear sky DNI.

    Returns
    -------
    azimuth : float
    tilt : float

    Notes
    -----
    Based on PVFleets QA project.

    """
    peak_times = _peak_times(power_or_poa[sunny])
    azimuth_by_minute = solar_azimuth.resample('T').interpolate(
        method='linear'
    )
    modeled_azimuth = azimuth_by_minute[peak_times]
    best_azimuth = None
    best_tilt = None
    smallest_sse = None
    for azimuth in azimuths:
        for tilt in tilts:
            poa = pvlib.irradiance.get_total_irradiance(
                tilt,
                azimuth,
                solar_zenith,
                solar_azimuth,
                ghi=ghi,
                dhi=dhi,
                dni=dni
            ).poa_global
            poa_azimuths = azimuth_by_minute[
                _group.by_day(poa).idxmax()
            ]
            filtered_azimuths = poa_azimuths[np.isin(
                poa_azimuths.index.date,
                modeled_azimuth.index.date
            )]
            sum_of_squares = sum(
                (filtered_azimuths.values - modeled_azimuth.values)**2
            )
            if (smallest_sse is None) or (smallest_sse > sum_of_squares):
                smallest_sse = sum_of_squares
                best_azimuth = azimuth
                best_tilt = tilt
    return best_azimuth, best_tilt