Exemplo n.º 1
0
def determine_opportunity_costs_model_b(
    previous_commitments: List[List[Commitment]],
    start: datetime,
    end: datetime,
    resolution: timedelta,
) -> Series:
    # Todo: set up at least one more model to determine opportunity costs
    # Todo (not urgent): slowly decay opportunity costs (to model a discounted sum of future returns)
    return initialize_series(0, start, end, resolution)
Exemplo n.º 2
0
def implement_your_own_flex_split_method_here(
        ems_agents: List[EMS], flex_request: FlexRequest) -> Series:
    """Todoc: write docstring."""
    return initialize_series(
        data=flex_request.commitment.constants.values,
        start=flex_request.start,
        end=flex_request.end,
        resolution=flex_request.resolution,
    )
Exemplo n.º 3
0
def determine_opportunity_costs_model_a(
    previous_commitments: List[List[Commitment]],
    start: datetime,
    end: datetime,
    resolution: timedelta,
) -> Series:
    """Always expect to make 1 buck. Just a silly implementation to get us started.
    Previous commitments is a list of commitments per EMS, i.e. a list of lists."""
    return initialize_series(1, start, end, resolution)
Exemplo n.º 4
0
def equal_flex_split_requested(ems_agents: List[EMS],
                               flex_request: FlexRequest,
                               environment) -> Series:
    """Split up the requested values in the FlexRequest equally amongst the EMS agents."""

    # TODO: Use environment.market_agent.commitment_data["Remaining Imbalances"]
    # flex_absolute = environment.market_agent.balancing_opportunities[
    #     "Imbalance (in MW)"
    # ] - environment.market_agent.commitment_data["Commited flexibility"].fillna(0)

    flex_absolute = environment.market_agent.commitment_data.loc[:,
                                                                 "Requested flexibility"]

    flex_absolute = initialize_series(
        data=[
            value / len(ems_agents)
            for value in flex_absolute[flex_request.start:flex_request.end -
                                       flex_request.resolution].values
        ],
        start=flex_request.start,
        end=flex_request.end,
        resolution=flex_request.resolution,
    )

    flex_relative = initialize_series(
        data=[
            value / len(ems_agents)
            for value in flex_request.commitment.constants.values
        ],
        start=flex_request.start,
        end=flex_request.end,
        resolution=flex_request.resolution,
    )

    environment.logfile.write("\nEQUAL FLEX SPLIT: {} \n".format(
        flex_absolute.values))

    return {"target_power": flex_relative, "target_flex": flex_absolute}
Exemplo n.º 5
0
    def create_prognosis(self, udi_events: List[UdiEvent]) -> Prognosis:
        """Todoc: write doc string."""
        # Todo: create prognosed values based on udi_events
        prognosed_values = initialize_series(
            data=None,
            start=udi_events[0].start,
            end=udi_events[0].end,
            resolution=udi_events[0].resolution,
        )

        for event in udi_events:
            prognosed_values = prognosed_values.add(
                event.commitment.constants[:], fill_value=0)

        return (
            Prognosis(
                id=self.environment.plan_board.get_message_id(),
                start=self.environment.now,
                end=self.environment.now + self.flex_trade_horizon,
                resolution=self.environment.resolution,
                prognosed_values=prognosed_values,
            ),
            udi_events,
        )
Exemplo n.º 6
0
def device_scheduler(
    device_constraints: List[DataFrame],
    ems_constraints: DataFrame,
    commitment_quantities: List[Series],
    commitment_downwards_deviation_price: Union[List[Series], List[float]],
    commitment_upwards_deviation_price: Union[List[Series], List[float]],
) -> Tuple[List[Series], List[float]]:
    """Schedule devices given constraints on a device and EMS level, and given a list of commitments by the EMS.
    The commitments are assumed to be with regards to the flow of energy to the device (positive for consumption,
    negative for production). The solver minimises the costs of deviating from the commitments, and returns the costs
    per commitment.
    Device constraints are on a device level. Handled constraints (listed by column name):
        max: maximum stock assuming an initial stock of zero (e.g. in MWh or boxes)
        min: minimum stock assuming an initial stock of zero
        derivative max: maximum flow (e.g. in MW or boxes/h)
        derivative min: minimum flow
        derivative equals: exact amount of flow
    EMS constraints are on an EMS level. Handled constraints (listed by column name):
        derivative max: maximum flow
        derivative min: minimum flow
    Commitments are on an EMS level. Parameter explanations:
        commitment_quantities: amounts of flow specified in commitments (both previously ordered and newly requested)
            - e.g. in MW or boxes/h
        commitment_downwards_deviation_price: penalty for downwards deviations of the flow
            - e.g. in EUR/MW or EUR/(boxes/h)
            - either a single value (same value for each flow value) or a Series (different value for each flow value)
        commitment_upwards_deviation_price: penalty for upwards deviations of the flow
    All Series and DataFrames should have the same resolution.
    For now we pass in the various constraints and prices as separate variables, from which we make a MultiIndex
    DataFrame. Later we could pass in a MultiIndex DataFrame directly.
    """

    # If the EMS has no devices, don't bother
    if len(device_constraints) == 0:
        return [], [] * len(commitment_quantities)

    # Check if commitments have the same time window and resolution as the constraints
    start = device_constraints[0].index.values[0]
    resolution = to_timedelta(device_constraints[0].index.freq)
    end = device_constraints[0].index.values[-1] + resolution
    if len(commitment_quantities) != 0:
        start_c = commitment_quantities[0].index.values[0]
        resolution_c = to_timedelta(commitment_quantities[0].index.freq)
        end_c = commitment_quantities[0].index.values[-1] + resolution
        if not (start_c == start and end_c == end):
            raise Exception(
                "Not implemented for different time windows.\n(%s,%s)\n(%s,%s)"
                % (start, end, start_c, end_c))
        if resolution_c != resolution:
            raise Exception(
                "Not implemented for different resolutions.\n%s\n%s" %
                (resolution, resolution_c))

    # Turn prices per commitment into prices per commitment flow
    if len(commitment_downwards_deviation_price) != 0:
        if all(not isinstance(price, Series)
               for price in commitment_downwards_deviation_price):
            commitment_downwards_deviation_price = [
                initialize_series(price, start, end, resolution)
                for price in commitment_downwards_deviation_price
            ]
    if len(commitment_upwards_deviation_price) != 0:
        if all(not isinstance(price, Series)
               for price in commitment_upwards_deviation_price):
            commitment_upwards_deviation_price = [
                initialize_series(price, start, end, resolution)
                for price in commitment_upwards_deviation_price
            ]

    # Determine appropriate overall bounds for power and price
    min_down_price = min(min(p) for p in commitment_downwards_deviation_price)
    max_down_price = max(max(p) for p in commitment_downwards_deviation_price)
    min_up_price = min(min(p) for p in commitment_upwards_deviation_price)
    max_up_price = max(max(p) for p in commitment_upwards_deviation_price)
    overall_min_price = min(min_down_price, min_up_price)
    overall_max_price = max(max_down_price, max_up_price)
    overall_min_power = min(ems_constraints["derivative min"])
    overall_max_power = max(ems_constraints["derivative max"])

    model = ConcreteModel()

    # Add indices for devices (d), datetimes (j) and commitments (c)
    model.d = RangeSet(0, len(device_constraints) - 1, doc="Set of devices")
    model.j = RangeSet(0,
                       len(device_constraints[0].index.values) - 1,
                       doc="Set of datetimes")
    model.c = RangeSet(0,
                       len(commitment_quantities) - 1,
                       doc="Set of commitments")

    # Add parameters
    def commitment_quantity_select(m, c, j):
        v = commitment_quantities[c].iloc[j]
        if isnan(v):  # Discount this nan commitment by setting the prices to 0
            commitment_downwards_deviation_price[c].iloc[j] = 0
            commitment_upwards_deviation_price[c].iloc[j] = 0
            return 0
        else:
            return v

    def price_down_select(m, c, j):
        return commitment_downwards_deviation_price[c].iloc[j]

    def price_up_select(m, c, j):
        return commitment_upwards_deviation_price[c].iloc[j]

    def device_max_select(m, d, j):
        v = device_constraints[d]["max"].iloc[j]
        if isnan(v):
            return infinity
        else:
            return v

    def device_min_select(m, d, j):
        v = device_constraints[d]["min"].iloc[j]
        if isnan(v):
            return -infinity
        else:
            return v

    def device_derivative_max_select(m, d, j):
        max_v = device_constraints[d]["derivative max"].iloc[j]
        equal_v = device_constraints[d]["derivative equals"].iloc[j]
        if isnan(max_v) and isnan(equal_v):
            return infinity
        else:
            return nanmin([max_v])

    def device_derivative_min_select(m, d, j):
        min_v = device_constraints[d]["derivative min"].iloc[j]
        equal_v = device_constraints[d]["derivative equals"].iloc[j]
        if isnan(min_v) and isnan(equal_v):
            return -infinity
        else:
            return nanmax([min_v])

    def device_derivative_equal_select(m, d, j):
        min_v = device_constraints[d]["derivative min"].iloc[j]
        equal_v = device_constraints[d]["derivative equals"].iloc[j]
        if isnan(equal_v):
            return 0
        else:
            return nanmax([equal_v])

    def ems_derivative_max_select(m, j):
        v = ems_constraints["derivative max"].iloc[j]
        if isnan(v):
            return infinity
        else:
            return v

    def ems_derivative_min_select(m, j):
        v = ems_constraints["derivative min"].iloc[j]
        if isnan(v):
            return -infinity
        else:
            return v

    model.commitment_quantity = Param(model.c,
                                      model.j,
                                      initialize=commitment_quantity_select)
    model.up_price = Param(model.c, model.j, initialize=price_up_select)
    model.down_price = Param(model.c, model.j, initialize=price_down_select)
    model.device_max = Param(model.d, model.j, initialize=device_max_select)
    model.device_min = Param(model.d, model.j, initialize=device_min_select)
    model.device_derivative_max = Param(
        model.d, model.j, initialize=device_derivative_max_select)
    model.device_derivative_min = Param(
        model.d, model.j, initialize=device_derivative_min_select)
    model.device_derivative_equal = Param(
        model.d, model.j, initialize=device_derivative_equal_select)

    model.ems_derivative_max = Param(model.j,
                                     initialize=ems_derivative_max_select)
    model.ems_derivative_min = Param(model.j,
                                     initialize=ems_derivative_min_select)

    # Add variables
    model.power = Var(
        model.d,
        model.j,
        domain=Reals,
        initialize=0,
        bounds=(overall_min_power, overall_max_power),
    )

    # Add constraints as a tuple of (lower bound, value, upper bound)
    def device_bounds(m, d, j):
        return (
            m.device_min[d, j],
            sum(m.power[d, k] for k in range(0, j + 1)),
            m.device_max[d, j],
        )

    def device_derivative_bounds(m, d, j):
        return (
            m.device_derivative_min[d, j],
            m.power[d, j] - m.device_derivative_equal[d, j],
            m.device_derivative_max[d, j],
        )

    def ems_derivative_bounds(m, j):
        return m.ems_derivative_min[j], sum(
            m.power[:, j]), m.ems_derivative_max[j]

    model.device_energy_bounds = Constraint(model.d,
                                            model.j,
                                            rule=device_bounds)
    model.device_power_bounds = Constraint(model.d,
                                           model.j,
                                           rule=device_derivative_bounds)
    model.ems_power_bounds = Constraint(model.j, rule=ems_derivative_bounds)

    # Add logical disjunction for deviations
    model.price = Var(model.c,
                      model.j,
                      initialize=0,
                      bounds=(overall_min_price, overall_max_price))

    def up_linker(b, c, d, j):
        # print("In up linker")
        m = b.model()
        ems_power_in_j = sum(m.power[d, j] for d in m.d)
        ems_power_deviation = ems_power_in_j - m.commitment_quantity[c, j]
        # try:
        # print(value(ems_power_deviation))
        # except:
        # pass
        b.linker = Constraint(expr=m.price[c, j] == m.up_price[c, j])
        b.constr = Constraint(expr=ems_power_deviation >= 0)
        b.BigM = Suffix(direction=Suffix.LOCAL)
        b.BigM[b.linker] = 10e5
        return

    def down_linker(b, c, d, j):
        # print("In down linker")
        m = b.model()
        ems_power_in_j = sum(m.power[d, j] for d in m.d)
        ems_power_deviation = ems_power_in_j - m.commitment_quantity[c, j]
        # try:
        # print(value(ems_power_deviation))
        # except:
        # pass
        b.linker = Constraint(expr=m.price[c, j] == m.down_price[c, j])
        b.constr = Constraint(expr=ems_power_deviation <= 0)
        b.BigM = Suffix(direction=Suffix.LOCAL)
        b.BigM[b.linker] = 10e5
        return

    # def zero_linker(b, c, d, j):
    #     #print("In down linker")
    #     m = b.model()
    #     ems_power_in_j = sum(m.power[d, j] for d in m.d)
    #     ems_power_deviation = ems_power_in_j - m.commitment_quantity[c, j]
    #     #try:
    #         #print(value(ems_power_deviation))
    #     #except:
    #         #pass
    #     b.linker = Constraint(expr=m.price[c, j] == 0)
    #     b.constr = Constraint(expr=ems_power_deviation == 0)
    #     #b.BigM = Suffix(direction=Suffix.LOCAL)
    #     #b.BigM[b.linker] = 10e10
    #     return

    model.up_deviation = Disjunct(model.c, model.d, model.j, rule=up_linker)
    model.down_deviation = Disjunct(model.c,
                                    model.d,
                                    model.j,
                                    rule=down_linker)

    # model.zero_deviation = Disjunct(model.c, model.d, model.j, rule=zero_linker)

    def bind_prices(m, c, d, j):
        return [
            model.up_deviation[c, d, j],
            model.down_deviation[c, d, j],
            # model.zero_deviation[c, d, j]
        ]

    model.up_or_down_deviation = Disjunction(model.c,
                                             model.d,
                                             model.j,
                                             rule=bind_prices,
                                             xor=True)

    # Add objective
    def cost_function(m):
        costs = 0
        for j in m.j:
            for c in m.c:
                ems_power_in_j = sum(m.power[d, j] for d in m.d)
                ems_power_deviation = ems_power_in_j - m.commitment_quantity[c,
                                                                             j]
                costs += ems_power_deviation * m.price[c, j]
        return costs

    model.costs = Objective(rule=cost_function, sense=minimize)

    # def xfrm(m):
    #     TransformationFactory('gdp.chull').apply_to(m)
    # model.xfrm = BuildAction(rule=xfrm)

    # Transform and solve
    xfrm = TransformationFactory("gdp.bigm")
    xfrm.apply_to(model)
    solver = SolverFactory(
        "cplex", executable="D:/CPLEX/Studio/cplex/bin/x64_win64/cplex")
    # solver.options['CPXchgprobtype'] = "CPXPROB_QP"
    # solver.options["solver"] = "CPXqpopt"
    solver.options["qpmethod"] = 1
    solver.options["optimalitytarget"] = 3

    # solver.options["acceptable_constr_viol_tol"] = 10
    # solver.options['acceptable_tol'] = 1
    # solver.options['acceptable_dual_inf_tol'] = 10
    # solver.options['acceptable_compl_inf_tol'] = 10
    results = solver.solve(model, tee=False)

    planned_costs = value(model.costs)
    planned_power_per_device = []
    for d in model.d:
        planned_device_power = [
            round(model.power[d, j].value, 3) for j in model.j
        ]
        planned_power_per_device.append(
            initialize_series(planned_device_power,
                              start=start,
                              end=end,
                              resolution=resolution))
    # model.display()
    # results.pprint()
    # model.down_deviation.pprint()
    # model.up_deviation.pprint()
    # model.power.pprint()

    # print(planned_power_per_device)
    # input()

    # Redo the cost calculation, because before the solver actually approximated the prices.
    def redo_cost_calculation(m):
        commitments_costs = []
        for c in m.c:
            commitment_cost = 0
            for j in m.j:

                ems_power_in_j = sum(m.power[d, j] for d in m.d)
                ems_power_deviation = ems_power_in_j - m.commitment_quantity[c,
                                                                             j]

                if value(ems_power_deviation) >= 0:
                    commitment_cost += round(
                        value(ems_power_deviation * m.up_price[c, j]), 3)

                else:
                    commitment_cost += round(
                        value(ems_power_deviation * m.down_price[c, j]), 3)

            commitments_costs.append(commitment_cost)
        return commitments_costs

    planned_costs_per_commitment = redo_cost_calculation(model)
    # print(planned_costs_per_commitment)

    return planned_power_per_device, planned_costs_per_commitment
Exemplo n.º 7
0
def create_adverse_and_plain_offers(self, flex_request, best_udi_event,
                                    opportunity_costs, plan_board):
    ''' Adverse_flexoffer:
                           Offered power and offered flexibility values comply with the requested ones, regardless of the actual values
                           the EMS has planned.Hence, the commitment costs include deviation costs for each datetime where the EM-Systems plan to deviate.
        Plain_flexoffer:   Offered values and offered flexibility comply with the planned values of the EM-Systems.
                           Commitment costs include only the cost difference of the contract costs.'''

    start = flex_request.start
    end = flex_request.end - flex_request.resolution
    offered_flexibility = best_udi_event.offered_flexibility
    requested_flexibility = flex_request.requested_flexibility
    deviation_costs_best_udi_event = initialize_series(
        data=None,
        start=flex_request.start,
        end=flex_request.end,
        resolution=flex_request.resolution)

    # for index, row in offered_flexibility.loc[start:end].iteritems():
    #
    #     if offered_flexibility.loc[index] is not nan_to_num(requested_flexibility.loc[index]):
    #
    #         if offered_flexibility.loc[index] > requested_flexibility.loc[index]:
    #
    #             diff = (offered_flexibility.loc[index] - requested_flexibility[index])
    #
    #             if diff > 0:
    #
    #                 deviation_costs_best_udi_event[index] = diff * best_udi_event.deviation_cost_curve.gradient_up
    #             else:
    #                 deviation_costs_best_udi_event[index] = diff * best_udi_event.deviation_cost_curve.gradient_down
    #
    #         elif offered_flexibility.loc[index] < requested_flexibility.loc[index]:
    #             diff = (requested_flexibility[index] - offered_flexibility.loc[index])
    #
    #             if diff > 0:
    #
    #                 deviation_costs_best_udi_event[index] = diff * best_udi_event.deviation_cost_curve.gradient_up
    #             else:
    #                 deviation_costs_best_udi_event[index] = diff * best_udi_event.deviation_cost_curve.gradient_down
    #
    #         else:
    #             deviation_costs_best_udi_event[index] = 0

    # If deviation costs in best_udi_event are not defined, overwrite the nan with 0 (otherwise the following calculations fail due to nan)
    # for index, row in best_udi_event.deviation_costs.loc[start:end].iteritems():
    #
    #     if isnull(best_udi_event.deviation_costs.loc[index]):
    #
    #         deviation_costs_best_udi_event[index] = 0
    #
    #     else:
    #         deviation_costs_best_udi_event[index] = best_udi_event.deviation_cost.loc[index]

    # adverse_values = flex_request.requested_values.loc[start : end].where(isnull(requested_flexibility) == False)
    #
    # adverse_flexibility = flex_request.requested_flexibility.loc[start : end].where(isnull(requested_flexibility) == False)
    #
    # commitment_costs_best_udi_event = best_udi_event.costs \
    #                                   + opportunity_costs.loc[start : end].sum(axis="index")
    #
    # adverse_flex_offer = FlexOffer(
    #                               id=plan_board.get_message_id(),
    #                               description="Adverse flex offer",
    #                               offered_values=adverse_values,
    #                               offered_flexibility=adverse_flexibility,
    #                               deviation_cost_curve=flex_request.commitment.deviation_cost_curve,
    #                               costs=commitment_costs_best_udi_event,
    #                               flex_request=flex_request,
    #                               )

    plain_values = best_udi_event.offered_values.loc[start:end]

    plain_flexibility = offered_flexibility.loc[start:end]

    commitment_costs_best_udi_event = best_udi_event.costs \
                                      + opportunity_costs.loc[start : end].sum(axis="index")

    plain_flex_offer = FlexOffer(
        id=plan_board.get_message_id(),
        description="Plain flex offer",
        offered_values=plain_values,
        offered_flexibility=plain_flexibility,
        deviation_cost_curve=flex_request.commitment.deviation_cost_curve,
        costs=commitment_costs_best_udi_event,
        flex_request=flex_request,
    )
    flex_offers = []
    flex_offers.append(plain_flex_offer)

    # Only take both offers if values are different
    # if adverse_flex_offer.costs.sum(axis=0) is not plain_flex_offer.costs.sum(axis=0):
    #     flex_offers.append(plain_flex_offer)
    #
    # else:
    #     flex_offers.append(adverse_flex_offer)
    #     flex_offers.append(plain_flex_offer)

    return flex_offers
Exemplo n.º 8
0
    def step(self):

        print(
            "+++++++++++++++++++++ NEW STEP +++++++++++++++++++++    TIME: {}\n"
            .format(self.environment.now))

        # Pull market agent request for prognosis
        prognosis_request = self.market_agent.post_prognosis_request()
        # Add prognosis request to plan board
        self.environment.plan_board.store_message(
            timeperiod=self.environment.now,
            message=prognosis_request,
            keys=["TA", "MA"],
        )

        # Decision Gate 1: TA and MA bargain over prognosis price

        # Update parameters related to q-learning
        # self.prognosis_q_parameter["Step now"] = self.environment.step_now
        print(
            "---------------------PROGNOSIS NEGOTIATION--------------------------"
        )

        update_adaptive_strategy_data(description="Prognosis",
                                      ta_parameter=self.prognosis_parameter,
                                      plan_board=self.environment.plan_board,
                                      timeperiod_now=self.environment.now,
                                      step_now=self.environment.step_now,
                                      snapshot=True)

        prognosis_decision = start_negotiation(
            description="Prognosis",
            environment_now=self.environment.now,
            ta_parameter=self.prognosis_parameter,
            ma_parameter=self.environment.market_agent.prognosis_parameter,
            plan_board=self.environment.plan_board,
            negotiation_log=self.environment.plan_board.
            prognosis_negotiations_log,
        )

        # If the negotiation got cleared let the model continue, otherwise proceed to next step of simulation horizon
        if "Not Cleared" in prognosis_decision["Status"]:
            self.commitment_data.loc[
                self.environment.now,
                "Clearing price prognosis negotiations 1"] = nan
            return
        else:
            print("TA: Prognosis negotiation status: AGREEMENT\n")
            self.commitment_data.loc[
                self.environment.now,
                "Clearing price prognosis negotiations 1"] = prognosis_decision[
                    "Clearing price"]
            pass

        # Pull UdiEvents while pushing empty DeviceMessages to each EMS
        print(
            "---------------------PROGNOSIS UDI EVENTS--------------------------"
        )
        udi_events = []
        for ems in self.ems_agents:
            # Create empty device message
            device_message = self.create_device_message(
                ems,
                description="Prognosis data",
                targeted_power=initialize_series(
                    None,
                    self.environment.now,
                    self.environment.now + self.flex_trade_horizon,
                    self.environment.resolution,
                ),
                targeted_flexibility=initialize_series(
                    0,
                    self.environment.now,
                    self.environment.now + self.flex_trade_horizon,
                    self.environment.resolution,
                ),
                deviation_cost_curve=DeviationCostCurve(
                    gradient=0,
                    flow_unit_multiplier=self.environment.flow_unit_multiplier,
                ),
            )
            # Add DeviceMessage to plan board
            self.environment.plan_board.store_message(
                timeperiod=self.environment.now,
                message=device_message,
                keys=[ems.name])

            # Get UDI event
            udi_events.append(ems.post_udi_event(device_message))

        # Add UDI events to plan board
        for event in udi_events:
            self.environment.plan_board.store_message(
                timeperiod=self.environment.now,
                message=event,
                keys=[ems.name])

        # Determine Prognosis
        prognosis, prognosis_udi_events = self.create_prognosis(udi_events)

        print("TA: Prognosis event flexibility: {}".format(
            prognosis_udi_events[0].offered_flexibility))

        # Add Prognosis to planboard message log
        self.environment.plan_board.store_message(
            timeperiod=self.environment.now,
            message=prognosis,
            keys=["TA", "MA"])

        # TODO: Decision Gate 2: If prognosis is very interesting, than another negotiation over the prognosis price starts

        # Pull FlexRequest while pushing Prognosis to MA
        flex_request = self.market_agent.post_flex_request(
            self.market_agent.get_prognosis(prognosis))

        # Add message to plan board
        self.environment.plan_board.store_message(
            timeperiod=self.environment.now,
            message=flex_request,
            keys=["TA", "MA"])

        # Determine FlexOffer (could be multiple offers)
        flex_offers, flex_offer_udi_events = self.create_flex_offer(
            flex_request)

        print("TA: Flexrequest udi event flexibility: {}\n".format(
            flex_offer_udi_events[0].offered_flexibility))

        # Flex Decision Gate 1: TA and MA bargain over flex request price
        update_adaptive_strategy_data(description="Flexrequest",
                                      ta_parameter=self.flexrequest_parameter,
                                      plan_board=self.environment.plan_board,
                                      timeperiod_now=self.environment.now,
                                      step_now=self.environment.step_now,
                                      snapshot=True)

        for enum, offer in enumerate(flex_offers):

            self.flexrequest_parameter["Reservation price"] = offer.costs.sum(
                axis="index")

            # Submit flexoffer with adverse costs to MA
            self.market_agent.get_flex_offer(offer)
            self.environment.plan_board.store_message(
                timeperiod=self.environment.now,
                message=offer,
                keys=["TA", "MA"])

            print("TA: Market agent reservation price {}\n".format(
                self.environment.market_agent.
                flexrequest_parameter["Reservation price"]))
            print("TA: Trading agent reservation price {}\n".format(
                self.flexrequest_parameter["Reservation price"]))

            flexrequest_decision = start_negotiation(
                description=offer.description,
                environment_now=self.environment.now,
                ta_parameter=self.flexrequest_parameter,
                ma_parameter=self.environment.market_agent.
                flexrequest_parameter,
                plan_board=self.environment.plan_board,
                negotiation_log=self.environment.plan_board.
                flexrequest_negotiations_log,
            )

            print("TA: Flex negotiation status: {}\n".format(
                flexrequest_decision["Status"]))
            print("TA: Flex negotiation clearing price: {}\n".format(
                flexrequest_decision["Clearing price"]))
            print(
                "----------------------DATA VALUE UPDATE--------------------------       TIME: {} \n"
                .format(self.environment.now))

            if "NOT CLEARED" in flexrequest_decision["Status"]:

                # Check if another offer is available and continue with the next negotiation
                if enum is not len(flex_offers) - 1:
                    print("Next Round")
                    print("TA: Next offer: {}".format(offer[enum + 1]))
                    continue

                # If there is no offer left to bargain over, save agents data and return
                else:

                    for ems, event in zip(self.ems_agents,
                                          prognosis_udi_events):

                        print(
                            "TA: Store prognosis flex as commited: {}".format(
                                event.offered_flexibility.
                                loc[offer.start:offer.end - offer.resolution]))

                        # Assign the prognosed udi event values to a device message, and pass it to the EMS for storing data and commitment
                        ems.store_data(
                            commitments=event.commitment,
                            device_message=self.create_device_message(
                                ems,
                                description="Failed Negotiation",
                                targeted_power=event.offered_values.
                                loc[offer.start:offer.end - offer.resolution],
                                targeted_flexibility=event.offered_flexibility.
                                loc[offer.start:offer.end - offer.resolution],
                                deviation_cost_curve=(0, 0),
                                costs=event.costs,
                                order=True),
                        )
                    return

            # "CLEARED"
            else:
                # Assign the offered udi event values to a device message, and pass it to the EMS for storing data and commitment
                for ems, event in zip(self.ems_agents, flex_offer_udi_events):

                    print("TA: Cleared negotiaton over {}\n".format(
                        offer.description))

                    commited_power = deepcopy(event.offered_values)
                    commited_flexibility = deepcopy(event.offered_flexibility)

                    # This loop cuts already commited values from actual offer (-> applicable commitments)
                    for index, row in offer.offered_values.iteritems():

                        if isnull(offer.offered_flexibility.loc[index]):
                            commited_flexibility.loc[index] = nan

                        if isnull(offer.offered_values.loc[index]):
                            commited_power.loc[index] = nan

                    self.store_commitment_data(commited_power,
                                               commited_flexibility)

                    ems.store_data(
                        commitments=event.commitment,
                        device_message=self.create_device_message(
                            ems,
                            description="Succeeded Negotiation",
                            targeted_power=commited_power,
                            targeted_flexibility=commited_flexibility,
                            deviation_cost_curve=flex_request.commitment.
                            deviation_cost_curve,
                            costs=event.costs,
                            order=True),
                    )

                # Pull FlexOrder by pushing FlexOffer to MA
                flex_order = self.market_agent.post_flex_order(
                    self.market_agent.get_flex_offer(flex_offer=offer,
                                                     order=True))

                #Add Order to planboard message log
                self.environment.plan_board.store_message(
                    timeperiod=self.environment.now,
                    message=flex_order,
                    keys=["TA", "MA"])

            return
Exemplo n.º 9
0
    def create_flex_offer(
            self,
            flex_request: FlexRequest) -> Tuple[FlexOffer, List[UdiEvent]]:
        """Create a FlexOffer after freely exploring EMS flexibility."""

        # Todo: set aspiration margin
        udi_events_local_memory = []
        udi_event_cnt = 0
        udi_event_costs = float("inf")

        # Either do a central optimisation just for the analysis, or do a flex split
        if self.central_optimization is True:
            # Todo: create a UdiEvent based on central optimization
            best_udi_event = None
            best_udi_events = [None]
        else:
            # Todo: write more flex_split_methods if needed, where each method results in one UdiEvent
            flex_split_methods = [
                equal_flex_split_requested,
                # implement_your_own_flex_split_method_here,
            ]
            aggregated_udi_events = []
            for flex_split_method in flex_split_methods:
                # TODO: Find other way to get absolute flex values and remove environment from arguments then
                output = flex_split_method(self.ems_agents, flex_request,
                                           self.environment)
                targeted_power = output["target_power"]
                targeted_flexibility = output["target_flex"]

                # Find out how well the EMS agents can fulfil the FlexRequest.
                udi_events = []
                for ems in self.ems_agents:

                    # TODO: Get targeted_flex into device message and store values at EMS
                    for index, row in targeted_flexibility.iteritems():
                        ems.ems_data.loc[
                            index,
                            "Requested flexibility"] = targeted_flexibility.loc[
                                index]

                    # Determine DeviceMessage
                    device_message = self.create_device_message(
                        ems,
                        description="Flex request",
                        targeted_power=targeted_power,
                        targeted_flexibility=targeted_flexibility,
                        deviation_cost_curve=flex_request.commitment.
                        deviation_cost_curve,
                    )

                    # Pull UdiEvent while pushing DeviceMessage to EMS
                    udi_events.append(
                        ems.post_udi_event(
                            ems.get_device_message(device_message)))

                udi_events_local_memory.append(udi_events)

                # Calculate aggregated power values and total costs (private and bid)

                offered_values_aggregated = initialize_series(
                    data=[
                        sum(x) for x in zip(*[
                            udi_event.offered_power.values
                            for udi_event in udi_events
                        ])
                    ],
                    start=udi_events[0].start,
                    end=udi_events[0].end,
                    resolution=udi_events[0].resolution,
                )

                offered_flexibility_aggregated = initialize_series(
                    data=[
                        sum(x) for x in zip(*[
                            udi_event.offered_flexibility.values
                            for udi_event in udi_events
                        ])
                    ],
                    start=udi_events[0].start,
                    end=udi_events[0].end,
                    resolution=udi_events[0].resolution,
                )

                offered_costs_aggregated = initialize_series(
                    data=[
                        sum(x) for x in zip(*[
                            udi_event.costs.values for udi_event in udi_events
                        ])
                    ],
                    start=udi_events[0].start,
                    end=udi_events[0].end,
                    resolution=udi_events[0].resolution,
                )

                # Check if already commited values are same as actual offer values. If true, don't offer them again.
                offered_values_aggregated, offered_flexibility_aggregated, offered_costs_aggregated = \
                    sort_out_already_commited_values(self,
                                                     offered_values_aggregated,
                                                     offered_flexibility_aggregated,
                                                     offered_costs_aggregated)

                #TODO: Check if dev costs and contract costs are still used
                aggregated_udi_events.append(
                    UdiEvent(
                        id=
                        -1,  # Not part of the plan board, as this is just a convenient object for the Trading Agent
                        offered_values=offered_values_aggregated,
                        offered_flexibility=offered_flexibility_aggregated,
                        contract_costs=sum([
                            udi_event.contract_costs
                            for udi_event in udi_events
                        ]),
                        deviation_costs=sum([
                            udi_event.deviation_costs
                            for udi_event in udi_events
                        ]),
                        deviation_cost_curve=flex_request.commitment.
                        deviation_cost_curve,
                        costs=offered_costs_aggregated,
                    ))

            # Todo: choose the best aggregated UdiEvent (implement policies as separate module)
            best_udi_event = None
            for event in aggregated_udi_events:
                if event.costs.sum() < udi_event_costs:
                    best_udi_event = event
                else:
                    pass
                if len(aggregated_udi_events) > 1:
                    udi_event_cnt += 1

        # Unpack opportunity costs for actual horizon
        opportunity_costs = self.commitment_data["Opportunity costs"].loc[
            flex_request.start:flex_request.end - flex_request.resolution]

        # NOTE: Use function call to create one or multiple specific offers after UDI-event aggregation
        # TODO: Move function create_adverse_and_plain_offers from comopt.utils to comopt.model.utils
        # -> There's some (recursive?) issue when loading the dependencies that i couldn't get solved (yet).

        flex_offers = create_adverse_and_plain_offers(
            self, flex_request, best_udi_event, opportunity_costs,
            self.environment.plan_board)

        # Todo: suggest DeviationCostCurve
        # deviation_cost_curve = DeviationCostCurve(gradient=1, flow_unit_multiplier=self.environment.flow_unit_multiplier)

        #------------- PRINTS ---------------#
        for offer in flex_offers:
            print("\nTA: Costs/Power/Flex {}: {} {} {}\n".format(
                offer.description, offer.costs, offer.offered_values,
                offer.offered_flexibility))
            # print("TA: Power {}: {}\n".format(offer.description, )
            # print("TA: Flexibility {}: {}\n".format(offer.description, ))

        # print("\nTA: UDI Event flex: {}\n".format(udi_events_local_memory[udi_event_cnt][0].offered_flexibility))

        return flex_offers, udi_events_local_memory[udi_event_cnt]