def test_irradiance_to_power(modeling_parameters_system_type, apparent_zenith,
                             azimuth, ghi, dni, dhi, temp_air, wind_speed):
    modeling_parameters, system_type = modeling_parameters_system_type
    out = pvmodel.irradiance_to_power(modeling_parameters,
                                      apparent_zenith,
                                      azimuth,
                                      ghi,
                                      dni,
                                      dhi,
                                      temp_air=temp_air,
                                      wind_speed=wind_speed)
    index = apparent_zenith.index
    expected_fixed = pd.Series([0., 0.003], index=index)
    expected_tracking = pd.Series([0., 0.00293178], index=index)
    fixed_or_tracking(system_type, expected_fixed, expected_tracking, out)
예제 #2
0
def run_nwp(forecast, model, run_time, issue_time):
    """
    Calculate benchmark irradiance and power forecasts for a Forecast or
    ProbabilisticForecast.

    Forecasts may be run operationally or retrospectively. For
    operational forecasts, *run_time* is typically set to now. For
    retrospective forecasts, *run_time* is the time by which the
    forecast should be run so that it could have been be delivered for
    the *issue_time*. Forecasts will only use data with timestamps
    before *run_time*.

    Parameters
    ----------
    forecast : datamodel.Forecast or datamodel.ProbabilisticForecast
        The metadata of the desired forecast.
    model : function
        NWP model loading and processing function.
        See :py:mod:`solarforecastarbiter.reference_forecasts.models`
        for options.
    run_time : pd.Timestamp
        Run time of the forecast.
    issue_time : pd.Timestamp
        Issue time of the forecast run.

    Returns
    -------
    ghi : pd.Series or pd.DataFrame
    dni : pd.Series or pd.DataFrame
    dhi : pd.Series or pd.DataFrame
    air_temperature : pd.Series or pd.DataFrame
    wind_speed : pd.Series or pd.DataFrame
    ac_power : pd.Series or pd.DataFrame

    Series are returned for deterministic forecasts, DataFrames are
    returned for probabilisic forecasts.

    Examples
    --------
    The following code would return hourly average forecasts derived
    from the subhourly HRRR model.

    .. testsetup::

       import datetime
       from solarforecastarbiter import datamodel
       from solarforecastarbiter.reference_forecasts import models
       from solarforecastarbiter.reference_forecasts.main import *

    >>> run_time = pd.Timestamp('20190515T0200Z')
    >>> issue_time = pd.Timestamp('20190515T0000Z')
    >>> modeling_parameters = datamodel.FixedTiltModelingParameters(
    ...     surface_tilt=30, surface_azimuth=180,
    ...     ac_capacity=10, dc_capacity=15,
    ...     temperature_coefficient=-0.4, dc_loss_factor=0,
    ...     ac_loss_factor=0)
    >>> power_plant = datamodel.SolarPowerPlant(
    ...     name='Test plant', latitude=32.2, longitude=-110.9,
    ...     elevation=715, timezone='America/Phoenix',
    ...     modeling_parameters=modeling_parameters)
    >>> forecast = datamodel.Forecast(
    ...     name='Test plant fx',
    ...     site=power_plant,
    ...     variable='ac_power',
    ...     interval_label='ending',
    ...     interval_value_type='mean',
    ...     interval_length='1h',
    ...     issue_time_of_day=datetime.time(hour=0),
    ...     run_length=pd.Timedelta('24h'),
    ...     lead_time_to_start=pd.Timedelta('0h'))
    >>> ghi, dni, dhi, temp_air, wind_speed, ac_power = run_nwp(
    ...     forecast, models.hrrr_subhourly_to_hourly_mean,
    ...     run_time, issue_time)
    """
    fetch_metadata = fetch_nwp.model_map[models.get_nwp_model(model)]
    # absolute date and time for model run most recently available
    # as of run_time
    init_time = utils.get_init_time(run_time, fetch_metadata)
    # absolute start and end times. interval_label still controls
    # inclusive/exclusive
    start, end = utils.get_forecast_start_end(forecast, issue_time)
    site = forecast.site
    logger.info(
        'Calculating forecast for model %s starting at %s from %s to %s',
        model, init_time, start, end)
    # model will account for interval_label
    *forecasts, resampler, solar_position_calculator = model(
        site.latitude, site.longitude, site.elevation, init_time, start, end,
        forecast.interval_label)

    if isinstance(site, datamodel.SolarPowerPlant):
        solar_position = solar_position_calculator()
        if isinstance(forecasts[0], pd.DataFrame):
            # must iterate over columns because pvmodel.irradiance_to_power
            # calls operations that do not properly broadcast Series along
            # a DataFrame time index. pvlib.irradiance.haydavies operation
            # (AI = dni_ens / dni_extra) is the known culprit, though there
            # may be more.
            ac_power = {}
            for col in forecasts[0].columns:
                member_fx = [fx.get(col) for fx in forecasts]
                member_ac_power = pvmodel.irradiance_to_power(
                    site.modeling_parameters,
                    solar_position['apparent_zenith'],
                    solar_position['azimuth'], *member_fx)
                ac_power[col] = member_ac_power
            ac_power = pd.DataFrame(ac_power)
        else:
            ac_power = pvmodel.irradiance_to_power(
                site.modeling_parameters, solar_position['apparent_zenith'],
                solar_position['azimuth'], *forecasts)
    else:
        ac_power = None

    # resample data after power calculation
    resampled = list(map(resampler, (*forecasts, ac_power)))
    nwpoutput = namedtuple(
        'NWPOutput',
        ['ghi', 'dni', 'dhi', 'air_temperature', 'wind_speed', 'ac_power'])
    return nwpoutput(*resampled)
예제 #3
0
def run(site, model, init_time, start, end):
    """
    Calculate benchmark irradiance and power forecasts for a site.

    The meaning of the timestamps (instantaneous or interval average)
    is determined by the model processing function.

    It's currently the user's job to determine time parameters that
    correspond to a particular Forecast Evaluation Time Series.

    Parameters
    ----------
    site : datamodel.Site
    model : function
        NWP model loading and processing function.
        See :py:mod:`solarforecastarbiter.reference_forecasts.models`
        for options.
    init_time : pd.Timestamp
        NWP model initialization time.
    start : pd.Timestamp
        Start of the forecast.
    end : pd.Timestamp
        End of the forecast.

    Returns
    -------
    ghi : pd.Series
    dni : pd.Series
    dhi : pd.Series
    temp_air : None or pd.Series
    wind_speed : None or pd.Series
    ac_power : None or pd.Series

    Examples
    --------
    The following code would return hourly average forecasts derived
    from the subhourly HRRR model.

    >>> from solarforecastarbiter import datamodel
    >>> from solarforecastarbiter.reference_forecasts import models
    >>> init_time = pd.Timestamp('20190328T1200Z')
    >>> start = pd.Timestamp('20190328T1300Z')  # typical available time
    >>> end = pd.Timestamp('20190329T1300Z')  # 24 hour forecast
    >>> modeling_parameters = datamodel.FixedTiltModelingParameters(
    ...     ac_capacity=10, dc_capacity=15,
    ...     temperature_coefficient=-0.004, dc_loss_factor=0,
    ...     ac_loss_factor=0)
    >>> power_plant = datamodel.SolarPowerPlant(
    ...     name='Test plant', latitude=32.2, longitude=-110.9,
    ...     elevation=715, timezone='America/Phoenix',
    ...     modeling_parameters = modeling_parameters)
    >>> ghi, dni, dhi, temp_air, wind_speed, ac_power = run(
    ...     power_plant, models.hrrr_subhourly_to_hourly_mean,
    ...     init_time, start, end)
    """

    *forecast, resampler, solar_position_calculator = model(
        site.latitude, site.longitude, site.elevation,
        init_time, start, end)

    if isinstance(site, datamodel.SolarPowerPlant):
        solar_position = solar_position_calculator()
        ac_power = pvmodel.irradiance_to_power(
            site.modeling_parameters, solar_position['apparent_zenith'],
            solar_position['azimuth'], *forecast)
    else:
        ac_power = None

    # resample data after power calculation
    resampled = list(map(resampler, (*forecast, ac_power)))
    return resampled
예제 #4
0
def persistence_scalar_index(observation, data_start, data_end, forecast_start,
                             forecast_end, interval_length, interval_label,
                             load_data):
    r"""
    Calculate a persistence forecast using the mean value of the
    *observation* clear sky index or AC power index from *data_start* to
    *data_end*.

    In the example below, we use GHI to be concrete but the concept also
    applies to AC power. The persistence forecast is:

    .. math::

       GHI_{t_f} = \overline{
           \frac{ GHI_{t_{start}} }{ GHI_{{clear}_{t_{start}}} } \ldots
           \frac{ GHI_{t_{end}} }{ GHI_{{clear}_{t_{end}}} } }

    where :math:`t_f` is a forecast time, and the overline represents
    the average of all observations or clear sky values that occur
    between :math:`t_{start}` = *data_start* and
    :math:`t_{end}` = *data_end*. All :math:`GHI_{t}/GHI_{{clear}_t}`
    ratios are restricted to the range [0, 2] before the average is
    computed.

    Parameters
    ----------
    observation : datamodel.Observation
    data_start : pd.Timestamp
        Observation data start. Forecast is inclusive of this instant if
        observation.interval_label is *beginning* or *instant*.
    data_end : pd.Timestamp
        Observation data end. Forecast is inclusive of this instant if
        observation.interval_label is *ending* or *instant*.
    forecast_start : pd.Timestamp
        Forecast start. Forecast is inclusive of this instant if
        interval_label is *beginning* or *instant*.
    forecast_end : pd.Timestamp
        Forecast end. Forecast is inclusive of this instant if
        interval_label is *ending* or *instant*.
    interval_length : pd.Timedelta
        Forecast interval length
    interval_label : str
        instant, beginning, or ending
    load_data : function
        A function that loads the observation data. Must have the
        signature load_data(observation, data_start, data_end) and
        properly account for observation interval label.

    Returns
    -------
    forecast : pd.Series
        The persistence forecast. The forecast interval label is the
        same as the observation interval label.
    """
    # ensure that we're using times rounded to multiple of interval_length
    _check_intervals_times(observation.interval_label, data_start, data_end,
                           forecast_start, forecast_end,
                           observation.interval_length)

    # get observation data for specified range
    obs = load_data(observation, data_start, data_end)

    # partial-up the metadata for solar position and
    # clearsky calculation clarity and consistency
    site = observation.site
    calc_solpos = partial(pvmodel.calculate_solar_position,
                          site.latitude, site.longitude, site.elevation)
    calc_cs = partial(pvmodel.calculate_clearsky,
                      site.latitude, site.longitude, site.elevation)

    # Calculate solar position and clearsky for obs time range.
    # if data is instantaneous, calculate at the obs time.
    # else (if data is interval average), calculate at 1 minute resolution to
    # reduce errors from changing solar position during persistence data range.
    # Later, modeled clear sky or ac power will be averaged over the data range
    closed = datamodel.CLOSED_MAPPING[observation.interval_label]
    if closed is None:
        freq = observation.interval_length
    else:
        freq = pd.Timedelta('1min')
    obs_range = pd.date_range(start=data_start, end=data_end, freq=freq,
                              closed=closed)
    solar_position_obs = calc_solpos(obs_range)
    clearsky_obs = calc_cs(solar_position_obs['apparent_zenith'])

    # Calculate solar position and clearsky for the forecast times.
    # Use 5 minute or better frequency to minimize solar position errors.
    # Later, modeled clear sky or ac power will be resampled to interval_length
    closed_fx = datamodel.CLOSED_MAPPING[interval_label]
    fx_range = pd.date_range(start=forecast_start, end=forecast_end, freq=freq,
                             closed=closed_fx)
    solar_position_fx = calc_solpos(fx_range)
    clearsky_fx = calc_cs(solar_position_fx['apparent_zenith'])

    # Consider putting the code within each if/else block below into its own
    # function with standard outputs clear_ref and clear_fx. But with only two
    # cases for now, it might be more clear to leave inline.
    if (
            isinstance(site, datamodel.SolarPowerPlant) and
            observation.variable == 'ac_power'
    ):
        # No temperature input is only OK so long as temperature effects
        # do not push the system above or below AC clip point.
        # It's only a reference forecast!
        clear_ref = pvmodel.irradiance_to_power(
            site.modeling_parameters, solar_position_obs['apparent_zenith'],
            solar_position_obs['azimuth'], clearsky_obs['ghi'],
            clearsky_obs['dni'], clearsky_obs['dhi']
        )
        clear_fx = pvmodel.irradiance_to_power(
            site.modeling_parameters, solar_position_fx['apparent_zenith'],
            solar_position_fx['azimuth'], clearsky_fx['ghi'],
            clearsky_fx['dni'], clearsky_fx['dhi']
        )
    else:
        # assume we are working with ghi, dni, or dhi.
        clear_ref = clearsky_obs[observation.variable]
        clear_fx = clearsky_fx[observation.variable]

    # resample sub-interval reference clear sky to observation intervals
    clear_ref_resampled = clear_ref.resample(
        observation.interval_length, closed=closed, label=closed).mean()
    # calculate persistence index (clear sky index or ac power index)
    # avg{index_{t_start}...index_{t_end}} =
    #   avg{obs_{t_start}/clear_{t_start}...obs_{t_end}/clear_{t_end}}
    # clear_ref is calculated at high temporal resolution, so this is accurate
    # for any observation interval length
    # apply clip to the clear sky index array before computing the average.
    # this prevents outliers from distorting the mean, a common occurance
    # near sunrise and sunset.
    pers_index = (obs / clear_ref_resampled).clip(lower=0, upper=2).mean()

    # average instantaneous clear forecasts over interval_length windows
    # resample operation should be safe due to
    # _check_interval_length calls above
    clear_fx_resampled = clear_fx.resample(
        interval_length, closed=closed_fx, label=closed_fx).mean()

    # finally, make forecast
    # fx_t_f = avg{index_{t_start}...index_{t_end}} * clear_t_f
    fx = pers_index * clear_fx_resampled

    return fx
예제 #5
0
                                     timezone='America/Denver',
                                     provider='Sandia',
                                     modeling_parameters=modeling_params)

times = pd.date_range(start='20190801',
                      end='20191001',
                      freq='5min',
                      closed='left')

solpos = pvmodel.calculate_solar_position(metadata.latitude,
                                          metadata.longitude,
                                          metadata.elevation, times)
cs = pvmodel.calculate_clearsky(metadata.latitude, metadata.longitude,
                                metadata.elevation, solpos['apparent_zenith'])
ac_clear = pvmodel.irradiance_to_power(modeling_params,
                                       solpos['apparent_zenith'],
                                       solpos['azimuth'], cs['ghi'], cs['dni'],
                                       cs['dhi'])

ac_clear_1h = ac_clear.resample('1h').mean()
ac_clear_1h = pd.DataFrame({'value': ac_clear_1h, 'quality_flag': 0})
ac_clear_1h.to_csv('boulder_clear_aug_sept.csv', index_label='timestamp')

# make it cloudy, but apply clipping
random = np.random.random(size=len(times)) + 0.5
random = random.clip(max=1)
ac = ac_clear * random

ac_1h = ac.resample('1h').mean()
ac_1h = pd.DataFrame({'value': ac_1h, 'quality_flag': 0})
ac_1h.to_csv('boulder_cloudy_aug_sept.csv', index_label='timestamp')