Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor Pyomo Dispatch #326

Merged
merged 14 commits into from
Jan 24, 2024
11 changes: 0 additions & 11 deletions src/_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,17 +92,6 @@ def get_farm_loc(raven_path=None): # Added by Haoyu Wang, May 25, 2022
farm_loc = plugin_handler.getPluginLocation('FARM')
return farm_loc

def get_all_resources(components):
"""
Provides a set of all resources used among all components
@ In, components, list, HERON component objects
@ Out, resources, list, resources used in case
"""
res = set()
for comp in components:
res.update(comp.get_resources())
return res

def get_project_lifetime(case, components):
"""
obtains the project lifetime
Expand Down
43 changes: 43 additions & 0 deletions src/dispatch/DispatchState.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,46 @@ def set_activity_vector(self, comp, res, values, tracker='production', start_idx

r = self._resources[comp][res]
self._data[f'{comp.name}_{tracker}'][r, start_idx:end_idx] = values

# DispatchState for Pyomo dispatcher
class PyomoState(DispatchState):
def __init__(self):
"""
Constructor.
@ In, None
@ Out, None
"""
DispatchState.__init__(self)
self._model = None # Pyomo model object

def initialize(self, components, resources_map, times, model):
"""
Connect information about this State to other objects
@ In, components, list, HERON components
@ In, resources_map, dict, map of component names to resources used
@ In, times, np.array, values of "time" this state represents
@ In, model, pyomo.Model, associated model for this state
@ Out, None
"""
DispatchState.initialize(self, components, resources_map, times)
self._model = model

def get_activity_indexed(self, comp, activity, r, t, valued=True, **kwargs):
"""
Getter for activity level.
@ In, comp, HERON Component, component whose information should be retrieved
@ In, activity, str, tracking variable name for activity subset
@ In, r, int, index of resource to retrieve (as given by meta[HERON][resource_indexer])
@ In, t, int, index of time at which activity should be provided
@ In, valued, bool, optional, if True then get float value instead of pyomo expression
@ In, kwargs, dict, additional pass-through keyword arguments
@ Out, activity, float, amount of resource "res" produced/consumed by "comp" at time "time";
note positive is producting, negative is consuming
"""
prod = getattr(self._model, f'{comp.name}_{activity}')[r, t]
if valued:
return prod()
return prod

def set_activity_indexed(self, comp, r, t, value, valued=False):
raise NotImplementedError
112 changes: 7 additions & 105 deletions src/dispatch/Dispatcher.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
from ravenframework.utils import InputData, InputTypes
from ravenframework.BaseClasses import MessageUser, InputDataUser

class DispatchError(Exception):
"""
Custom exception for dispatch errors.
"""
pass


class Dispatcher(MessageUser, InputDataUser):
"""
Base class for strategies for consecutive dispatching in a continuous period.
Expand Down Expand Up @@ -122,108 +129,3 @@ def validate(self, components, activity, times, meta):
# no validator, nothing needs to be changed
return {}

# ---------------------------------------------
# UTILITY METHODS
def _compute_cashflows(self, components, activity, times, meta, state_args=None, time_offset=0):
"""
Method to compute CashFlow evaluations given components and their activity.
@ In, components, list, HERON components whose cashflows should be evaluated
@ In, activity, DispatchState instance, activity by component/resources/time
@ In, times, np.array(float), time values to evaluate; may be length 1 or longer
@ In, meta, dict, additional info to be passed through to functional evaluations
@ In, state_args, dict, optional, additional arguments to pass while getting activity state
@ In, time_offset, int, optional, increase time index tracker by this value if provided
@ Out, total, float, total cashflows for given components
"""
if state_args is None:
state_args = {}

if meta['HERON']['Case'].use_levelized_inner:
total = self._compute_levelized_cashflows(components, activity, times, meta, state_args, time_offset)
return total

total = 0
specific_meta = dict(meta) # TODO what level of copying do we need here?
resource_indexer = meta['HERON']['resource_indexer']

#print('DEBUGG computing cashflows!')
for comp in components:
#print(f'DEBUGG ... comp {comp.name}')
specific_meta['HERON']['component'] = comp
comp_subtotal = 0
for t, time in enumerate(times):
#print(f'DEBUGG ... ... time {t}')
# NOTE care here to assure that pyomo-indexed variables work here too
specific_activity = {}
for tracker in comp.get_tracking_vars():
specific_activity[tracker] = {}
for resource in resource_indexer[comp]:
specific_activity[tracker][resource] = activity.get_activity(comp, tracker, resource, time, **state_args)
specific_meta['HERON']['time_index'] = t + time_offset
specific_meta['HERON']['time_value'] = time
cfs = comp.get_state_cost(specific_activity, specific_meta, marginal=True)
time_subtotal = sum(cfs.values())
comp_subtotal += time_subtotal
total += comp_subtotal
return total

def _compute_levelized_cashflows(self, components, activity, times, meta, state_args=None, time_offset=0):
"""
Method to compute CashFlow evaluations given components and their activity.
@ In, components, list, HERON components whose cashflows should be evaluated
@ In, activity, DispatchState instance, activity by component/resources/time
@ In, times, np.array(float), time values to evaluate; may be length 1 or longer
@ In, meta, dict, additional info to be passed through to functional evaluations
@ In, state_args, dict, optional, additional arguments to pass while getting activity state
@ In, time_offset, int, optional, increase time index tracker by this value if provided
@ Out, total, float, total cashflows for given components
"""
total = 0
specific_meta = dict(meta) # TODO what level of copying do we need here?
resource_indexer = meta['HERON']['resource_indexer']

# How does this work?
# The general equation looks like:
#
# SUM(Non-Multiplied Terms) + x * SUM(Multiplied Terms) = Target
#
# and we are solving for `x`. Target is 0 by default. Terms here are marginal cashflows.
# Summations here occur over: components, time steps, tracking variables, and resources.
# Typically, there is only 1 multiplied term/cash flow.

multiplied = 0
non_multiplied = 0

for comp in components:
specific_meta['HERON']['component'] = comp
multiplied_comp = 0
non_multiplied_comp = 0
for t, time in enumerate(times):
# NOTE care here to assure that pyomo-indexed variables work here too
specific_activity = {}
for tracker in comp.get_tracking_vars():
specific_activity[tracker] = {}
for resource in resource_indexer[comp]:
specific_activity[tracker][resource] = activity.get_activity(comp, tracker, resource, time, **state_args)
specific_meta['HERON']['time_index'] = t + time_offset
specific_meta['HERON']['time_value'] = time
cfs = comp.get_state_cost(specific_activity, specific_meta, marginal=True)

# there is an assumption here that if a component has a levelized cost, marginal cashflow
# then it is the only marginal cashflow
if comp.levelized_meta:
for cf in comp.levelized_meta.keys():
lcf = cfs.pop(cf) # this should be ok as long as HERON init checks are successful
multiplied_comp += lcf
else:
time_subtotal = sum(cfs.values())
non_multiplied_comp += time_subtotal

multiplied += multiplied_comp
non_multiplied += non_multiplied_comp

# at this point, there should be a not None NPV Target
multiplied += self._eps
total = (meta['HERON']['Case'].npv_target - non_multiplied) / multiplied
total *= -1
return total
Loading
Loading