Source code for powersimdata.design.scenario_info

import warnings

from powersimdata.network.model import area_to_loadzone


def _check_state(scenario):
    """Check if the state of the scenario object is 'analyze'.

    :param powersimdata.scenario.scenario.Scenario scenario:
        scenario instance
    :raise TypeError: if the scenario is not in 'analyze' state.
    """
    if scenario.state.name != "analyze":
        raise TypeError("Scenario state must be 'analyze.'")


[docs]class ScenarioInfo: """Gather information from previous scenarios for capacity scaling. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance :raise TypeError: if the scenario is not in 'analyze' state. """ def __init__(self, scenario): _check_state(scenario) self.info = scenario.info self.pg = scenario.state.get_pg() self.grid = scenario.state.get_grid() self.demand = scenario.state.get_demand() self.grid_model = self.grid.grid_model solar = scenario.state.get_solar() wind = scenario.state.get_wind() hydro = scenario.state.get_hydro() self.profile = {"solar": solar, "wind": wind, "hydro": hydro}
[docs] def area_to_loadzone(self, area, area_type=None): """Map the query area to a list of loadzones. For more info, see :func:`powersimdata.network.model.area_to_loadzone`. :param str area: one of: *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str area_type: one of: *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*set*) -- set of loadzones associated to the query area """ return area_to_loadzone(self.grid_model, area, area_type)
def _check_time_range(self, start_time, end_time): """Check if the start_time and end_time define a valid time range of the given scenario :param str start_time: start timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str end_time: end timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :return: (*tuple*) -- a pair of integer indicates the index of the start timestamp and end timestamp in self.pg :raise ValueError: if the time range is invalid. """ if (start_time not in self.pg.index) or (end_time not in self.pg.index): available_times = "Available time range [%s, %s]" % ( str(self.pg.index[0]), str(self.pg.index[-1]), ) raise ValueError(f"Time range out of scope! {available_times}") start_i = self.pg.index.get_loc(start_time) end_i = self.pg.index.get_loc(end_time) if start_i > end_i: raise ValueError("Invalid time range: " "start_time falls behind end_time!") return start_i, end_i
[docs] def get_available_resource(self, area, area_type=None): """Find the available resources of a specific area in the grid of the given scenario :param str area: one of: *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str area_type: one of: *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*list*) -- a list of available resources in the query area """ loadzone_set = self.area_to_loadzone(area, area_type) available_resources = self.grid.plant[ self.grid.plant["zone_name"].isin(loadzone_set) ]["type"].unique() return available_resources.tolist()
[docs] def get_demand(self, area, start_time, end_time, area_type=None): """Calculate the total demand of the query area during the query time range of the given scenario :param str area: one of: *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str start_time: start timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str end_time: end timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str area_type: one of: *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*float*) -- total demand (in MWh) based on the specified parameters """ loadzone_set = self.area_to_loadzone(area, area_type) self._check_time_range(start_time, end_time) total_demand = ( self.demand.loc[ start_time:end_time, [self.grid.zone2id[loadzone] for loadzone in loadzone_set], ] .sum() .sum() ) return float(total_demand)
[docs] def get_capacity(self, gentype, area, area_type=None): """Calculate the total capacity of the query gentype in the query area of the given scenario :param str gentype: type of generator :param str area: one of: *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str area_type: one of: *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*float*) -- total capacity (in MW) based on the specified parameters """ loadzone_set = self.area_to_loadzone(area, area_type) total_capacity = self.grid.plant[ (self.grid.plant["type"] == gentype) & (self.grid.plant["zone_name"].isin(loadzone_set)) ]["Pmax"].sum() if total_capacity == 0: warnings.warn("No such type of generator in the area specified!") return float(total_capacity)
[docs] def get_generation(self, gentype, area, start_time, end_time, area_type=None): """Calculate the total generation of the query gentype in the query area during the query time range of the given scenario :param str gentype: type of generator :param str area: one of: *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str start_time: start timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str end_time: end timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str area_type: one of: *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*float*) -- total generation (in MWh) based on the specified parameters """ loadzone_set = self.area_to_loadzone(area, area_type) plant_id_list = list( self.grid.plant[ (self.grid.plant["type"] == gentype) & (self.grid.plant["zone_name"].isin(loadzone_set)) ].index ) query_pg_df = self.pg[plant_id_list] self._check_time_range(start_time, end_time) total_generation = query_pg_df.loc[start_time:end_time].sum().sum() return float(total_generation)
[docs] def get_profile_resource(self, gentype, area, start_time, end_time, area_type=None): """Calculate the total resource from profile of the query gentype in the query area during the query time range of the given scenario :param str gentype: type of generator :param str area: one of: *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str start_time: start timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str end_time: end timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str area_type: one of: *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*float*) -- total resource from profile (in MWh) based on the specified parameters :raise ValueError: if the resource type is invalid """ loadzone_set = self.area_to_loadzone(area, area_type) plant_id_list = list( self.grid.plant[ (self.grid.plant["type"] == gentype) & (self.grid.plant["zone_name"].isin(loadzone_set)) ].index ) if gentype not in self.profile: raise ValueError("Invalid resource type") query_profile_df = self.profile[gentype][plant_id_list] self._check_time_range(start_time, end_time) total_resource = query_profile_df.loc[start_time:end_time].sum().sum() return float(total_resource)
[docs] def get_curtailment(self, gentype, area, start_time, end_time, area_type=None): """Calculate the curtailment of the query gentype in the query area during the query time range of the given scenario :param str gentype: type of generator :param str area: one of: *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str start_time: start timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str end_time: end timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str area_type: one of: *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*float*) -- curtailment percentage (rounded up to two decimals) based on the specified parameters """ total_generation = self.get_generation( gentype, area, start_time, end_time, area_type ) total_profile_resource = self.get_profile_resource( gentype, area, start_time, end_time, area_type ) if total_profile_resource == 0 and total_generation == 0: return 0 curtailment = round(1 - (total_generation / total_profile_resource), 4) return float(curtailment)
[docs] def get_capacity_factor(self, gentype, area, start_time, end_time, area_type=None): """Calculate the capacity factor of the query gentype in the query area during the query time range of the given scenario :param str gentype: type of generator :param str area: one of: *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str start_time: start timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str end_time: end timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str area_type: one of: *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*float*) -- capacity factor based on the specified parameters :raise ZeroDivisionError: if no generator of gentype is found in the area """ start_i, end_i = self._check_time_range(start_time, end_time) total_hours = end_i - start_i + 1 total_capacity = self.get_capacity(gentype, area, area_type) if total_capacity == 0: raise ZeroDivisionError( "No such type of generator in the area " "specified. Division by zero." ) total_generation = self.get_generation( gentype, area, start_time, end_time, area_type ) cf = round(total_generation / (total_hours * total_capacity), 4) return float(cf)
[docs] def get_no_congest_capacity_factor( self, gentype, area, start_time, end_time, area_type=None ): """Calculate the no congestion capacity factor of the query gentype in the query area during the query time range of the given scenario :param str gentype: type of generator :param str area: one of: *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str start_time: start timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str end_time: end timestamp in the format *'YYYY-MM-DD HH:MM:SS'* :param str area_type: one of: *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*float*) -- no congestion capacity factor based on the specified parameters """ cf = self.get_capacity_factor(gentype, area, start_time, end_time, area_type) curtailment = self.get_curtailment( gentype, area, start_time, end_time, area_type ) no_congest_cf = round(cf / (1 - curtailment), 4) return float(no_congest_cf)