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). """ renewables = ["wind", "wind_offshore", "solar"] curtailment = get_generation_time_series_by_resources(scenario, area, renewables, area_type=area_type) renewable_profiles = pd.concat( [scenario.state.get_wind(), scenario.state.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)
def get_capacity_by_resources(scenario, area, resources, area_type=None): """Get the total nameplate capacity for generator type(s) in an area :param powersimdata.scenario.scenario.Scenario scenario: scenario instance :param str area: one of *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str/list resources: one or a list of resources :param str area_type: one of *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*pandas.Series*) -- index: resources, column: total nameplate Pmax capacity """ plant_id = get_plant_id_for_resources_in_area(scenario, area, resources, area_type=area_type) grid = scenario.state.get_grid() return grid.plant.loc[plant_id].groupby("type")["Pmax"].sum()
def get_generation_time_series_by_resources(scenario, area, resources, area_type=None): """Get hourly total generation for generator type(s) in an area. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance :param str area: one of *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str/list resources: one or a list of resources :param str area_type: one of *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*pandas.DataFrame*) -- times series of generation for each resource, index: timestamps, columns: resources """ _check_scenario_is_in_analyze_state(scenario) plant_id = get_plant_id_for_resources_in_area( scenario, area, resources, area_type=area_type ) pg = scenario.state.get_pg()[plant_id] grid = scenario.state.get_grid() return pg.groupby(grid.plant.loc[plant_id, "type"].values, axis=1).sum()
def get_capacity_factor_time_series(scenario, area, resources, area_type=None): """Get the hourly capacity factor of each generator fueled by resource(s) in an area. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance :param str area: one of *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str/list resources: one or a list of resources :param str area_type: one of *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :return: (*pandas.DataFrame*) -- index: timestamps, column: plant ids, value: capacity factors """ _check_scenario_is_in_analyze_state(scenario) plant_id = get_plant_id_for_resources_in_area(scenario, area, resources, area_type=area_type) pg = scenario.state.get_pg()[plant_id] capacity = scenario.state.get_grid().plant.loc[plant_id, "Pmax"] cf = (pg / capacity).replace(np.inf, 0).clip(0, 1) return cf
def test_get_plant_id_for_resources_in_area(): arg = [(scenario, "Washington", "coal"), (scenario, "all", "coal")] expected = [["A"], ["A", "C"]] for a, e in zip(arg, expected): plant_id = get_plant_id_for_resources_in_area(*a) assert e == plant_id
def plot_scatter_capacity_vs_cost_curve_slope( scenario, area, resources, area_type=None, markersize=50, fontsize=20, title=None, show_plot=True, ): """Generate for a given scenario the scatter plot of the capacity (x-axis) vs cost curve slope (y-axis) of generators located in area and fueled by resources :param powersimdata.scenario.scenario.Scenario scenario: scenario instance :param str area: one of *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str/list resources: one or a list of resources. :param str area_type: one of *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :param int/float markersize: marker size, default to 50. :param int/float fontsize: font size, default to 20. :param str title: user specified figure title, default to None. :param bool show_plot: show the plot or not, default to True. :return: (*tuple*) -- the first entry is matplotlib.axes.Axes object of the plot, the second entry is the capacity weighted average of cost curve slopes over the selected time range. :raises TypeError: if markersize is not an integer or a float and/or if fontsize is not an integer or a float and/or if title is provided but not in a string format. """ _check_scenario_is_in_analyze_state(scenario) if not isinstance(markersize, (int, float)): raise TypeError("markersize should be either an integer or a float") if not isinstance(fontsize, (int, float)): raise TypeError("fontsize should be either an integer or a float") if title is not None and not isinstance(title, str): raise TypeError("title should be a string") plant_id = get_plant_id_for_resources_in_area(scenario, area, resources, area_type=area_type) grid = scenario.state.get_grid() plant_df = grid.plant.loc[plant_id] cost_df = grid.gencost["after"].loc[plant_id] slope = (cost_df["f2"] - cost_df["f1"]) / (cost_df["p2"] - cost_df["p1"]) total_cap = plant_df["Pmax"].sum() if total_cap == 0: data_avg = 0 else: data_avg = (plant_df["Pmax"] * slope).sum() / total_cap _, ax = plt.subplots(figsize=[20, 10]) ax.scatter(plant_df["Pmax"], slope, s=markersize) ax.plot(plant_df["Pmax"], [data_avg] * len(plant_df.index), c="red") ax.grid() if title is None: ax.set_title( f'{area} {" ".join(resources) if isinstance(resources, list) else resources}' ) else: ax.set_title(title) ax.set_xlabel("Capacity (MW)") ax.set_ylabel("Slope") for item in ([ax.title, ax.xaxis.label, ax.yaxis.label] + ax.get_xticklabels() + ax.get_yticklabels()): item.set_fontsize(fontsize) if show_plot: plt.show() return ax, data_avg
def plot_scatter_capacity_vs_curtailment( scenario, area, resources, time_zone="utc", time_range=None, area_type=None, between_time=None, dayofweek=None, markersize=50, fontsize=20, title=None, percentage=False, show_plot=True, ): """Generate for a given scenario the scatter plot of the capacity (x-axis) vs curtailment as a fraction of available resources (y-axis) of generators located in area and fueled by resources over a time range. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance :param str area: one of *loadzone*, *state*, *state abbreviation*, *interconnect*, *'all'* :param str/list resources: one or a list of resources. :param str time_zone: new time zone, default to be *'utc'*. :param tuple time_range: [start_timestamp, end_timestamp] where each time stamp is pandas.Timestamp/numpy.datetime64/datetime.datetime. If None, the entire time range is used for the given scenario. :param str area_type: one of *'loadzone'*, *'state'*, *'state_abbr'*, *'interconnect'* :param list between_time: specify the start hour and end hour of each day inclusively, default to None, which includes every hour of a day. Note that if the end hour is set before the start hour, the complementary hours of a day are picked. :param set dayofweek: specify the interest days of week, which is a subset of integers in [0, 6] with 0 being Monday and 6 being Sunday, default to None, which includes every day of a week. :param int/float markersize: marker size, default to 50. :param int/float fontsize: font size, default to 20. :param str title: user specified figure title, default to None. :param bool percentage: show capacity factor in percentage or not, default to False :param bool show_plot: show the plot or not, default to True. :return: (*tuple*) -- the first entry is matplotlib.axes.Axes object of the plot, the second entry is the capacity weighted average of curtailment over the selected time range. :raises TypeError: if markersize is not an integer or a float and/or if fontsize is not an integer or a float and/or if title is provided but not in a string format. """ if not isinstance(markersize, (int, float)): raise TypeError("markersize should be either an integer or a float") if not isinstance(fontsize, (int, float)): raise TypeError("fontsize should be either an integer or a float") if title is not None and not isinstance(title, str): raise TypeError("title should be a string") resources = _check_resources_are_renewable_and_format( resources, grid_model=scenario.info["grid_model"]) curtailment = calculate_curtailment_time_series(scenario) plant_list = get_plant_id_for_resources_in_area(scenario, area, resources, area_type=area_type) curtailment = curtailment[set(plant_list) & set(curtailment.columns)] curtailment = change_time_zone(curtailment, time_zone) if not time_range: time_range = ( pd.Timestamp(scenario.info["start_date"], tz="utc"), pd.Timestamp(scenario.info["end_date"], tz="utc"), ) curtailment = slice_time_series( curtailment, time_range[0], time_range[1], between_time=between_time, dayofweek=dayofweek, ) profiles = pd.concat( [scenario.state.get_solar(), scenario.state.get_wind()], axis=1) curtailment = curtailment.sum() / profiles[curtailment.columns].sum() if percentage: curtailment = (curtailment * 100).round(2) total_cap = get_capacity_by_resources(scenario, area, resources, area_type=area_type).sum() plant_df = scenario.state.get_grid().plant.loc[plant_list] if total_cap == 0: data_avg = 0 else: data_avg = (plant_df["Pmax"] * curtailment).sum() / total_cap _, ax = plt.subplots(figsize=[20, 10]) ax.scatter(plant_df["Pmax"], curtailment, s=markersize) ax.plot(plant_df["Pmax"], [data_avg] * len(plant_df.index), c="red") ax.grid() if title is None: ax.set_title( f"{area} " f'{" ".join(resources) if isinstance(resources, (list, set)) else resources}' ) else: ax.set_title(title) ax.set_xlabel("Capacity (MW)") if percentage: ax.set_ylabel("Curtailment %") else: ax.set_ylabel("Curtailment") for item in ([ax.title, ax.xaxis.label, ax.yaxis.label] + ax.get_xticklabels() + ax.get_yticklabels()): item.set_fontsize(fontsize) if show_plot: plt.show() return ax, data_avg