示例#1
0
def carbon_diff(scenario_1, scenario_2):
    """Prints percentage change in carbon emissions of two scenarios.

    :param powersimdata.scenario.scenario.Scenario scenario_1: scenario instance.
    :param powersimdata.scenario.scenario.Scenario scenario_2: scenario instance.
    :return: (*float*) -- relative difference in emission in percent.
    """
    carbon_by_bus_1 = summarize_emissions_by_bus(
        generate_emissions_stats(scenario_1), scenario_1.state.get_grid())
    carbon_by_bus_2 = summarize_emissions_by_bus(
        generate_emissions_stats(scenario_2), scenario_2.state.get_grid())

    sum_1 = sum(carbon_by_bus_1["coal"].values()) + sum(
        carbon_by_bus_1["ng"].values())
    sum_2 = sum(carbon_by_bus_2["coal"].values()) + sum(
        carbon_by_bus_2["ng"].values())

    return 100 * (1 - sum_2 / sum_1)
示例#2
0
def plot_carbon_bar(*args, labels=None, labels_size=15, show_plot=True):
    """Make bar chart of carbon emissions by fuel type for n scenarios.

    :param powersimdata.scenario.scenario.Scenario args: scenario instances.
    :param list/tuple/set labels: labels on plot. Default to scenario id.
    :param int labels_size: size of labels.
    :param bool show_plot: whether to save the plot.
    :raises ValueError:
        if ``args`` are not scenario instances.
        if ``labels`` has a different length than the number of passed scenarios.
    :raises TypeError:
        if ``labels`` is not an iterable.
        if ``labels_size`` is not an int.
    :return: (*tuple*) -- the figure elements that can be further modified.
    """
    if not all([isinstance(a, Scenario) for a in args]):
        raise ValueError("all inputs must be Scenario objects")
    if labels is not None and not isinstance(labels, (list, tuple, set)):
        raise TypeError("labels must be a list/tuple/set")
    if labels is not None and len(args) != len(labels):
        raise ValueError("labels must have same length as number of scenarios")
    if not isinstance(labels_size, int):
        raise TypeError("labels_size must be an integer")

    labels = tuple([s.info["id"]
                    for s in args]) if labels is None else tuple(labels)

    carbon_val = {"coal": [], "ng": []}
    for i, s in enumerate(args):
        grid = s.state.get_grid()
        carbon_by_bus = summarize_emissions_by_bus(generate_emissions_stats(s),
                                                   grid)
        carbon_val["coal"].append(sum(carbon_by_bus["coal"].values()))
        carbon_val["ng"].append(sum(carbon_by_bus["ng"].values()))

    fig, (ax1, ax2) = plt.subplots(1,
                                   2,
                                   sharey=True,
                                   figsize=(14, len(labels)))
    y_pos = np.arange(len(labels))

    for a, f, c, t in zip(
        [ax1, ax2],
        ["coal", "ng"],
        ["black", "purple"],
        ["Coal: CO$_2$  Emissions", "Natural Gas: CO$_2$ Emissions"],
    ):
        a.barh(y_pos, carbon_val[f], align="center", alpha=0.25, color=c)
        a.set_xlabel("Tons", fontsize=labels_size + 3)
        a.set_title(t, y=1.04, fontsize=labels_size + 5)
        a.tick_params(axis="both", labelsize=labels_size)

    plt.yticks(y_pos, labels)
    plt.subplots_adjust(wspace=0.1)
    if show_plot:
        plt.show()
    return ax1, ax2
示例#3
0
 def test_calculate_so2_simple(self, scenario):
     expected_values = np.array(
         [
             [0, 0, 0, 0, 0],
             [0, 0, 3.0000e-05, 3.8600e-03, 1.0945e-02],
             [0, 0, 6.0000e-05, 7.7200e-03, 2.1890e-02],
             [0, 0, 9.0000e-05, 1.1580e-02, 3.2835e-02],
         ]
     )
     nox = generate_emissions_stats(scenario, pollutant="so2", method="simple")
     assert_array_almost_equal(
         expected_values, nox.to_numpy(), err_msg="Values do not match expected"
     )
示例#4
0
 def test_calculate_nox_simple(self, scenario):
     expected_values = np.array(
         [
             [0, 0, 0, 0, 0],
             [0, 0, 0.000537, 0.002632, 0.007685],
             [0, 0, 0.001074, 0.005264, 0.015370],
             [0, 0, 0.001611, 0.007896, 0.023055],
         ]
     )
     nox = generate_emissions_stats(scenario, pollutant="nox", method="simple")
     assert_array_almost_equal(
         expected_values, nox.to_numpy(), err_msg="Values do not match expected"
     )
示例#5
0
def combine_bus_info_and_emission(scenario):
    """Build data frame needed for plotting carbon emitted by thermal generators.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario instance.
    :return: (*pandas.DataFrame*) -- bus data frame with emission.
    """
    grid = scenario.state.get_grid()
    carbon_by_bus = summarize_emissions_by_bus(
        generate_emissions_stats(scenario), grid)

    selected = grid.bus.loc[pd.DataFrame.from_dict(carbon_by_bus).index]
    bus_w_emission = selected.merge(pd.DataFrame.from_dict(carbon_by_bus),
                                    right_index=True,
                                    left_index=True)

    return bus_w_emission
示例#6
0
    def test_carbon_calc_always_on(self, scenario, mock_plant):

        carbon = generate_emissions_stats(scenario, method="always-on")
        _test_emissions_structure(carbon, mock_plant, scenario.state.get_pg())

        # check specific values
        expected_values = np.array(
            [
                [0, 0, 4.82, 8.683333, 6.77],
                [0, 0, 6.6998, 13.546000, 11.8475],
                [0, 0, 9.4472, 21.1873333, 20.3100],
                [0, 0, 13.0622, 31.6073333, 32.1575],
            ]
        )
        assert_array_almost_equal(
            expected_values, carbon.to_numpy(), err_msg="Values do not match expected"
        )
示例#7
0
    def test_carbon_calc_simple(self, scenario, mock_plant):

        carbon = generate_emissions_stats(scenario, method="simple")
        _test_emissions_structure(carbon, mock_plant, scenario.state.get_pg())

        # check specific values
        expected_values = np.array(
            [
                [0, 0, 0, 0, 0],
                [0, 0, 1.407, 4.004, 4.2],
                [0, 0, 2.814, 8.008, 8.4],
                [0, 0, 4.221, 12.012, 12.6],
            ]
        )
        assert_array_almost_equal(
            expected_values, carbon.to_numpy(), err_msg="Values do not match expected"
        )
示例#8
0
 def test_calculate_so2_disallowed_method(self, scenario):
     with pytest.raises(ValueError):
         generate_emissions_stats(scenario, pollutant="so2", method="always-on")
示例#9
0
 def test_calculate_nox_disallowed_method(self, scenario):
     with pytest.raises(ValueError):
         generate_emissions_stats(scenario, pollutant="nox", method="decommit")
def plot_n_scenarios(*args):
    """For 1-to-n scenarios, plot stacked energy and carbon side-by-side.

    :param powersimdata.scenario.scenario.Scenario args: scenario instances.
    :raises ValueError: if arguments are not scenario instances.
    """
    if not all([isinstance(a, Scenario) for a in args]):
        raise ValueError("all inputs must be Scenario objects")
    # Build dictionaries of calculated data for each scenario
    scenario_numbers = [a.info["id"] for a in args]
    first_id = scenario_numbers[0]
    scenarios = {id: scen for (id, scen) in zip(scenario_numbers, args)}
    grid = {
        id: scenario.state.get_grid()
        for id, scenario in scenarios.items()
    }
    plant = {k: v.plant for k, v in grid.items()}
    # First scenario is chosen to set fuel colors
    type2color = ModelImmutables(
        args[0].info["grid_model"]).plants["type2color"]
    carbon_by_type, energy_by_type = {}, {}
    for id, scenario in scenarios.items():
        # Calculate raw numbers
        annual_plant_energy = scenario.state.get_pg().sum()
        raw_energy_by_type = annual_plant_energy.groupby(plant[id].type).sum()
        annual_plant_carbon = generate_emissions_stats(scenario).sum()
        raw_carbon_by_type = annual_plant_carbon.groupby(plant[id].type).sum()
        # Drop fuels with zero energy (e.g. all offshore_wind scaled to 0 MW)
        energy_by_type[id] = raw_energy_by_type[raw_energy_by_type != 0]
        carbon_by_type[id] = raw_carbon_by_type[raw_energy_by_type != 0]
    # Carbon multiplier is inverse of carbon intensity, to scale bar heights
    carbon_multiplier = energy_by_type[first_id].sum(
    ) / carbon_by_type[first_id].sum()

    # Determine the fuel types with generation in either scenario
    fuels = list(set.union(*[set(v.index) for v in energy_by_type.values()]))
    # Fill zeros in scenarios without a fuel when it's present in another
    for id in scenario_numbers:
        energy_by_type[id] = energy_by_type[id].reindex(fuels, fill_value=0)
        carbon_by_type[id] = carbon_by_type[id].reindex(fuels, fill_value=0)
    # Re-order according to plotting preferences
    fuels = [f for f in all_possible if f in fuels]
    # Re-assess subsets
    renewable_fuels = [f for f in possible_renewable if f in fuels]
    clean_fuels = [f for f in possible_clean if f in fuels]
    carbon_fuels = [f for f in possible_carbon if f in fuels]
    dropped_fuels = (set(possible_clean) | set(possible_carbon)) - (
        set(clean_fuels) | set(carbon_fuels))
    print("no energy in any grid(s), dropping: %s" % ", ".join(dropped_fuels))

    fuel_series = {
        f: sum(
            [[energy_by_type[s][f], carbon_by_type[s][f] * carbon_multiplier]
             for s in scenario_numbers],
            [],
        )
        for f in fuels
    }
    num_bars = 2 * len(scenario_numbers)
    ind = np.arange(num_bars)
    width = 0.5
    line_alpha = 0.25
    line_offsets = np.array((0.25, 0.75))

    # Strart plotting
    fig = plt.figure(figsize=(10, 8))
    ax = fig.add_subplot(111)
    # Plot bars
    patches = {}
    bottom = np.zeros(num_bars)
    for i, f in enumerate(fuels):
        patches[i] = plt.bar(ind,
                             fuel_series[f],
                             width,
                             bottom=bottom,
                             color=type2color[f])
        bottom += fuel_series[f]
    # Plot lines
    for i, fuel in enumerate(carbon_fuels):
        for j, s in enumerate(scenario_numbers):
            cumulative_energy = sum(
                [energy_by_type[s][carbon_fuels[k]] for k in range(i + 1)])
            cumulative_scaled_carbon = carbon_multiplier * sum(
                [carbon_by_type[s][carbon_fuels[k]] for k in range(i + 1)])
            ys = (cumulative_energy, cumulative_scaled_carbon)
            xs = line_offsets + 2 * j
            plt.plot(xs, ys, "k-", alpha=line_alpha)

    # Labeling
    energy_fmt = "{0:,.0f} TWh\n{1:.0f}% renewable\n{2:.0f}% carbon-free\n"
    carbon_fmt = "{0:,.0f} million\ntons CO2\n"
    energy_total = {s: energy_by_type[s].sum() for s in scenarios}
    for j, s in enumerate(scenario_numbers):
        carbon_total = carbon_by_type[s].sum()
        renewable_energy = sum([energy_by_type[s][f] for f in renewable_fuels])
        clean_energy = sum([energy_by_type[s][f] for f in clean_fuels])
        renewable_share = renewable_energy / energy_total[s] * 100
        clean_share = clean_energy / energy_total[s] * 100
        annotation_str = energy_fmt.format(energy_total[s] / 1e6,
                                           renewable_share, clean_share)
        annotation_x = 2 * (j - 0.08)
        plt.annotate(xy=(annotation_x, energy_total[s]), text=annotation_str)
        annotation_str = carbon_fmt.format(carbon_total / 1e6)
        annotation_x = 2 * (j - 0.08) + 1
        annotation_y = carbon_total * carbon_multiplier
        plt.annotate(xy=(annotation_x, annotation_y), text=annotation_str)

    # Plot formatting
    plt.ylim((0, max(energy_total.values()) * 1.2))
    labels = sum([["%s Energy" % id, "%s Carbon" % id]
                  for id in scenario_numbers], [])
    plt.xticks(ind, labels)
    plt.yticks([], [])
    ax.tick_params(axis="x", labelsize=12)
    # Invert default legend order to match stack ordering
    plt.legend(
        handles=(patches[i] for i in range(len(fuels) - 1, -1, -1)),
        labels=fuels[::-1],
        fontsize=12,
        bbox_to_anchor=(1.02, 1),
        loc="upper left",
    )
    plt.tight_layout()
    plt.show()