Source code for postreise.analyze.generation.curtailment

import pandas as pd
from powersimdata.input.check import (
    _check_areas_are_in_grid_and_format,
    _check_resources_are_renewable_and_format,
)
from powersimdata.input.helpers import (
    decompose_plant_data_frame_into_areas,
    decompose_plant_data_frame_into_areas_and_resources,
    decompose_plant_data_frame_into_resources,
    decompose_plant_data_frame_into_resources_and_areas,
    get_plant_id_for_resources,
    get_plant_id_for_resources_in_area,
    summarize_plant_to_bus,
    summarize_plant_to_location,
)
from powersimdata.scenario.check import _check_scenario_is_in_analyze_state

from postreise.analyze.generation.summarize import (
    get_generation_time_series_by_resources,
)


[docs]def calculate_curtailment_time_series(scenario): """Calculate hourly curtailment for each renewable generator. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :return: (*pandas.DataFrame*) -- time series of curtailment. """ _check_scenario_is_in_analyze_state(scenario) grid = scenario.get_grid() pg = scenario.get_pg() plant_id = list( get_plant_id_for_resources( grid.model_immutables.plants["renewable_resources"].intersection( set(grid.plant.type) ), grid, ) ) profiles = pd.concat([scenario.get_solar(), scenario.get_wind()], axis=1) curtailment = (profiles[plant_id] - pg[plant_id]).clip(lower=0).round(6) return curtailment
[docs]def calculate_curtailment_time_series_by_resources(scenario, resources=None): """Calculate hourly curtailment by generator type(s). :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :param str/tuple/list/set resources: names of resources to analyze. Default is all renewable esources. :return: (*dict*) -- keys are resources, values are data frames indexed by (datetime, plant id). """ curtailment = calculate_curtailment_time_series(scenario) grid = scenario.get_grid() if resources is None: resources = grid.model_immutables.plants["renewable_resources"].intersection( set(grid.plant.type) ) else: resources = _check_resources_are_renewable_and_format( resources, mi=grid.model_immutables ) curtailment_by_resources = decompose_plant_data_frame_into_resources( curtailment, resources, grid ) return curtailment_by_resources
[docs]def calculate_curtailment_time_series_by_areas(scenario, areas=None): """Calculate hourly curtailment by area(s). :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :param dict areas: keys are area types ('*loadzone*', '*state*' or '*interconnect*'), values are a list of areas. Default is the interconnect of the scenario. Default is the scenario interconnect. :return: (*dict*) -- keys are areas, values are data frames indexed by (datetime, plant id). """ curtailment = calculate_curtailment_time_series(scenario) grid = scenario.get_grid() areas = ( _check_areas_are_in_grid_and_format(areas, grid) if areas is not None else {"interconnect": grid.interconnect} ) curtailment_by_areas = decompose_plant_data_frame_into_areas( curtailment, areas, grid ) return curtailment_by_areas
[docs]def calculate_curtailment_percentage_by_resources(scenario, resources=None): """Calculate scenario-long average curtailment fraction for a set of generator type(s). :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :param str/tuple/list/set resources: names of resources to analyze. Default is all renewable resources. :return: (*float*) -- average curtailment fraction over the scenario. """ curtailment = calculate_curtailment_time_series_by_resources(scenario, resources) resources = set(curtailment.keys()) grid = scenario.get_grid() total_curtailment = {r: curtailment[r].sum().sum() for r in resources} profiles = pd.concat([scenario.get_solar(), scenario.get_wind()], axis=1) total_potential = profiles.groupby(grid.plant.type, axis=1).sum().sum() curtailment_percentage = ( sum(v for v in total_curtailment.values()) / total_potential.loc[list(resources)].sum() ) return curtailment_percentage
[docs]def calculate_curtailment_time_series_by_areas_and_resources( scenario, areas=None, resources=None ): """Calculate hourly curtailment of each generator located in area(s) area and fueled by resource(s). :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :param dict areas: keys are area types ('*loadzone*', '*state*' or '*interconnect*'), values are a list of areas. Default is the scenario interconnect(s). :param str/tuple/list/set resources: names of resources to analyze. Default is all renewable resources. :return: (*dict*) -- keys are areas, values are dictionaries whose keys are resources and values are data frames indexed by (datetime, plant id). """ curtailment = calculate_curtailment_time_series(scenario) grid = scenario.get_grid() areas = ( _check_areas_are_in_grid_and_format(areas, grid) if areas is not None else {"interconnect": grid.interconnect} ) if resources is None: resources = grid.model_immutables.plants["renewable_resources"].intersection( set(grid.plant.type) ) else: resources = _check_resources_are_renewable_and_format( resources, mi=grid.model_immutables ) curtailment_by_areas_and_resources = ( decompose_plant_data_frame_into_areas_and_resources( curtailment, areas, resources, grid ) ) return curtailment_by_areas_and_resources
[docs]def calculate_curtailment_time_series_by_resources_and_areas( scenario, areas=None, resources=None ): """Calculate hourly curtailment of each generator fueled by resources and located in area(s). :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :param str/tuple/list/set resources: names of resources. Default is all renewable resources. :param dict areas: keys are area types ('*loadzone*', '*state*' or '*interconnect*'), values are a list of areas. Default is the scenario interconnect(s). :return: (*dict*) -- keys are resources, values are dictionaries whose keys are areas and values are data frames indexed by (timestamp, plant id). """ curtailment = calculate_curtailment_time_series(scenario) grid = scenario.get_grid() areas = ( _check_areas_are_in_grid_and_format(areas, grid) if areas is not None else {"interconnect": grid.interconnect} ) if resources is None: resources = grid.model_immutables.plants["renewable_resources"].intersection( set(grid.plant.type) ) else: resources = _check_resources_are_renewable_and_format( resources, mi=grid.model_immutables ) curtailment_by_resources_and_areas = ( decompose_plant_data_frame_into_resources_and_areas( curtailment, resources, areas, grid ) ) return curtailment_by_resources_and_areas
[docs]def summarize_curtailment_by_bus(scenario): """Calculate total curtailment by bus. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :return: (*dict*) -- keys are resources, values are dict of (bus: curtailment vector). """ curtailment = calculate_curtailment_time_series_by_resources(scenario) grid = scenario.get_grid() bus_curtailment = { ren_type: summarize_plant_to_bus(curtailment_df, grid).sum().to_dict() for ren_type, curtailment_df in curtailment.items() } return bus_curtailment
[docs]def summarize_curtailment_by_location(scenario): """Calculate total curtailment by location. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :return: (*dict*) -- keys are resources, values are dict of ((lat, lon): curtailment vector). """ curtailment = calculate_curtailment_time_series_by_resources(scenario) grid = scenario.get_grid() location_curtailment = { ren_type: summarize_plant_to_location(curtailment_df, grid).sum().to_dict() for ren_type, curtailment_df in curtailment.items() } return location_curtailment
[docs]def get_curtailment_time_series(scenario, area, area_type=None): """Get hourly curtailment for each available resource(s) in area. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance :param str area: one of *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str area_type: one of *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*pandas.DataFrame*) -- index: timestamps, columns: available renewable resource(s). """ grid = scenario.get_grid() renewables = grid.model_immutables.plants["renewable_resources"] curtailment = get_generation_time_series_by_resources( scenario, area, renewables, area_type=area_type ) renewable_profiles = pd.concat([scenario.get_wind(), scenario.get_solar()], axis=1) for r in renewables: if r in curtailment.columns: plant_id = get_plant_id_for_resources_in_area( scenario, area, r, area_type=area_type ) curtailment[r] = renewable_profiles[plant_id].sum(axis=1) - curtailment[r] curtailment.rename(lambda x: x + "_curtailment", axis="columns", inplace=True) return curtailment.clip(lower=0)