def create_message_log(self, start: datetime, end: datetime, resolution: timedelta, ems_agents): self.message_logs = dict() ems_names = [ems_agents[x].name for x, ems in enumerate(ems_agents)] ems_columns = ["DeviceMessage", "UdiEvent"] columns = [ "Prognosis Request", "Prognosis", "FlexRequest", "FlexOffer", "FlexOrder", ] periods = int(((end - start)) / resolution) self.message_logs[next] = dict() self.message_logs[next]["TA"] = initialize_df(columns=columns, start=start, end=end, resolution=resolution) self.message_logs[next]["MA"] = initialize_df(columns=columns, start=start, end=end, resolution=resolution) self.message_logs[next]["EMS"] = { ems: initialize_df(columns=ems_columns, start=start, end=end, resolution=resolution) for ems in ems_names } return
def single_curtailment_or_shift_each_day_between_12_and_14_pm( start: datetime, end: datetime, resolution: timedelta) -> DataFrame: """Each day it is valuable to curtail production between 2 and 3 am, or to shift consumption to that period.""" imbalance_start_time = "12:00" imbalance_end_time = "14:00" imbalance_value = -2 # MW imbalance_price_between_2_and_3_am = 10 # EUR/MWh imbalance_price_otherwise = 5 # EUR/MWh df = initialize_df( columns=["Imbalance (in MW)", "Price (in EUR/MWh)"], start=start, end=end, resolution=resolution, ) df["Imbalance (in MW)"] = 0 df["Imbalance (in MW)"].iloc[df.index.indexer_between_time( start_time=imbalance_start_time, end_time=imbalance_end_time, include_end=False, )] = imbalance_value df["Price (in EUR/MWh)"] = imbalance_price_otherwise df["Price (in EUR/MWh)"].iloc[df.index.indexer_between_time( start_time=imbalance_start_time, end_time=imbalance_end_time, include_end=False, )] = imbalance_price_between_2_and_3_am return df
def __init__( self, name, environment, flex_trade_horizon: timedelta, balancing_opportunities: DataFrame, # deviation_prices: Union[Tuple[int], int], # prognosis_policy: Callable, prognosis_parameter: dict, # flexrequest_policy: Callable, flexrequest_parameter: dict, # sticking_factor: float, # deviation_multiplicator: float, # imbalance_market_costs: Series, ): """ Creates an instance of the Class MarketAgent. Inherits from the Class Agent """ super().__init__(name, environment) commitment_data_columns = [ "Imbalances", "Imbalance market price", "Imbalance market costs", "Prognosis values", "Prognosis costs", "Requested flexibility", "Realised flexibility", "Commited flexibility", "Deviated flexibility", "Flexibility costs", "Deviation prices", "Deviation revenues", "Remaining imbalances", "Remaining market costs", "Opportunity costs", ] self.commitment_data = initialize_df( commitment_data_columns, environment.start, environment.end, environment.resolution ) self.commitment_data.loc[:, "Deviation prices"] = flexrequest_parameter["Deviation prices"] self.commitment_data.loc[:, "Imbalances"] = balancing_opportunities.loc[:, "Imbalance (in MW)"] self.commitment_data.loc[:, "Imbalance market price"] = balancing_opportunities.loc[:, "Price (in EUR/MWh)"] self.commitment_data.loc[:, "Imbalance market costs"] = self.commitment_data.loc[:, "Imbalances"] \ * self.commitment_data.loc[:, "Imbalance market price"] # self.commitment_data.loc[:, "Received flexibility"] = 0 self.flex_trade_horizon = flex_trade_horizon # self.balancing_opportunities = balancing_opportunities # self.deviation_prices = deviation_prices # self.deviation_prices_realised = [] # # self.sticking_factor = sticking_factor # self.prognosis_policy = prognosis_policy self.prognosis_parameter = prognosis_parameter # self.flexrequest_policy = flexrequest_policy self.flexrequest_parameter = flexrequest_parameter
def none_ever(start: datetime, end: datetime, resolution: timedelta) -> DataFrame: """No balancing opportunities ever.""" return initialize_df( columns=["Imbalance (in MW)", "Price (in EUR/MWh)"], start=start, end=end, resolution=resolution, )
def generated_imbalance_profile( start: datetime, end: datetime, resolution: timedelta, imbalance_range: Tuple, imbalance_price_1: float, imbalance_price_2: float, frequency: float, window_size: Tuple, imbalance_profile: Series = None, imbalance_prices: Series = None, ) -> DataFrame: """Generate imbalances for a given timeperiod.""" df = initialize_df( columns=["Imbalance (in MW)", "Price (in EUR/MWh)"], start=start, end=end, resolution=resolution, ) if imbalance_profile is None: dummy_index = DatetimeIndex(start=start, end=end, freq=resolution) num_samples = int(len(dummy_index) * frequency) windows_data = uniform(size=len(dummy_index)) windows = Series(data=windows_data, index=dummy_index) samples_df = Series(index=dummy_index) samples = [ windows.iloc[x:x + randint(window_size[0], window_size[1])] * choice([-1, 1]) for x in randint(len(windows), size=num_samples) ] for sample in samples: samples_df.loc[sample.index[0]:sample.index[-1]] = sample samples_df[ samples_df < 0] = samples_df[samples_df < 0] * imbalance_range[0] samples_df[ samples_df > 0] = samples_df[samples_df > 0] * imbalance_range[1] imbalance_profile = samples_df df["Imbalance (in MW)"] = 0 df["Imbalance (in MW)"].loc[start:end] = imbalance_profile.loc[ start:end] df["Price (in EUR/MWh)"] = where(df["Imbalance (in MW)"] == NaN, imbalance_price_1, imbalance_price_2) else: df["Imbalance (in MW)"].loc[start:end] = imbalance_profile df["Price (in EUR/MWh)"] = imbalance_prices # print("balance") # print(df) return df
def completely_unconstrained_profile(start: datetime, end: datetime, resolution: timedelta) -> DataFrame: """Can be used as a base model.""" return initialize_df( columns=[ "equals", "max", "min", "derivative equals", "derivative max", "derivative min", ], start=start, end=end, resolution=resolution, )
def single_curtailment_each_day_between_2_and_3_am( start: datetime, end: datetime, resolution: timedelta) -> DataFrame: """Each day it is valuable to curtail production between 2 and 3 am.""" opportunity_start_time = "2:00" opportunity_end_time = "3:00" imbalance_value = 100 # MW imbalance_price_between_2_and_3_am = 10 # EUR/MWh df = initialize_df( columns=["Imbalance (in MW)", "Price (in EUR/MWh)"], start=start, end=end, resolution=resolution, ) df["Imbalance (in MW)"].iloc[df.index.indexer_between_time( start_time=opportunity_start_time, end_time=opportunity_end_time, include_end=False, )] = imbalance_value df["Price (in EUR/MWh)"].iloc[df.index.indexer_between_time( start_time=opportunity_start_time, end_time=opportunity_end_time, include_end=False, )] = imbalance_price_between_2_and_3_am return df
def post_flex_request(self, prognosis: Prognosis) -> FlexRequest: """Callback function to let the Market Agent create a FlexRequest based on a Prognosis and post it to the Trading Agent.""" # Todo: rationalise the following # We assume that the balancing opportunities, which are put into the simulation as an exogenous variable, # already take into account the prognosis. # Get the Market Agent's unfulfilled balancing opportunities with positive value, which will become the # requested_power, flex_trade_window = ( self.environment.now, self.environment.now + self.flex_trade_horizon, ) requested_values = initialize_df( columns=["requested_power", "requested_flex", "requested_flex_imbalance_market_costs"], start=flex_trade_window[0], end=flex_trade_window[1], resolution=self.environment.resolution, ) if uniform(0, 0.99) >= self.flexrequest_parameter["Sticking factor"]: print("\n----------------MA: POST FLEX REQUEST---------------------") original_commitment_opportunities = self.commitment_data.loc[ flex_trade_window[0] : flex_trade_window[1] - self.environment.resolution, "Imbalances", ].values already_bought_commitment = around(self.commitment_data.loc[ flex_trade_window[0] : flex_trade_window[1] - self.environment.resolution, "Commited flexibility", ].values.astype("float64"),3) remaining_commitment_opportunities = [ opportunity - commitment if not isna(commitment) else opportunity for opportunity, commitment in zip( original_commitment_opportunities, already_bought_commitment ) ] remaining_commitment_opportunities = [ nan if opportunity == 0 else opportunity for opportunity in remaining_commitment_opportunities ] requested_values["Requested flexibility"] = remaining_commitment_opportunities requested_values["Requested power"] = prognosis.commitment.constants.loc[ flex_trade_window[0] : flex_trade_window[1]- self.environment.resolution] \ + remaining_commitment_opportunities # Get market costs for flexibility for the acutal horizon requested_values["Requested costs"] = self.commitment_data.loc[ flex_trade_window[0] : flex_trade_window[1] - self.environment.resolution, "Imbalance market costs"] # Only add market costs to reservation price for timesteps where requested flexibility is not nan self.flexrequest_parameter["Reservation price"] = 0 for enum, val in enumerate(requested_values["Requested flexibility"]): if val != nan: self.flexrequest_parameter["Reservation price"] += requested_values["Requested costs"].iloc[enum] print("\n MA: Reservation price: {}".format(self.flexrequest_parameter["Reservation price"])) print("\nMA: Already bought commitment: {}".format(already_bought_commitment)) print("MA: Remaining commitment opportunities: {}".format(remaining_commitment_opportunities)) print("\nMA: Requested Power values: {}".format(requested_values["Requested power"])) print("\nMA: Requested Flex values: {}\n".format(requested_values["Requested flexibility"])) print("\nMA: Requested Cost values: {}\n".format(requested_values["Requested costs"])) else: # TODO: Fix sticking requested_values["Requested power"] = prognosis.commitment.constants requested_values["Requested flexibility"] = nan print("----------------MA: STICKING ---------------------\n") print("MA: Request sticking to prognosis values: {}\n".format(prognosis.commitment.constants)) # Store requested flexibility in MA commitment data for val, index in zip(requested_values["Requested flexibility"], requested_values["Requested power"].index): if not isnull(val): self.commitment_data.loc[index, "Requested flexibility"] = val return FlexRequest( id=self.environment.plan_board.get_message_id(), requested_values=round(requested_values["Requested power"],2), requested_flexibility=round(requested_values["Requested flexibility"],2), costs=round(self.flexrequest_parameter["Reservation price"] - self.flexrequest_parameter["Markup"] ,2), deviation_cost_curve=DeviationCostCurve( gradient=( self.commitment_data.loc[self.environment.now, "Deviation prices"] * -1, self.commitment_data.loc[self.environment.now, "Deviation prices"], ), flow_unit_multiplier=self.environment.flow_unit_multiplier, ), prognosis=prognosis, )
def __init__( self, name, market_agent: MarketAgent, ems_agents: List[EMS], environment, flex_trade_horizon: timedelta, reprognosis_period: timedelta, # prognosis_policy: Callable, prognosis_parameter: dict, # prognosis_rounds: int, # prognosis_learning_parameter: dict, # flexrequest_policy: Callable, flexrequest_parameter: dict, # flexrequest_rounds: int, # flexrequest_learning_parameter: dict, central_optimization: bool = False, ): """ Creates an instance of the Class TradingAgent. Inherits from the Class Agent.""" super().__init__(name, environment) self.market_agent = market_agent self.ems_agents = ems_agents columns_aggregated_ems_data = [ "Activated EMS", "Requested power", "Requested flexibility", \ "Prog power", "Plan power", "Realised power", "Deviated power",\ "Prog flexibility", "Plan flexibility", "Realised flexibility", "Deviated flexibility", \ ] self.ems_data = initialize_df(columns_aggregated_ems_data, environment.start, environment.end, environment.resolution) columns_commitment_data = [ "Requested power", "Requested flexibility", "Commited power", "Realised power", "Deviated power", "Commited flexibility", "Realised flexibility", "Deviated flexibility", "Realised commitment costs", "Realised profits", "Average purchase price", "Average feedin price", "Deviation price up", "Deviation price down", "Opportunity costs", "Clearing price prognosis negotiations 1", "Clearing price flex negotiations 1", "Clearing price flex negotiations 2", ] self.commitment_data = initialize_df(columns_commitment_data, environment.start, environment.end, environment.resolution) self.commitment_data["Opportunity costs"] = 0 # self.cleared_prognosis_negotiations = DataFrame( # index=initialize_index( # environment.start, environment.end, environment.resolution # ), # columns=["Cleared", "Clearing Price"], # ) # # self.cleared_flex_negotiations = DataFrame( # index=initialize_index( # environment.start, environment.end, environment.resolution # ), # columns=["Cleared", "Clearing Price"], # ) # # self.realised_power = initialize_series( # None, environment.start, environment.end, environment.resolution # ) # self.sold_flex = initialize_series( # None, environment.start, environment.end, environment.resolution # ) # self.flex_revenues = initialize_series( # None, environment.start, environment.end, environment.resolution # ) # self.opportunity_costs = initialize_series( # 0, environment.start, environment.end, environment.resolution # ) # self.prognosis = initialize_series( # None, environment.start, environment.end, environment.resolution # ) self.flex_trade_horizon = flex_trade_horizon self.reprognosis_period = reprognosis_period self.central_optimization = central_optimization # self.flexrequest_parameter["Negotiation rounds"] = flexrequest_rounds # Prognosis negotiation inputs # self.prognosis_policy = prognosis_policy self.prognosis_parameter = prognosis_parameter # self.prognosis_q_parameter = prognosis_learning_parameter # self.prognosis_q_table_df_1 = DataFrame( # data=0, # index=range(1, prognosis_rounds + 1), # columns=self.prognosis_q_parameter["Action function"]( # action=None, markup=None, show_actions=True # ).keys(), # ) # self.prognosis_q_table_df_2 = DataFrame( # data=0, # index=range(1, prognosis_rounds + 1), # columns=self.prognosis_q_parameter["Action function"]( # action=None, markup=None, show_actions=True # ).keys(), # ) # self.prognosis_q_table_df_1.index.name = "Rounds" # self.prognosis_q_table_df_2.index.name = "Rounds" # self.prognosis_action_table_df_1 = deepcopy(self.prognosis_q_table_df_1) # self.prognosis_action_table_df_2 = deepcopy(self.prognosis_q_table_df_1) # # # Flexrequest negotiation inputs # self.flexrequest_policy = flexrequest_policy self.flexrequest_parameter = flexrequest_parameter