Skip to content

Commit

Permalink
Move some logic to Egret; update documentation strings
Browse files Browse the repository at this point in the history
  • Loading branch information
darrylmelander committed Oct 6, 2021
1 parent 285a493 commit 7a295bd
Show file tree
Hide file tree
Showing 7 changed files with 165 additions and 129 deletions.
53 changes: 6 additions & 47 deletions prescient/data/providers/gmlc_data_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import copy

from egret.parsers import rts_gmlc_parser as parser
from egret.parsers.rts_gmlc._reserves import reserve_name_map
from egret.data.model_data import ModelData as EgretModel

from ..data_provider import DataProvider
Expand Down Expand Up @@ -207,52 +206,12 @@ def _populate_with_forecastable_data(self,
dt = start_time + i*delta
time_labels[i] = dt.strftime('%Y-%m-%d %H:%M')

def _get_forecastable_locations(self, simulation_type:str, md:EgretModel):
df = self._cache.timeseries_df

system = md.data['system']
loads = md.data['elements']['load']
generators = md.data['elements']['generator']
areas = md.data['elements']['area']

# Go through each timeseries value for this simulation type
for i in range(self._cache._first_indices[simulation_type], len(df)):
if df.iat[i, df.columns.get_loc('Simulation')] != simulation_type:
break

category = df.iat[i, df.columns.get_loc('Category')]

if category == 'Generator':
gen_name = df.iat[i, df.columns.get_loc('Object')]
param = df.iat[i, df.columns.get_loc('Parameter')]

if param == 'PMin MW':
yield (generators[gen_name], 'p_min')
elif param == 'PMax MW':
yield (generators[gen_name], 'p_max')
else:
raise ValueError(f"Unexpected generator timeseries data: {param}")

elif category == 'Area':
area_name = df.iat[i, df.columns.get_loc('Object')]
param = df.iat[i, df.columns.get_loc('Parameter')]
assert(param == "MW Load")
for l_d in loads.values():
# Skip loads from other areas
if l_d['area'] != area_name:
continue
yield (l_d, 'p_load')
yield (l_d, 'q_load')

elif category == 'Reserve':
res_name = df.iat[i, df.columns.get_loc('Object')]
if res_name in reserve_name_map:
yield (system, reserve_name_map[res_name])
else:
# reserve name must be <type>_R<area>,
# split into type and area
res_name, area_name = res_name.split("_R", 1)
yield (areas[area_name], reserve_name_map[res_name])
def _get_forecastable_locations(self, simulation_type:str, md:EgretModel) -> Iterable[Tuple[dict, str]]:
''' Get all recognized forecastable locations with a defined time series
Each location is returned as a dict and the name of a key within the dict
'''
return self._cache.get_timeseries_locations(simulation_type, md)

def _ensure_forecastable_storage(self, sim_type:str, num_entries:int, model:EgretModel) -> None:
""" Ensure that the model has an array allocated for every type of forecastable data
Expand Down
44 changes: 35 additions & 9 deletions prescient/data/simulation_state/mutable_simulation_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ def __init__(self):
def timestep_count(self) -> int:
''' The number of timesteps we have data for '''
for _ in self._forecasts.values():
# Return the length of the first forecast array, if there is one...
return len(_)
# ...or return 0 if _forecasts is empty
return 0

@property
Expand Down Expand Up @@ -78,30 +80,54 @@ def get_initial_state_of_charge(self, s:S) -> float:
def get_current_actuals(self, forecastable:str) -> float:
''' Get the current actual value for forecastable
This is the actual value for the current time period (time index 0).
Values are returned in the same order as forecast_helper.get_forecastables,
but instead of returning arrays it returns a single value.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Returns
-------
Returns the actual scalar value for the current time period (time index 0)
'''
return self._actuals[forecastable][0]

def get_forecasts(self, forecastable:str) -> Sequence[float]:
''' Get the forecast values for forecastable
''' Get the forecast values for a named forecastable
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
This is very similar to forecast_helper.get_forecastables(); the
function yields an array per forecastable, in the same order as
get_forecastables().
Returns
-------
Returns an array for the named forecastable.
Note that the value at index 0 is the forecast for the current time,
not the actual value for the current time.
'''
return self._forecasts[forecastable]

def get_future_actuals(self, forecastable:str) -> Sequence[float]:
''' Warning: Returns actual values of forecastable for the current time AND FUTURE TIMES.
''' Warning: Returns actual values of a forecastable for the current time AND FUTURE TIMES.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Be aware that this function returns information that is not yet known!
The function lets you peek into the future. Future actuals may be used
by some (probably unrealistic) algorithm options, such as
by some (probably unrealistic) algorithm options.
Returns
-------
Returns an array of actual values. The value at index 0 is the actual value
for the current time, and the rest of the array holds actual values for
future times.
'''
return self._actuals[forecastable]

Expand Down
44 changes: 33 additions & 11 deletions prescient/data/simulation_state/simulation_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,33 +48,55 @@ def get_initial_state_of_charge(self, s:S):

@abstractmethod
def get_current_actuals(self, forecastable:str) -> float:
''' Get the current actual value for forecastable
''' Get the current actual value for a forecastable data item
This is the actual value for the current time period (time index 0).
Values are returned in the same order as forecast_helper.get_forecastables,
but instead of returning arrays it returns a single value.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Returns
-------
Returns the actual value for the current time period (time index 0).
'''
pass

@abstractmethod
def get_forecasts(self, forecastable:str) -> Sequence[float]:
''' Get the forecast values for forecastable
''' Get the forecast values for a forecastable
This is very similar to forecast_helper.get_forecastables(); the
function yields an array per forecastable, in the same order as
get_forecastables().
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Note that the value at index 0 is the forecast for the current time,
not the actual value for the current time.
Returns
-------
Returns an array of forecast values, starting with the forecast
for the current time at index 0.
'''
pass

@abstractmethod
def get_future_actuals(self, forecastable:str) -> Sequence[float]:
''' Warning: Returns actual values of forecastable for the current time AND FUTURE TIMES.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Returns
-------
Returns an array of actual values, starting with the actual value
for the current time at index 0. All values beyond index 0 are actual
values for future time periods, which cannot be known at the current time.
Be aware that this function returns information that is not yet known!
The function lets you peek into the future. Future actuals may be used
by some (probably unrealistic) algorithm options, such as
by some (probably unrealistic) algorithm options.
'''
pass
48 changes: 35 additions & 13 deletions prescient/data/simulation_state/state_with_offset.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,33 +83,55 @@ def get_initial_state_of_charge(self, s:S):
return self._init_soc[s]

def get_current_actuals(self, forecastable:str) -> float:
''' Get the current actual value for forecastable
''' Get the current actual value for a forecastable data item
This is the actual value for the current time period (time index 0).
Values are returned in the same order as forecast_helper.get_forecastables,
but instead of returning arrays it returns a single value.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Returns
-------
Returns the actual value for the current time period (time index 0).
'''
return self._parent.get_future_actuals(forecastable)[self._offset]

def get_forecasts(self, forecastable:str) -> Sequence[float]:
''' Get the forecast values for forecastable
''' Get the forecast values for a forecastable
This is very similar to forecast_helper.get_forecastables(); the
function yields an array per forecastable, in the same order as
get_forecastables().
Note that the value at index 0 is the forecast for the current time,
not the actual value for the current time.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Returns
-------
Returns an array of forecast values, starting with the forecast
for the current time at index 0.
'''
# Copy the relevent portion to a new array
return list(itertools.islice(self._parent.get_forecasts(forecastable), self._offset, None))

def get_future_actuals(self, forecastable:str) -> Sequence[float]:
''' Warning: Returns actual values of forecastable for the current time AND FUTURE TIMES.
''' Warning: Returns actual values of a forecastable for the current time AND FUTURE TIMES.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Returns
-------
Returns an array of actual values, starting with the actual value
for the current time at index 0. All values beyond index 0 are actual
values for future time periods, which cannot be known at the current time.
Be aware that this function returns information that is not yet known!
The function lets you peek into the future. Future actuals may be used
by some (probably unrealistic) algorithm options, such as
by some (probably unrealistic) algorithm options.
'''
# Copy the relevent portion to a new array
return list(itertools.islice(self._parent.get_future_actuals(forecastable), self._offset, None))
46 changes: 34 additions & 12 deletions prescient/data/simulation_state/time_interpolated_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,17 @@ def get_initial_state_of_charge(self, s:S):
return self._inner_state.get_initial_state_of_charge(s)

def get_current_actuals(self, forecastable:str) -> float:
''' Get the current actual value for forecastable
''' Get the current actual value for a forecastable data item
This is the actual value for the current time period (time index 0).
Values are returned in the same order as forecast_helper.get_forecastables,
but instead of returning arrays it returns a single value.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Returns
-------
Returns the actual value for the current time period (time index 0).
'''
if self._minutes_past_first_actuals == 0:
return self._inner_state.get_current_actuals(forecastable)
Expand All @@ -150,14 +156,18 @@ def get_current_actuals(self, forecastable:str) -> float:
fractional_index.fraction_between)

def get_forecasts(self, forecastable:str) -> Sequence[float]:
''' Get the forecast values for forecastable
''' Get the forecast values for a forecastable
This is very similar to forecast_helper.get_forecastables(); the
function yields an array per forecastable, in the same order as
get_forecastables().
Note that the value at index 0 is the forecast for the current time,
not the actual value for the current time.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Returns
-------
Returns an array of forecast values, starting with the forecast
for the current time at index 0.
'''
return self._get_forecastables(self._inner_state.get_forecasts(forecastable),
self._minutes_per_inner_forecast,
Expand All @@ -166,9 +176,21 @@ def get_forecasts(self, forecastable:str) -> Sequence[float]:
def get_future_actuals(self, forecastable:str) -> Sequence[float]:
''' Warning: Returns actual values of forecastable for the current time AND FUTURE TIMES.
Arguments
---------
forecastable:str
The unique identifier for the forecastable data item of interest,
as returned by forecast_helper.get_forecastables()
Returns
-------
Returns an array of actual values, starting with the actual value
for the current time at index 0. All values beyond index 0 are actual
values for future time periods, which cannot be known at the current time.
Be aware that this function returns information that is not yet known!
The function lets you peek into the future. Future actuals may be used
by some (probably unrealistic) algorithm options, such as
by some (probably unrealistic) algorithm options.
'''
return self._get_forecastables(self._inner_state.get_future_actuals(forecastable),
self._minutes_per_inner_actuals,
Expand Down
7 changes: 3 additions & 4 deletions prescient/engine/egret/egret_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -367,14 +367,13 @@ def create_deterministic_ruc(options,
ruc_delay = -(options.ruc_execution_hour%(-options.ruc_every_hours))
if options.ruc_prescience_hour > ruc_delay + 1:
improved_hour_count = options.ruc_prescience_hour - ruc_delay - 1
for forecast, actuals in zip(get_forecastables(md),
current_state.get_future_actuals()):
for forecastable, forecast in get_forecastables(md):
actuals = current_state.get_future_actuals(forecastable)
for t in range(0, improved_hour_count):
forecast_portion = (ruc_delay+t)/options.ruc_prescience_hour
actuals_portion = 1-forecast_portion
forecast[t] = forecast_portion*forecast[t] + actuals_portion*actuals[t]


# Ensure the reserve requirement is satisfied
_ensure_reserve_factor_honored(options, md, range(forecast_request_count))

Expand Down Expand Up @@ -626,7 +625,7 @@ def create_simulation_actuals(
timesteps_per_day = 24 * 60 / step_size_minutes
steps_to_request = math.min(timesteps_per_day, total_step_count)
get_data_func(options, start_time, steps_to_request, step_size_minutes, md)
for vals, in get_forecastables(md):
for _, vals in get_forecastables(md):
for t in range(timesteps_per_day, total_step_count):
vals[t] = vals[t-timesteps_per_day]

Expand Down
Loading

0 comments on commit 7a295bd

Please sign in to comment.