7. PostREISE

This tutorial focuses on the analysis and plotting of scenario output data. PostREISE is an open source package written in Python that is available on GitHub.

Note that analysis and plotting tools in this package rely extensively on our scenario framework presented in the PowerSimData tutorial.

7.1. Scenario used

You will find below a short description of the scenarios that will be used across this tutorial:

  • Scenario 403 was designed to represent the Eastern interconnect in the year 2020.

  • Scenario 3287 was designed to represent the entire U.S. in the year 2030, where there is a large increase in transmission capacity (but no increase across interconnection seams) and carbon-free energy represents approximately 70% of total generation over the course of the year.

  • Scenario 2497 was designed to represent the Western interconnect in the year 2030, where 90% of the total energy could come from carbon-free sources, but the transmission network has not been upgraded to accommodate these new clean generation sources. Of this 90% potential, 10% could come from nuclear.

  • Scenario 1171 was designed to represent the Western interconnect in the year 2030, with all states meeting their clean energy goals (with ‘collaboration’ via imports and exports across state lines) and there are 4 GW of energy storage capacity.

  • Scenario 3101 was designed to represent the Western interconnect in the year 2030, where 90% of the total energy does come from carbon-free sources, enabled by upgrades in the transmission network to accommodate these new clean generation sources. Of this 90% potential, 10% comes from nuclear.

7.2. Analysis of Scenario Data

Several categories of analysis focusing on:

  • transmission congestion/utilization

  • generation and generator capacity

  • emission (\(\rm{CO}_2\), \(\rm{NO}_x\) and \(\rm{SO}_2\)) from thermal generators

  • curtailment of renewable generators

can be conducted with the PostREISE package.

You will find below some code snippets that will help you use these analysis tools for each category. Information on the input and returned parameter(s) of the analysis functions can be found on this website. Use the Module Index to locate a module and access the documentation along with the source code of the objects therein.

7.2.1. Transmission

  • calculate the hourly congestion surplus

    from powersimdata import Scenario
    
    from postreise.analyze.transmission.congestion import calculate_congestion_surplus
    
    
    scenario = Scenario(403)
    congestion_surplus = calculate_congestion_surplus(scenario)
    
  • get the hourly utilization level (in [0, 1]) of the branches, i.e., the extent to which the transmission path is used

    from powersimdata import Scenario
    
    from postreise.analyze.transmission.utilization import get_utilization
    
    
    scenario = Scenario(403)
    grid = scenario.get_grid()
    pf = scenario.get_pf()
    
    utilization = get_utilization(grid.branch, pf)
    
  • obtain utilization/congestion statistics for each line

    from powersimdata import Scenario
    
    from postreise.analyze.transmission.utilization import generate_cong_stats
    
    
    scenario = Scenario(403)
    grid = scenario.get_grid()
    pf = scenario.get_pf()
    
    congestion_stats = generate_cong_stats(pf, grid.branch)
    

    The columns of the returned table are:

    • capacity: the capacity of the line

    • branch_device_type: the type of line

    • per_util1, per_util2 and per_util3: fraction of hours the line is used above threshold 1, 2 and 3. Default value for threshold are 0.75, 0.9 and 0.99, respectively

    • bind: number of hours the line is used at full capacity

    • risk: total power flowing on the line for hours used above threshold 1 (0.75 by default)

    • uflag1, uflag2 and uflag3: threshold for per_util1, per_util2 and per_util3. Default values for threshold are 0.5, 0.2 and 0.05, respectively

    • sumflag: total number of flags (in [0, 3])

    • dist: the length of the line

7.2.2. Generator Capacity and Generation

  • identify hours for which generators are at minimum power, maximum power and have binding ramp constraints

    from powersimdata import Scenario
    
    from postreise.analyze.generation.binding import (
      pmax_constraints,
      pmin_constraints,
      ramp_constraints,
    )
    
    
    scenario = Scenario(3287)
    binding_pmin = pmin_constraints(scenario)
    binding_pmax = pmax_constraints(scenario)
    binding_ramp = ramp_constraints(scenario)
    
  • calculate the net load duration curve, i.e., the capacity value of a class of resources by comparing the mean of the top N hour of absolute demand to the mean of the top N hours of net demand

    from powersimdata import Scenario
    
    from postreise.analyze.generation.capacity import calculate_NLDC
    
    
    scenario = Scenario(3287)
    nldc = calculate_NLDC(scenario, {"ng", "coal"})
    
  • calculate the capacity value of a class of resources by averaging the power generated in the top N hours of net load peak

    from powersimdata import Scenario
    
    from postreise.analyze.generation.capacity import calculate_net_load_peak
    
    
    scenario = Scenario(3287)
    nlp = calculate_net_load_peak(scenario, {"nuclear", "hydro"}, hours=50)
    
  • get the total nameplate capacity for generator type(s) in an area

    from powersimdata import Scenario
    
    from postreise.analyze.generation.capacity import get_capacity_by_resources
    
    
    scenario = Scenario(2497)
    resources_capacity = get_capacity_by_resources(scenario, "CA", {"solar", "wind"})
    
  • get total storage nameplate capacity in an area

    from powersimdata import Scenario
    
    from postreise.analyze.generation.capacity import get_storage_capacity
    
    
    scenario = Scenario(1171)
    storage_capacity = get_storage_capacity(scenario, "CA")
    
  • get the total nameplate capacity for each generator type and load zone combination

    from powersimdata import Scenario
    
    from postreise.analyze.generation.capacity import sum_capacity_by_type_zone
    
    
    scenario = Scenario(2497)
    capacity = sum_capacity_by_type_zone(scenario)
    
  • get the hourly capacity factor of each generator fueled by resource(s) in an area

    from powersimdata import Scenario
    
    from postreise.analyze.generation.capacity import get_capacity_factor_time_series
    
    
    scenario = Scenario(3287)
    capacity = get_capacity_factor_time_series(
      scenario, "Texas", {"solar", "wind"}, area_type= "interconnect"
    )
    
  • get the total generation for each generator type and load zone combination

    from powersimdata import Scenario
    
    from postreise.analyze.generation.summarize import sum_generation_by_type_zone
    
    
    scenario = Scenario(3101)
    generation = sum_generation_by_type_zone(scenario)
    
  • get the total generation for each generator type and state combination, adding totals for the interconnects and for all states

    from powersimdata import Scenario
    
    from postreise.analyze.generation.summarize import sum_generation_by_state
    
    scenario = Scenario(3101)
    generation = sum_generation_by_state(scenario)
    
  • get the total historical generation for each generator type and state combination, adding totals for interconnects and for all states

    import inspect
    import os
    import pandas as pd
    
    import postreise
    from postreise.analyze.generation.summarize import summarize_hist_gen
    
    
    data = os.path.join(os.path.dirname(inspect.getfile(postreise)), "data")
    hist_gen = pd.read_csv(
        os.path.join(data, "2016_Historical_USA_TAMU_Generation_GWh.csv"), index_col=0
    ).T
    historical_generation = summarize_hist_gen(hist_gen, hist_gen.columns.to_list())
    
  • get hourly total generation for generator type(s) in an area

    from powersimdata import Scenario
    
    from postreise.analyze.generation.summarize import (
        get_generation_time_series_by_resources,
    )
    
    
    scenario = Scenario(3287)
    generation = get_generation_time_series_by_resources(
      scenario, "Western", {"solar", "wind"}
    )
    
  • get hourly total storage power generation in an area

    from powersimdata import Scenario
    
    from postreise.analyze.generation.summarize import get_storage_time_series
    
    
    scenario = Scenario(1171)
    generation = get_storage_time_series(scenario, "Western")
    

7.2.3. Emission

  • calculate hourly emissions for each generator

    from powersimdata import Scenario
    
    from postreise.analyze.generation.emissions import generate_emissions_stats
    
    
    scenario = Scenario(403)
    emission = generate_emissions_stats(scenario, pollutant="carbon", method="simple")
    

    Different methods can be used to infer the carbon emission:

    • ‘simple’ uses a fixed ratio of \(\rm{CO}_2\) to MWh

    • ‘always-on’ uses generator heat-rate curves including non-zero intercepts

    • ‘decommit’ uses generator heat-rate curves but de-commits generators if off

    Only the ‘simple’ method can be used to infer emission of \(\rm{NO}_x\) or \(\rm{SO}_2\).

    Pollutant options:

    • carbon = carbon dioxide (\(\rm{CO}_2\))

    • nox = nitrogen oxides (\(\rm{NO}_x\))

    • so2 = sulfur dioxide (\(\rm{SO}_2\))

  • calculate total emissions by generator type and bus

    from powersimdata import Scenario
    
    from postreise.analyze.generation.emissions import (
      generate_emissions_stats,
      summarize_emissions_by_bus,
    )
    
    
    scenario = Scenario(403)
    grid = scenario.get_grid()
    emission = generate_emissions_stats(scenario, pollutant="carbon", method="simple")
    emission_by_resources_and_bus = summarize_emissions_by_bus(emission, grid)
    
  • calculate individual generator costs at given power output

    from powersimdata import Scenario
    
    from postreise.analyze.generation.emissions import calculate_costs
    
    
    scenario = Scenario(403)
    costs = calculate_costs(scenario)
    

7.2.4. Curtailment

  • calculate hourly curtailment for each renewable generator

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import (
      calculate_curtailment_time_series,
    )
    
    
    scenario = Scenario(403)
    curtailment = calculate_curtailment_time_series(scenario)
    
  • calculate hourly curtailment by generator type(s)

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import (
      calculate_curtailment_time_series_by_resources,
    )
    
    
    scenario = Scenario(403)
    curtailment = calculate_curtailment_time_series_by_resources(
      scenario, {"wind", "solar"}
    )
    
  • calculate hourly curtailment by area(s)

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import (
      calculate_curtailment_time_series_by_areas,
    )
    
    
    scenario = Scenario(3287)
    curtailment = calculate_curtailment_time_series_by_areas(
      scenario, {"state": ["CA", "WA", "OR", "NV", "UT"]}
    )
    
  • calculate scenario-long average curtailment fraction for a set of generator type(s)

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import (
      calculate_curtailment_percentage_by_resources,
    )
    
    
    scenario = Scenario(3287)
    curtailment = calculate_curtailment_percentage_by_resources(
      scenario, {"wind", "solar"}
    )
    
  • calculate hourly curtailment of each generator located in area(s) and fueled by resource(s)

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import (
      calculate_curtailment_time_series_by_areas_and_resources,
    )
    
    
    scenario = Scenario(3287)
    curtailment = calculate_curtailment_time_series_by_areas_and_resources(
      scenario, areas={"state": ["Maine", "CA", "TX"]})
    
  • calculate hourly curtailment of each generator fueled by resources and located in area(s).

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import (
      calculate_curtailment_time_series_by_resources_and_areas,
    )
    
    
    scenario = Scenario(3287)
    curtailment = calculate_curtailment_time_series_by_resources_and_areas(
      scenario, areas={"state": ["Maine", "CA", "TX"]})
    
  • calculate total curtailment by bus

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import summarize_curtailment_by_bus
    
    
    scenario = Scenario(403)
    curtailment = summarize_curtailment_by_bus(scenario)
    
  • calculate total curtailment by location

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import (
      summarize_curtailment_by_location,
    )
    
    
    scenario = Scenario(2497)
    curtailment = summarize_curtailment_by_location(scenario)
    
  • get hourly curtailment for each available renewable resource(s) in area.

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import get_curtailment_time_series
    
    
    scenario = Scenario(2497)
    curtailment = get_curtailment_time_series(scenario, "WA")
    

7.3. Plotting Scenario Data

The plotting functions use the analysis modules to process data. We have a demo folder that encloses numerous notebooks that we hope will help you analyze/display your scenario data.

7.3.1. Single Scenario

7.3.1.1. Transmission

  • get a power flow snapshot (notebook)

    import pandas as pd
    from bokeh.io import show
    from powersimdata import Scenario
    
    from postreise.plot.plot_powerflow_snapshot import plot_powerflow_snapshot
    
    scenario = Scenario(3287)
    grid = scenario.get_grid()
    
    pf_map = plot_powerflow_snapshot(
        scenario, pd.Timestamp(2016, 11, 2, 22), legend_font_size=20
    )
    show(pf_map)
    
    ../_images/pf_snapshot_map.png
  • get utilization map (notebook)

    from bokeh.io import show
    from powersimdata import Scenario
    
    from postreise.plot.plot_utilization_map import map_utilization
    
    scenario = Scenario("3287")
    util_map = map_utilization(scenario, state_borders_kwargs={"background_map": False})
    show(util_map)
    
    Bokeh Plot

7.3.1.2. Emission

  • show carbon emission by generator type using circles overlay on a map (notebook)

    from bokeh.io import show
    from powersimdata import Scenario
    
    from postreise.plot.plot_carbon_map import map_carbon_emission_generator
    
    scenario = Scenario(3287)
    
    emission_map = map_carbon_emission_generator(
        scenario, coordinate_rounding=0, scale_factor=0.75
    )
    show(emission_map)
    
    Bokeh Plot

7.3.1.3. Generator Capacity and Generation

  • plot stacked generation time series in an area (notebook)

    from powersimdata import Scenario
    
    from postreise.plot.plot_generation_ts_stack import plot_generation_time_series_stack
    
    t2c = {
        "nuclear": "#173FA5",
        "hydro": "#0090FF",
        "geothermal": "#CC67F3",
        "other": "#8B36FF",
        "dfo": "#31E8CB",
        "coal": "#37404C",
        "ng": "#72818F",
        "solar": "#FFBB45",
        "wind": "#78D911",
        "solar_curtailment": "#FFBB45",
        "wind_curtailment": "#78D911",
    }
    
    t2l = {
        "nuclear": "Nuclear",
        "hydro": "Hydro",
        "geothermal": "Geothermal",
        "other": "Other",
        "dfo": "Distillate Fuel Oil",
        "coal": "Coal",
        "ng": "Natural Gas",
        "solar": "Solar",
        "wind": "Wind",
        "wind_offshore": "Wind Offshore",
        "biomass": "Biomass",
        "storage": "Storage",
        "solar_curtailment": "Solar Curtailment",
        "wind_curtailment": "Wind Curtailment",
        "wind_offshore_curtailment": "Offshore Wind Curtailment",
    }
    
    t2hc = {"solar_curtailment": "#996100", "wind_curtailment": "#4e8e0b"}
    
    scenario = Scenario(1171)
    
    resources = [
        "nuclear",
        "coal",
        "hydro",
        "geothermal",
        "other",
        "dfo",
        "ng",
        "solar",
        "wind",
        "storage",
        "solar_curtailment",
        "wind_curtailment",
    ]
    
    plot_generation_time_series_stack(
        scenario,
        "Western",
        resources,
        time_freq="D",
        normalize=True,
        t2c=t2c,
        t2l=t2l,
        t2hc=t2hc,
    )
    
    ../_images/generation_stack_western_ts.png
  • plot capacity vs capacity factor of generators in an area (notebook)

    from powersimdata import Scenario
    from powersimdata.utility.helpers import PrintManager
    
    from postreise.plot.plot_scatter_capacity_vs_capacity_factor import (
        plot_scatter_capacity_vs_capacity_factor,
    )
    
    with PrintManager():
        scenario = Scenario(1171)
        plot_scatter_capacity_vs_capacity_factor(
            scenario, "Western", "solar", percentage=True
        )
    
    ../_images/capacity_vs_cf_solar_western_scatter.png

7.3.1.4. Curtailment

  • plot renewable generators curtailment time series in an area (notebook)

    import matplotlib.pyplot as plt
    from powersimdata import Scenario
    
    from postreise.plot.plot_curtailment_ts import plot_curtailment_time_series
    
    scenario = Scenario(403)
    
    t2c = {"wind_curtailment": "blue", "solar_curtailment": "blue"}
    
    plot_curtailment_time_series(
        scenario,
        "Eastern",
        ["solar", "wind"],
        time_freq="D",
        t2c=t2c,
        label_fontsize=30,
        title_fontsize=35,
        tick_fontsize=25,
        legend_fontsize=25,
    )
    plt.show()
    
    ../_images/curtailment_solar_eastern_ts.png ../_images/curtailment_wind_eastern_ts.png
  • plot capacity vs curtailment of generators in an area (notebook)

    from powersimdata import Scenario
    from powersimdata.utility.helpers import PrintManager
    
    from postreise.plot.plot_scatter_capacity_vs_curtailment import (
        plot_scatter_capacity_vs_curtailment,
    )
    
    with PrintManager():
        scenario = Scenario(3287)
        plot_scatter_capacity_vs_curtailment(scenario, "Western", "solar", percentage=True)
    
    ../_images/capacity_vs_curtailment_solar_western_scatter.png

7.3.1.5. Price

  • map locational marginal price (notebook)

    from bokeh.io import show
    from powersimdata import Scenario
    
    from postreise.plot.plot_lmp_map import map_lmp
    
    scenario = Scenario(3287)
    
    lmp_map = map_lmp(scenario, lmp_min=10, lmp_max=35, num_ticks=6, scale_factor=1.5)
    show(lmp_map)
    
    Bokeh Plot
  • plot capacity vs cost curve slope of generators in an area (notebook)

    from powersimdata import Scenario
    from powersimdata.utility.helpers import PrintManager
    
    from postreise.plot.plot_scatter_capacity_vs_cost_curve_slope import (
        plot_scatter_capacity_vs_cost_curve_slope,
    )
    
    with PrintManager():
        scenario = Scenario(3287)
        plot_scatter_capacity_vs_cost_curve_slope(scenario, "Eastern", "coal")
    
    ../_images/capacity_vs_cost_curve_slope_coal_eastern_scatter.png

7.3.1.6. General

  • plot any time-series values using a heatmap where each column is one color-coded day (notebook)

    from powersimdata import Scenario
    
    from postreise.analyze.generation.curtailment import calculate_curtailment_time_series
    from postreise.plot.plot_heatmap import plot_heatmap
    
    scenario = Scenario(3287)
    curtailment = calculate_curtailment_time_series(scenario).sum(axis=1)
    
    plot_heatmap(
        curtailment,
        cmap="PiYG_r",
        scale=1e-3,
        cbar_label="GW",
        vmin=0,
        vmax=250,
        cbar_tick_values=[0, 50, 100, 150, 200, 250],
        cbar_tick_labels=["0", "50", "100", "150", "200", "≥250"],
        time_zone="ETC/GMT+6",
        time_zone_label="(CST)",
        contour_levels=[250],
    )
    
    ../_images/curtailment_usa_heatmap.png
  • map transmission lines color coded by interconnection

    from bokeh.io import show
    from powersimdata import Scenario
    
    from postreise.plot.plot_interconnection_map import map_interconnections
    
    scenario = Scenario(3287)
    grid = scenario.get_grid()
    
    transmission_map = map_interconnections(grid)
    show(transmission_map)
    
    Bokeh Plot

7.3.2. Scenarios Comparison

7.3.2.1. Generator Capacity and Generation

  • compare generation and capacity in various scenarios through bar charts (notebook)

    from powersimdata.utility.helpers import PrintManager
    
    from postreise.plot.plot_bar_generation_vs_capacity import (
        plot_bar_generation_vs_capacity,
    )
    
    with PrintManager():
        plot_bar_generation_vs_capacity(
            areas=["CA", "Western"],
            scenario_ids=[2497, 3101],
            scenario_names=[
                "Western 90% clean and 10% nuclear",
                "Western 90% clean and 10% nuclear with transmission upgrade",
            ],
        )
    
    ../_images/capacity_vs_generation_ca_bar.png ../_images/capacity_vs_generation_western_bar.png
  • compare generation and capacity in various scenarios through pie charts (notebook)

    from powersimdata.utility.helpers import PrintManager
    
    from postreise.plot.plot_pie_generation_vs_capacity import (
        plot_pie_generation_vs_capacity,
    )
    
    with PrintManager():
        plot_pie_generation_vs_capacity(
            areas=["WA", "Western"],
            scenario_ids=[2497, 3101],
            scenario_names=[
                "Western 90% clean and 10% nuclear",
                "Western 90% clean and 10% nuclear \n with transmission upgrade",
            ],
        )
    
    ../_images/capacity_vs_generation_wa_pie.png ../_images/capacity_vs_generation_western_pie.png
  • compare generation shortfall in various scenarios through bar charts (notebook)

    import inspect
    import os
    
    from powersimdata.design.generation.clean_capacity_scaling import load_targets_from_csv
    from powersimdata.utility.helpers import PrintManager
    
    import postreise
    from postreise.plot.plot_bar_shortfall import plot_bar_shortfall
    
    data = os.path.join(os.path.dirname(inspect.getfile(postreise)), "data")
    target = load_targets_from_csv(
        os.path.join(data, "2030_USA_Clean_Energy_Regular_Targets.csv")
    )
    with PrintManager():
        plot_bar_shortfall(
            "Nevada",
            [2497, 3101],
            target,
            scenario_names=[
                "Western 90% clean and 10% nuclear",
                "Western 90% clean and 10% nuclear" + "\n" + "with transmission upgrade",
            ],
            baseline_scenario=2497,
            baseline_scenario_name="Western 90% clean and 10% nuclear",
        )
    
    ../_images/shortfall_nv.png

7.3.2.2. Emission

  • compare total carbon emissions by generator type for 1-to-n scenarios through bar charts (notebook)

    from powersimdata import Scenario
    from powersimdata.utility.helpers import PrintManager
    
    from postreise.plot.plot_carbon_bar import plot_carbon_bar
    
    scenarioA = Scenario(2497)
    scenarioB = Scenario(3101)
    
    with PrintManager():
        scenarioA = Scenario(2497)
        scenarioB = Scenario(3101)
    
    plot_carbon_bar(
        scenarioA,
        scenarioB,
        labels=[
            "Western" + "\n" + "90% clean and 10% nuclear",
            "Western"
            + "\n"
            + "90% clean and 10% nuclear"
            + "\n"
            + "with transmission upgrade",
        ],
    )
    
    ../_images/emission_bar.png
  • compare carbon emission by generator type for two scenarios on a map (notebook)

    from bokeh.io import show
    from powersimdata import Scenario
    
    from postreise.plot.plot_carbon_map import map_carbon_emission_difference
    
    scenarioA = Scenario(2497)
    scenarioB = Scenario(3101)
    
    emission_difference_map = map_carbon_emission_difference(
        scenarioA, scenarioB, coordinate_rounding=1
    )
    show(emission_difference_map)
    
    Bokeh Plot
  • plot stacked generation and carbon emission for 1-to-n scenarios side-by-side (notebook)

    from powersimdata import Scenario
    
    from postreise.plot.plot_energy_carbon_stack import plot_n_scenarios
    
    scenarioA = Scenario(2497)
    scenarioB = Scenario(3101)
    
    plot_n_scenarios(scenarioA, scenarioB)
    
    ../_images/energy_emission_stack_bar.png