def test_persistence_scalar_index_invalid_times_instant(site_metadata): data = pd.Series(100., index=[0]) load_data = partial(load_data_base, data) tz = 'America/Phoenix' interval_label = 'instant' observation = default_observation(site_metadata, interval_length='5min', interval_label=interval_label) # instant obs that cover the whole interval - not allowed! data_start = pd.Timestamp('20190404 1200', tz=tz) data_end = pd.Timestamp('20190404 1300', tz=tz) forecast_start = pd.Timestamp('20190404 1300', tz=tz) forecast_end = pd.Timestamp('20190404 1400', tz=tz) interval_length = pd.Timedelta('30min') with pytest.raises(ValueError): persistence.persistence_scalar_index(observation, data_start, data_end, forecast_start, forecast_end, interval_length, interval_label, load_data)
def test_persistence_scalar_index_invalid_times_invalid_label(site_metadata): data = pd.Series(100., index=[0]) load_data = partial(load_data_base, data) tz = 'America/Phoenix' interval_length = pd.Timedelta('30min') interval_label = 'invalid' observation = default_observation(site_metadata, interval_length='5min') object.__setattr__(observation, 'interval_label', interval_label) data_start = pd.Timestamp('20190404 1200', tz=tz) data_end = pd.Timestamp('20190404 1300', tz=tz) forecast_start = pd.Timestamp('20190404 1300', tz=tz) forecast_end = pd.Timestamp('20190404 1400', tz=tz) with pytest.raises(ValueError) as excinfo: persistence.persistence_scalar_index(observation, data_start, data_end, forecast_start, forecast_end, interval_length, interval_label, load_data) assert "invalid interval_label" in str(excinfo.value)
def test_persistence_scalar_index_low_solar_elevation(site_metadata, powerplant_metadata): interval_label = 'beginning' observation = default_observation(site_metadata, interval_length='5min', interval_label=interval_label) observation_ac = default_observation(powerplant_metadata, interval_length='5min', interval_label=interval_label, variable='ac_power') # at ABQ Baseline, solar apparent zenith for these points is # 2019-05-13 12:00:00+00:00 91.62 # 2019-05-13 12:05:00+00:00 90.09 # 2019-05-13 12:10:00+00:00 89.29 # 2019-05-13 12:15:00+00:00 88.45 # 2019-05-13 12:20:00+00:00 87.57 # 2019-05-13 12:25:00+00:00 86.66 tz = 'UTC' data_start = pd.Timestamp('20190513 1200', tz=tz) data_end = pd.Timestamp('20190513 1230', tz=tz) index = pd.date_range(start=data_start, end=data_end, freq='5min', closed='left') # clear sky 5 min avg (from 1 min avg) GHI is # [0., 0.10932908, 1.29732454, 4.67585122, 10.86548521, 19.83487399] # create data series that could produce obs / clear of # 0/0, 1/0.1, -1/1.3, 5/5, 10/10, 20/20 # average without limits is (10 - 1 + 1 + 1 + 1) / 5 = 2.4 # average with element limits of [0, 2] = (2 + 0 + 1 + 1 + 1) / 5 = 1 data = pd.Series([0, 1, -1, 5, 10, 20.], index=index) forecast_start = pd.Timestamp('20190513 1230', tz=tz) forecast_end = pd.Timestamp('20190513 1300', tz=tz) interval_length = pd.Timedelta('5min') load_data = partial(load_data_base, data) expected_index = pd.date_range(start=forecast_start, end=forecast_end, freq='5min', closed='left') # clear sky 5 min avg GHI is # [31.2, 44.5, 59.4, 75.4, 92.4, 110.1] expected_vals = [31.2, 44.5, 59.4, 75.4, 92.4, 110.1] expected = pd.Series(expected_vals, index=expected_index) fx = persistence.persistence_scalar_index(observation, data_start, data_end, forecast_start, forecast_end, interval_length, interval_label, load_data) assert_series_equal(fx, expected, check_less_precise=1, check_names=False) expected = pd.Series([0.2, 0.7, 1.2, 1.6, 2., 8.9], index=expected_index) fx = persistence.persistence_scalar_index(observation_ac, data_start, data_end, forecast_start, forecast_end, interval_length, interval_label, load_data) assert_series_equal(fx, expected, check_less_precise=1, check_names=False)
def run_persistence(session, observation, forecast, run_time, issue_time, index=False): """ Run a persistence *forecast* for an *observation*. For intraday forecasts, the *index* argument controls if the forecast is constructed using persistence of the measured values (*index = False*) or persistence using clear sky index or AC power index. For day ahead forecasts, only persistence of measured values (*index = False*) is supported. 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*. The persistence *window* is the time over which the persistence quantity (irradiance, power, clear sky index, or power index) is averaged. The persistence window is automatically determined from the *forecast* attributes: * Intraday persistence forecasts: *window = forecast.run_length*. No longer than 1 hour. * Day ahead forecasts (all but net load) and week ahead forecasts (net load only): *window = forecast.interval_length*. Users that would like more flexibility may use the lower-level functions in :py:mod:`solarforecastarbiter.reference_forecasts.persistence`. Parameters ---------- session : api.Session The session object to use to request data from the SolarForecastArbiter API. observation : datamodel.Observation The metadata of the observation to be used to create the forecast. forecast : datamodel.Forecast The metadata of the desired forecast. run_time : pd.Timestamp Run time of the forecast. issue_time : pd.Timestamp Issue time of the forecast run. index : bool, default False If False, use persistence of observed value. If True, use persistence of clear sky or AC power index. Returns ------- forecast : pd.Series Forecast conforms to the metadata specified by the *forecast* argument. Raises ------ ValueError If forecast and issue_time are incompatible. ValueError If persistence window < observation.interval_length. ValueError If forecast.run_length = 1 day and forecast period is not midnight to midnight. ValueError If forecast.run_length = 1 day and index=True. ValueError If instantaneous forecast and instantaneous observation interval lengths do not match. ValueError If average observations are used to make instantaneous forecast. Notes ----- For non-intraday net load forecasts, this function will use a weekahead persistence due to the fact that net load exhibits stronger correlation week-to-week than day-to-day. For example, the net load on a Monday tends to look more similar to the previous Monday that it does to the previous day (Sunday). """ utils.check_persistence_compatibility(observation, forecast, index) forecast_start, forecast_end = utils.get_forecast_start_end( forecast, issue_time, False) intraday = utils._is_intraday(forecast) if not intraday: # raise ValueError if not intraday and not midnight to midnight utils._check_midnight_to_midnight(forecast_start, forecast_end) data_start, data_end = utils.get_data_start_end(observation, forecast, run_time) def load_data(observation, data_start, data_end): df = session.get_observation_values(observation.observation_id, data_start, data_end, observation.interval_label) df = df.tz_convert(observation.site.timezone) return df['value'] if intraday and index: fx = persistence.persistence_scalar_index( observation, data_start, data_end, forecast_start, forecast_end, forecast.interval_length, forecast.interval_label, load_data) elif intraday and not index: fx = persistence.persistence_scalar(observation, data_start, data_end, forecast_start, forecast_end, forecast.interval_length, forecast.interval_label, load_data) elif not intraday and not index: fx = persistence.persistence_interval(observation, data_start, data_end, forecast_start, forecast.interval_length, forecast.interval_label, load_data) else: # pragma: no cover raise ValueError( 'index=True not supported for forecasts with run_length >= 1day') return fx
def run_persistence(session, observation, forecast, run_time, issue_time, index=False, load_data=None): """ Run a persistence *forecast* for an *observation*. For intraday forecasts, the *index* argument controls if the forecast is constructed using persistence of the measured values (*index = False*) or persistence using clear sky index or AC power index. For day ahead forecasts, only persistence of measured values (*index = False*) is supported. 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*. The persistence *window* is the time over which the persistence quantity (irradiance, power, clear sky index, or power index) is averaged. The persistence window is automatically determined from the *forecast* attributes: - Intraday persistence forecasts: + ``window = forecast.run_length``. No longer than 1 hour. - Day ahead forecasts (all but net load) and week ahead forecasts (net load only): + ``window = forecast.interval_length``. Users that would like more flexibility may use the lower-level functions in :py:mod:`solarforecastarbiter.reference_forecasts.persistence`. Parameters ---------- session : api.Session The session object to use to request data from the SolarForecastArbiter API. observation : datamodel.Observation The metadata of the observation to be used to create the forecast. forecast : datamodel.Forecast The metadata of the desired forecast. run_time : pd.Timestamp Run time of the forecast. issue_time : pd.Timestamp Issue time of the forecast run. index : bool, default False If False, use persistence of observed value. If True, use persistence of clear sky or AC power index. load_data : function Function to load the observation data 'value' series given (observation, data_start, data_end) arguments. Typically, calls `session.get_observation_values` and selects the 'value' column. May also have data preloaded to then slice from data_start to data_end. Returns ------- forecast : pd.Series Forecast conforms to the metadata specified by the *forecast* argument. Raises ------ ValueError If forecast and issue_time are incompatible. ValueError If data is required from after run_time. ValueError If persistence window < observation.interval_length. ValueError If forecast.run_length => 1 day and index=True. ValueError If instantaneous forecast and instantaneous observation interval lengths do not match. ValueError If average observations are used to make instantaneous forecast. Notes ----- For non-intraday net load forecasts, this function will use a weekahead persistence due to the fact that net load exhibits stronger correlation week-to-week than day-to-day. For example, the net load on a Monday tends to look more similar to the previous Monday that it does to the previous day (Sunday). """ utils.check_persistence_compatibility(observation, forecast, index) forecast_start, forecast_end = utils.get_forecast_start_end( forecast, issue_time, False) intraday = utils._is_intraday(forecast) if load_data is None: load_data = _default_load_data(session) data_start, data_end = utils.get_data_start_end(observation, forecast, run_time, issue_time) if data_end > run_time: raise ValueError( 'Persistence forecast requires data from after run_time') if isinstance(forecast, datamodel.ProbabilisticForecast): cvs = [f.constant_value for f in forecast.constant_values] fx = persistence.persistence_probabilistic( observation, data_start, data_end, forecast_start, forecast_end, forecast.interval_length, forecast.interval_label, load_data, forecast.axis, cvs) elif intraday and index: fx = persistence.persistence_scalar_index( observation, data_start, data_end, forecast_start, forecast_end, forecast.interval_length, forecast.interval_label, load_data) elif intraday and not index: fx = persistence.persistence_scalar(observation, data_start, data_end, forecast_start, forecast_end, forecast.interval_length, forecast.interval_label, load_data) elif not intraday and not index: fx = persistence.persistence_interval(observation, data_start, data_end, forecast_start, forecast.interval_length, forecast.interval_label, load_data) else: # pragma: no cover raise ValueError( 'index=True not supported for forecasts with run_length >= 1day') return fx