예제 #1
0
def plot_states(
    figsize=(1400, 800),
    state_borders_kwargs=None,
    state_colors_args=None,
    state_tooltips_args=None,
    state_legends_kwargs=None,
):
    """Add states on canvas and optionally add colors, tooltips and legends.

    :param tuple figsize: size of the bokeh figure (in pixels).
    :param dict state_borders_kwargs: keyword argument(s) to be passed to
        :func:`postreise.add_state_borders`.
    :param dict state_colors_args: arguments to be passed to
        :func:`postreise.add_state_colors`.
    :param dict state_tooltips_args: argument(s) to be passed to
        :func:`postreise.add_state_tooltips`.
    :param dict state_legends_kwargs: keyword argument(s) to be passed to
        :func:`postreise.add_state_legends`.
    :raises TypeError:
        if ``state_borders_kwargs`` is not None or dict.
        if ``state_colors_args`` is not None or dict.
        if ``state_tooltips_args`` is not None or dict.
        if ``state_legends_kwargs`` is not None or dict.
    :return: (*bokeh.plotting.figure.Figure*) -- map of us states.
    """
    for label in [
            "state_borders_kwargs",
            "state_colors_args",
            "state_tooltips_args",
            "state_legends_kwargs",
    ]:
        var = eval(label)
        if var is not None and not isinstance(var, dict):
            raise TypeError(f"{label} must be a dict")

    # create canvas
    canvas = create_map_canvas(figsize)

    # add state borders
    if state_borders_kwargs is not None:
        _check_func_kwargs(add_state_borders, set(state_borders_kwargs),
                           "state_borders_kwargs")
        canvas = add_state_borders(canvas, **state_borders_kwargs)
    else:
        canvas = add_state_borders(canvas)

    if state_colors_args is not None:
        canvas = add_state_colors(canvas, state_colors_args)

    if state_tooltips_args is not None:
        canvas = add_state_tooltips(canvas, state_tooltips_args["title"],
                                    state_tooltips_args["label"])

    if state_legends_kwargs is not None:
        _check_func_kwargs(add_state_legends, set(state_legends_kwargs),
                           "state_legends_kwargs")
        canvas = add_state_legends(canvas, **state_legends_kwargs)

    return canvas
예제 #2
0
def test_check_func_kwargs_errors():
    def func(a=1, b=2, c=3):
        return a + b + c

    arg = (
        ("a", "dummy"),
        (["a"], ["dummy"]),
        ([1, "b", "c"], "dummy"),
        ({"d", "e"}, "dummy"),
    )
    for a in arg:
        with pytest.raises(TypeError):
            assert _check_func_kwargs(func, a[0], a[1])
def plot_powerflow_snapshot(
    scenario,
    hour,
    b2b_dclines=None,
    demand_centers=None,
    ac_branch_color="#8B36FF",
    dc_branch_color="#01D4ED",
    solar_color="#FFBB45",
    wind_color="#78D911",
    demand_color="gray",
    figsize=(1400, 800),
    circle_scale_factor=0.25,
    bg_width_scale_factor=0.001,
    pf_width_scale_factor=0.00125,
    arrow_pf_threshold=3000,
    arrow_dist_threshold=20,
    num_ac_arrows=1,
    num_dc_arrows=1,
    min_arrow_size=5,
    branch_alpha=0.5,
    state_borders_kwargs=None,
    x_range=None,
    y_range=None,
    legend_font_size=None,
):
    """Plot a snapshot of powerflow.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario to plot.
    :param pandas.Timestamp/numpy.datetime64/datetime.datetime hour: snapshot interval.
    :param dict b2b_dclines: which DC lines are actually B2B facilities. Keys are:
        {"from", "to"}, values are iterables of DC line indices to plot (indices in
        "from" get plotted at the "from" end, and vice versa).
    :param pandas.DataFrame demand_centers: lat/lon centers at which to plot the demand
        from each load zone.
    :param str ac_branch_color: color to plot AC branches.
    :param str dc_branch_color: color to plot DC branches.
    :param str solar_color: color to plot solar generation.
    :param str wind_color: color to plot wind generation.
    :param str demand_color: color to plot demand.
    :param tuple figsize: size of the bokeh figure (in pixels).
    :param int/float circle_scale_factor: scale factor for demand/solar/wind circles.
    :param int/float bg_width_scale_factor: scale factor for grid capacities.
    :param int/float pf_width_scale_factor: scale factor for power flows.
    :param int/float arrow_pf_threshold: minimum power flow (MW) for adding arrows.
    :param int/float arrow_dist_threshold: minimum distance (miles) for adding arrows.
    :param int num_ac_arrows: number of arrows for each AC branch.
    :param int num_dc_arrows: number of arrows for each DC branch.
    :param int/float min_arrow_size: minimum arrow size.
    :param int/float branch_alpha: opaqueness of branches.
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :param tuple(float, float) x_range: x range to zoom plot to (EPSG:3857).
    :param tuple(float, float) y_range: y range to zoom plot to (EPSG:3857).
    :param int/str legend_font_size: size to display legend specified as e.g. 12/'12pt'.
    :return: (*bokeh.plotting.figure*) -- power flow snapshot map.
    """
    _check_scenario_is_in_analyze_state(scenario)
    _check_date_range_in_scenario(scenario, hour, hour)
    # Get scenario data
    grid = scenario.state.get_grid()
    bus = grid.bus
    plant = grid.plant
    # Augment the branch dataframe with extra info needed for plotting
    branch = grid.branch
    branch["pf"] = scenario.state.get_pf().loc[hour]
    branch = branch.query("pf != 0").copy()
    branch["dist"] = branch.apply(lambda x: haversine((x.from_lat, x.from_lon),
                                                      (x.to_lat, x.to_lon)),
                                  axis=1)
    branch["arrow_size"] = branch["pf"].abs(
    ) * pf_width_scale_factor + min_arrow_size
    branch = project_branch(branch)
    # Augment the dcline dataframe with extra info needed for plotting
    dcline = grid.dcline
    dcline["pf"] = scenario.state.get_dcline_pf().loc[hour]
    dcline["from_lat"] = dcline.apply(lambda x: bus.loc[x.from_bus_id, "lat"],
                                      axis=1)
    dcline["from_lon"] = dcline.apply(lambda x: bus.loc[x.from_bus_id, "lon"],
                                      axis=1)
    dcline["to_lat"] = dcline.apply(lambda x: bus.loc[x.to_bus_id, "lat"],
                                    axis=1)
    dcline["to_lon"] = dcline.apply(lambda x: bus.loc[x.to_bus_id, "lon"],
                                    axis=1)
    dcline["dist"] = dcline.apply(lambda x: haversine((x.from_lat, x.from_lon),
                                                      (x.to_lat, x.to_lon)),
                                  axis=1)
    dcline["arrow_size"] = dcline["pf"].abs(
    ) * pf_width_scale_factor + min_arrow_size
    dcline = project_branch(dcline)
    # Create a dataframe for demand plotting, if necessary
    if demand_centers is not None:
        demand = scenario.state.get_demand()
        demand_centers["demand"] = demand.loc[hour]
        demand_centers = project_bus(demand_centers)

    # create canvas
    canvas = create_map_canvas(figsize=figsize,
                               x_range=x_range,
                               y_range=y_range)

    # Add state borders
    default_state_borders_kwargs = {"fill_alpha": 0.0, "background_map": False}
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    if b2b_dclines is not None:
        # Append the pseudo AC lines to the branch dataframe, remove from dcline
        all_b2b_dclines = list(b2b_dclines["to"]) + list(b2b_dclines["from"])
        pseudo_ac_lines = dcline.loc[all_b2b_dclines]
        pseudo_ac_lines["rateA"] = pseudo_ac_lines[["Pmin",
                                                    "Pmax"]].abs().max(axis=1)
        branch = branch.append(pseudo_ac_lines)
        # Construct b2b dataframe so that all get plotted at their 'from' x/y
        b2b_from = dcline.loc[b2b_dclines["from"]]
        b2b_to = dcline.loc[b2b_dclines["to"]].rename(
            {
                "from_x": "to_x",
                "from_y": "to_y",
                "to_x": "from_x",
                "to_y": "from_y"
            },
            axis=1,
        )
        b2b = pd.concat([b2b_from, b2b_to])
        dcline = dcline.loc[~dcline.index.isin(all_b2b_dclines)]

    # Plot grid background in grey
    canvas.multi_line(
        branch[["from_x", "to_x"]].to_numpy().tolist(),
        branch[["from_y", "to_y"]].to_numpy().tolist(),
        color="gray",
        alpha=branch_alpha,
        line_width=(branch["rateA"].abs() * bg_width_scale_factor),
    )
    canvas.multi_line(
        dcline[["from_x", "to_x"]].to_numpy().tolist(),
        dcline[["from_y", "to_y"]].to_numpy().tolist(),
        color="gray",
        alpha=branch_alpha,
        line_width=(dcline[["Pmin", "Pmax"]].abs().max(axis=1) *
                    bg_width_scale_factor),
    )
    if b2b_dclines is not None:
        canvas.scatter(
            x=b2b.from_x,
            y=b2b.from_y,
            color="gray",
            alpha=0.5,
            marker="triangle",
            size=(b2b[["Pmin", "Pmax"]].abs().max(axis=1) *
                  bg_width_scale_factor),
        )

    fake_location = branch.iloc[0].drop("x").rename({
        "from_x": "x",
        "from_y": "y"
    })
    # Legend entries
    canvas.multi_line(
        (fake_location.x, fake_location.x),
        (fake_location.y, fake_location.y),
        color=dc_branch_color,
        alpha=branch_alpha,
        line_width=10,
        legend_label="HVDC powerflow",
        visible=False,
    )
    canvas.multi_line(
        (fake_location.x, fake_location.x),
        (fake_location.y, fake_location.y),
        color=ac_branch_color,
        alpha=branch_alpha,
        line_width=10,
        legend_label="AC powerflow",
        visible=False,
    )
    canvas.circle(
        fake_location.x,
        fake_location.y,
        color=solar_color,
        alpha=0.6,
        size=5,
        legend_label="Solar Gen.",
        visible=False,
    )
    canvas.circle(
        fake_location.x,
        fake_location.y,
        color=wind_color,
        alpha=0.6,
        size=5,
        legend_label="Wind Gen.",
        visible=False,
    )

    # Plot demand
    if demand_centers is not None:
        canvas.circle(
            fake_location.x,
            fake_location.y,
            color=demand_color,
            alpha=0.3,
            size=5,
            legend_label="Demand",
            visible=False,
        )
        canvas.circle(
            demand_centers.x,
            demand_centers.y,
            color=demand_color,
            alpha=0.3,
            size=(demand_centers.demand * circle_scale_factor)**0.5,
        )

    # Aggregate solar and wind for plotting
    plant_with_pg = plant.copy()
    plant_with_pg["pg"] = scenario.state.get_pg().loc[hour]
    grouped_solar = aggregate_plant_generation(
        plant_with_pg.query("type == 'solar'"))
    grouped_wind = aggregate_plant_generation(
        plant_with_pg.query("type == 'wind'"))
    # Plot solar, wind
    canvas.circle(
        grouped_solar.x,
        grouped_solar.y,
        color=solar_color,
        alpha=0.6,
        size=(grouped_solar.pg * circle_scale_factor)**0.5,
    )
    canvas.circle(
        grouped_wind.x,
        grouped_wind.y,
        color=wind_color,
        alpha=0.6,
        size=(grouped_wind.pg * circle_scale_factor)**0.5,
    )

    # Plot powerflow on AC branches
    canvas.multi_line(
        branch[["from_x", "to_x"]].to_numpy().tolist(),
        branch[["from_y", "to_y"]].to_numpy().tolist(),
        color=ac_branch_color,
        alpha=branch_alpha,
        line_width=(branch["pf"].abs() * pf_width_scale_factor),
    )
    add_arrows(
        canvas,
        branch,
        color=ac_branch_color,
        pf_threshold=arrow_pf_threshold,
        dist_threshold=arrow_dist_threshold,
        n=num_ac_arrows,
    )

    # Plot powerflow on DC lines
    canvas.multi_line(
        dcline[["from_x", "to_x"]].to_numpy().tolist(),
        dcline[["from_y", "to_y"]].to_numpy().tolist(),
        color=dc_branch_color,
        alpha=branch_alpha,
        line_width=(dcline["pf"].abs() * pf_width_scale_factor),
    )
    add_arrows(
        canvas,
        dcline,
        color=dc_branch_color,
        pf_threshold=0,
        dist_threshold=0,
        n=num_dc_arrows,
    )
    # B2Bs
    if b2b_dclines is not None:
        canvas.scatter(
            x=b2b.from_x,
            y=b2b.from_y,
            color=dc_branch_color,
            alpha=0.5,
            marker="triangle",
            size=(b2b["pf"].abs() * pf_width_scale_factor * 5),
        )

    canvas.legend.location = "bottom_left"
    if legend_font_size is not None:
        if isinstance(legend_font_size, (int, float)):
            legend_font_size = f"{legend_font_size}pt"
        canvas.legend.label_text_font_size = legend_font_size

    return canvas
예제 #4
0
def map_carbon_emission_difference(
    scenario_1,
    scenario_2,
    coordinate_rounding=1,
    figsize=(1400, 800),
    color_increase=be_red,
    color_decrease=be_green,
    scale_factor=1,
    state_borders_kwargs=None,
):
    """Make map of difference in emissions between two scenarios at bus level. Color of
    markers indicates increase/decrease in emissions (``scenario_2`` w.r,t.
    ``scenario_1``) and size reflects relative difference in emissions.

    :param powersimdata.scenario.scenario.Scenario scenario_1: scenario instance.
    :param powersimdata.scenario.scenario.Scenario scenario_2: scenario instance.
    :param int coordinate_rounding: number of digits to round lat/lon for aggregation.
    :param tuple figsize: size of the bokeh figure (in pixels).
    :param str color_increase: color assigned to increase in emissions.
    :param str color_decrease: color associated to decrease in emissions.
    :param float scale_factor: scaling factor for size of emissions circles glyphs.
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :return: (*bokeh.plotting.figure*) -- carbon emission map.
    :raises TypeError:
        if ``coordinate_rounding`` is not ``int``.
        if ``color_increase`` and ``color_decrease`` are not ``str``.
        if ``scale_factor`` is not ``int`` or ``float``.
    :raises ValueError:
        if ``coordinate_rounding`` is negative.
        if ``scale_factor`` is negative.
    """
    _check_scenario_is_in_analyze_state(scenario_1)
    _check_scenario_is_in_analyze_state(scenario_2)
    if not isinstance(coordinate_rounding, int):
        raise TypeError("coordinate_rounding must be an int")
    if coordinate_rounding < 0:
        raise ValueError("coordinate_rounding must be positive")
    if not isinstance(color_increase, str):
        raise TypeError("color_increase must be a str")
    if not isinstance(color_decrease, str):
        raise TypeError("color_decrease must be a str")
    if not isinstance(scale_factor, (int, float)):
        raise TypeError("scale_factor must be a int/float")
    if scale_factor < 0:
        raise ValueError("scale_factor must be positive")

    # create canvas
    canvas = create_map_canvas(figsize=figsize)

    # add state borders
    default_state_borders_kwargs = {"fill_alpha": 0.0, "background_map": False}
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # add emission
    bus_emission_1 = combine_bus_info_and_emission(scenario_1)
    bus_emission_2 = combine_bus_info_and_emission(scenario_2)
    emission = bus_emission_1.merge(
        bus_emission_2,
        right_index=True,
        left_index=True,
        suffixes=("_1", "_2"),
        how="outer",
    )
    emission.fillna(0, inplace=True)
    emission["amount"] = (emission["ng_2"] + emission["coal_2"] -
                          emission["ng_1"] - emission["coal_1"])
    emission["lon"] = emission["lon_1"].fillna(emission["lon_2"])
    emission["lat"] = emission["lat_1"].fillna(emission["lat_2"])
    emission = prepare_bus_data_difference(emission, coordinate_rounding,
                                           color_increase, color_decrease)
    canvas = add_emission(canvas, emission, scale_factor)

    return canvas
예제 #5
0
def map_carbon_emission_generator(
    scenario,
    coordinate_rounding=1,
    figsize=(1400, 800),
    color_coal="black",
    color_ng=be_purple,
    scale_factor=1,
    state_borders_kwargs=None,
):
    """Make map of carbon emissions at bus level. Color of markers indicates generator
    type and size reflects emissions level.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario instance.
    :param int coordinate_rounding: number of digits to round lat/lon for aggregation.
    :param tuple figsize: size of the bokeh figure (in pixels).
    :param str color_ng: color assigned for ng, default to BE purple.
    :param str color_coal: color associated with coal, default to black/gray.
    :param int/float scale_factor: scaling factor for size of emissions circles glyphs.
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :return: (*bokeh.plotting.figure*) -- carbon emission map.
    :raises TypeError:
        if ``coordinate_rounding`` is not ``int``.
        if ``color_coal`` and ``color_ng`` are not ``str``.
        if ``scale_factor`` is not ``int`` or ``float``.
    :raises ValueError:
        if ``coordinate_rounding`` is negative.
        if ``scale_factor`` is negative.
    """
    _check_scenario_is_in_analyze_state(scenario)
    if not isinstance(coordinate_rounding, int):
        raise TypeError("coordinate_rounding must be an int")
    if coordinate_rounding < 0:
        raise ValueError("coordinate_rounding must be positive")
    if not isinstance(color_coal, str):
        raise TypeError("color_coal must be a str")
    if not isinstance(color_ng, str):
        raise TypeError("color_ng must be a str")
    if not isinstance(scale_factor, (int, float)):
        raise TypeError("scale_factor must be a int/float")
    if scale_factor < 0:
        raise ValueError("scale_factor must be positive")

    # create canvas
    canvas = create_map_canvas(figsize=figsize)

    # add state borders
    default_state_borders_kwargs = {"fill_alpha": 0.0, "background_map": False}
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # add emission
    bus_emission = combine_bus_info_and_emission(scenario)
    emission = prepare_bus_data_generator(bus_emission, coordinate_rounding,
                                          color_ng, color_coal)
    canvas = add_emission(canvas, emission, scale_factor)
    return canvas
예제 #6
0
def map_transmission_upgrades(
    scenario1,
    scenario2,
    b2b_indices=None,
    figsize=(1400, 800),
    x_range=None,
    y_range=None,
    state_borders_kwargs=None,
    legend_font_size=20,
    legend_location="bottom_left",
    **plot_kwargs,
):
    """Plot capacity differences for branches & HVDC lines between two scenarios.

    :param powersimdata.scenario.scenario.Scenario scenario1: first scenario.
    :param powersimdata.scenario.scenario.Scenario scenario2: second scenario.
    :param list/set/tuple b2b_indices: indices of HVDC lines which are back-to-backs.
    :param tuple figsize: size of the bokeh figure (in pixels).
    :param tuple x_range: x range to zoom plot to (EPSG:3857).
    :param tuple y_range: y range to zoom plot to (EPSG:3857).
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :param int/float legend_font_size: font size for legend.
    :param str legend_location: location for legend.
    :param \\*\\*plot_kwargs: collected keyword arguments to be passed to
        :func:`add_transmission_upgrades`.
    :raises ValueError: if grid model and interconnect of scenarios differ.
    :return: (*bokeh.plotting.figure.Figure*) -- map with color-coded upgrades.
    """
    # Validate inputs
    if not (scenario1.info["grid_model"] == scenario2.info["grid_model"] and
            scenario1.info["interconnect"] == scenario2.info["interconnect"]):
        raise ValueError(
            "Scenarios to compare must be same grid model & interconnect")

    # Pre-plot data processing
    grid1 = scenario1.state.get_grid()
    grid2 = scenario2.state.get_grid()
    branch_merge = calculate_branch_difference(grid1.branch, grid2.branch)
    dc_merge = calculate_dcline_difference(grid1, grid2)

    # Set up figure
    canvas = create_map_canvas(figsize=figsize,
                               x_range=x_range,
                               y_range=y_range)

    # Add state outlines
    default_state_borders_kwargs = {
        "line_color": "slategrey",
        "line_width": 1,
        "fill_alpha": 1,
        "background_map": False,
    }
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # add transmission map
    canvas = add_transmission_upgrades(canvas, branch_merge, dc_merge,
                                       b2b_indices, **plot_kwargs)

    canvas.legend.location = legend_location
    canvas.legend.label_text_font_size = f"{legend_font_size}pt"

    return canvas
def map_interconnections(
    grid,
    branch_distance_cutoff=5,
    figsize=(1400, 800),
    branch_width_scale_factor=0.5,
    hvdc_width_scale_factor=1,
    b2b_size_scale_factor=50,
    state_borders_kwargs=None,
):
    """Map transmission lines color coded by interconnection.

    :param powersimdata.input.grid.Grid grid: grid object.
    :param int/float branch_distance_cutoff: distance cutoff for branch display.
    :param tuple figsize: size of the bokeh figure (in pixels).
    :param int/float branch_width_scale_factor: scale factor for branch capacities.
    :param int/float hvdc_width_scale_factor: scale factor for hvdc capacities.
    :param int/float b2b_size_scale_factor: scale factor for back-to_back capacities.
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :return: (*bokeh.plotting.figure*) -- interconnection map with lines and nodes.
    :raises TypeError:
        if ``branch_device_cutoff`` is not ``float``.
        if ``branch_width_scale_factor`` is not ``int`` or ``float``.
        if ``hvdc_width_scale_factor`` is not ``int`` or ``float``.
        if ``b2b_size_scale_factor`` is not ``int`` or ``float``.
    :raises ValueError:
        if ``branch_device_cutoff`` is negative.
        if ``branch_width_scale_factor`` is negative.
        if ``hvdc_width_scale_factor`` is negative.
        if ``b2b_size_scale_factor`` is negative.
        if grid model is not supported.
    """
    _check_grid_type(grid)
    if not isinstance(branch_distance_cutoff, (int, float)):
        raise TypeError("branch_distance_cutoff must be an int")
    if branch_distance_cutoff <= 0:
        raise ValueError("branch_distance_cutoff must be strictly positive")
    if not isinstance(branch_width_scale_factor, (int, float)):
        raise TypeError("branch_width_scale_factor must be a int/float")
    if branch_width_scale_factor < 0:
        raise ValueError("branch_width_scale_factor must be positive")
    if not isinstance(hvdc_width_scale_factor, (int, float)):
        raise TypeError("hvdc_width_scale_factor must be a int/float")
    if hvdc_width_scale_factor < 0:
        raise ValueError("hvdc_width_scale_factor must be positive")
    if not isinstance(b2b_size_scale_factor, (int, float)):
        raise TypeError("b2b_size_scale_factor must be a int/float")
    if b2b_size_scale_factor < 0:
        raise ValueError("b2b_size_scale_factor must be positive")

    # branches
    branch = grid.branch.copy()
    branch["to_coord"] = list(zip(branch["to_lat"], branch["to_lon"]))
    branch["from_coord"] = list(zip(branch["from_lat"], branch["from_lon"]))
    branch["dist"] = branch.apply(
        lambda row: distance.haversine(row["to_coord"], row["from_coord"]),
        axis=1)
    branch = branch.loc[branch["dist"] > branch_distance_cutoff]
    branch = project_branch(branch)

    branch_west = branch.loc[branch["interconnect"] == "Western"]
    branch_east = branch.loc[branch["interconnect"] == "Eastern"]
    branch_tx = branch.loc[branch["interconnect"] == "Texas"]

    # HVDC lines
    all_dcline = grid.dcline.copy()
    all_dcline["from_lon"] = grid.bus.loc[all_dcline["from_bus_id"],
                                          "lon"].values
    all_dcline["from_lat"] = grid.bus.loc[all_dcline["from_bus_id"],
                                          "lat"].values
    all_dcline["to_lon"] = grid.bus.loc[all_dcline["to_bus_id"], "lon"].values
    all_dcline["to_lat"] = grid.bus.loc[all_dcline["to_bus_id"], "lat"].values
    all_dcline = project_branch(all_dcline)

    if grid.grid_model == "usa_tamu":
        b2b_id = range(9)
    else:
        raise ValueError("grid model is not supported")
    dcline = all_dcline.iloc[~all_dcline.index.isin(b2b_id)]
    b2b = all_dcline.iloc[b2b_id]

    # create canvas
    canvas = create_map_canvas(figsize=figsize)

    # add state borders
    default_state_borders_kwargs = {
        "line_width": 2,
        "fill_alpha": 0,
        "background_map": False,
    }
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # add state tooltips
    state_counts = count_nodes_per_state(grid)
    state2label = {
        s: c
        for s, c in zip(state_counts.index, state_counts.to_numpy())
    }
    canvas = add_state_tooltips(canvas, "nodes", state2label)

    canvas.multi_line(
        branch_west[["from_x", "to_x"]].to_numpy().tolist(),
        branch_west[["from_y", "to_y"]].to_numpy().tolist(),
        color="#006ff9",
        line_width=branch_west["rateA"].abs() * 1e-3 *
        branch_width_scale_factor,
        legend_label="Western",
    )

    canvas.multi_line(
        branch_east[["from_x", "to_x"]].to_numpy().tolist(),
        branch_east[["from_y", "to_y"]].to_numpy().tolist(),
        color="#8B36FF",
        line_width=branch_east["rateA"].abs() * 1e-3 *
        branch_width_scale_factor,
        legend_label="Eastern",
    )

    canvas.multi_line(
        branch_tx[["from_x", "to_x"]].to_numpy().tolist(),
        branch_tx[["from_y", "to_y"]].to_numpy().tolist(),
        color="#01D4ED",
        line_width=branch_tx["rateA"].abs() * 1e-3 * branch_width_scale_factor,
        legend_label="Texas",
    )

    canvas.multi_line(
        dcline[["from_x", "to_x"]].to_numpy().tolist(),
        dcline[["from_y", "to_y"]].to_numpy().tolist(),
        color="#FF2370",
        line_width=dcline["Pmax"] * 1e-3 * hvdc_width_scale_factor,
        legend_label="HVDC",
    )

    canvas.scatter(
        x=b2b["from_x"],
        y=b2b["from_y"],
        color="#FF2370",
        marker="triangle",
        size=b2b["Pmax"] * 1e-3 * b2b_size_scale_factor,
        legend_label="Back-to-Back",
    )

    canvas.legend.location = "bottom_left"
    canvas.legend.label_text_font_size = "12pt"

    return canvas
예제 #8
0
def add_lmp(
    bus_with_lmp,
    coordinate_rounding,
    lmp_min,
    lmp_max,
    num_ticks,
    figsize,
    scale_factor,
    state_borders_kwargs,
):
    """Add LMP to canvas.

    :param list bus_with_lmp: bus data frame with LMP values.
    :param int coordinate_rounding: number of digits to round lat/lon for aggregation.
    :param str file_name: name for output png file.
    :param int lmp_min: minimum LMP to clamp plot range to.
    :param int lmp_max: maximum LMP to clamp plot range to.
    :param int num_ticks: number of ticks to display on the color bar.
    :param tuple figsize: size of the bokeh figure (in pixels).
    :param int/float scale_factor: scaling factor for size of circles centered on buses.
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :return: (*bokeh.plotting.figure*) -- canvas with LMP..
    """

    # create canvas
    canvas = create_map_canvas(figsize=figsize)

    # add state borders
    default_state_borders_kwargs = {
        "line_color": "gray",
        "line_width": 2,
        "fill_alpha": 0,
        "background_map": False,
    }
    all_state_borders_kwargs = (
        {**default_state_borders_kwargs, **state_borders_kwargs}
        if state_borders_kwargs is not None
        else default_state_borders_kwargs
    )
    _check_func_kwargs(
        add_state_borders, set(all_state_borders_kwargs), "state_borders_kwargs"
    )
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # aggregate buses by lat/lon
    grouped_bus = aggregate_bus_lmp(bus_with_lmp, coordinate_rounding)

    # assign color
    norm = BoundaryNorm(boundaries=range(lmp_min, lmp_max + 1), ncolors=256, clip=True)
    grouped_bus["col"] = norm(grouped_bus["lmp"])
    grouped_bus["col"] = grouped_bus["col"].apply(lambda x: Turbo256[x])

    grouped_bus_info = {
        "x": grouped_bus["x"],
        "y": grouped_bus["y"],
        "col": grouped_bus["col"],
        "lmp": grouped_bus["lmp"].round(2),
    }

    circle = canvas.circle(
        "x",
        "y",
        color="col",
        size=2 * scale_factor,
        alpha=0.2,
        source=ColumnDataSource(grouped_bus_info),
    )
    hover = HoverTool(
        tooltips=[
            ("$/MWh", "@lmp{1.11}"),
        ],
        renderers=[circle],
    )
    canvas.add_tools(hover)

    # Add color bar
    cm = LinearColorMapper(palette="Turbo256", low=norm.vmin, high=norm.vmax)
    cb = ColorBar(
        color_mapper=cm,
        ticker=BasicTicker(desired_num_ticks=num_ticks),
        title="$/MWh",
        title_standoff=8,
        orientation="vertical",
        location=(0, 0),
    )
    canvas.add_layout(cb, "left")

    return canvas
def map_risk_bind(
        risk_or_bind,
        scenario=None,
        congestion_stats=None,
        branch=None,
        us_states_dat=None,
        vmin=None,
        vmax=None,
        color_bar_width=500,
        palette=None,
        all_branch_scale_factor=0.5,
        all_branch_min_width=0.2,
        select_branch_scale_factor=1,
        select_branch_min_width=2,
        figsize=(1400, 800),
        show_color_bar=True,
        state_borders_kwargs=None,
):
    """Make map showing risk or binding incidents on US states map.
    Either ``scenario`` XOR (``congestion_stats`` AND ``branch``) must be specified.

    :param str risk_or_bind: specify plotting "risk" or "bind".
    :param powersimdata.scenario.scenario.Scenario scenario: scenario to analyze.
    :param pandas.DataFrame congestion_stats: data frame as returned by
        :func:`postreise.analyze.transmission.utilization.generate_cong_stats`.
    :param pandas.DataFrame branch: branch data frame.
    :param int/float vmin: minimum value for color range. If None, use data minimum.
    :param int/float vmax: maximum value for color range. If None, use data maximum.
    :param int color_bar_width: width of color bar (pixels).
    :param iterable palette: sequence of colors used for color range, passed as
        `palette` kwarg to :func:`bokeh.transform.linear_cmap`.
        If None, default to `postreise.plot.colors.traffic_palette`.
    :param int/float all_branch_scale_factor: scale factor for unhighlighted branches
        (pixels/GW).
    :param int/float all_branch_min_width: minimum width for unhighlighted branches
        (pixels).
    :param int/float select_branch_scale_factor: scale factor for highlighted branches
        (pixels/GW).
    :param int/float select_branch_min_width: minimum width for highlighted branches
        (pixels).
    :param tuple(int, int) figsize: size of the bokeh figure (in pixels).
    :param bool show_color_bar: whether to render the color bar on the figure.
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :raises ValueError: if (``scenario`` XOR (``congestion_stats`` AND ``branch``)) are
        not properly specified.
    :return: (*bokeh.plotting.figure*) -- map of lines with risk and bind incidents
        color coded.
    """
    unit_labels = {"risk": "Risk (MWH)", "bind": "Binding incidents"}
    if risk_or_bind not in unit_labels:
        raise ValueError("risk_or_bind must be either 'risk' or 'bind'")
    risk_or_bind_units = unit_labels[risk_or_bind]

    # Check that we've appropriately specified:
    #    `scenario` XOR (`congestion_stats` AND `branch`)
    if scenario is not None:
        branch = scenario.state.get_grid().branch
        congestion_stats = generate_cong_stats(scenario.state.get_pf(), branch)
    elif congestion_stats is not None and branch is not None:
        pass
    else:
        raise ValueError(
            "Either scenario XOR (congestion_stats AND branch) must be specified"
        )

    if palette is None:
        palette = list(traffic_palette)

    # projection steps for mapping
    branch_congestion = pd.concat(
        [branch.loc[congestion_stats.index], congestion_stats], axis=1)
    branch_map_all = project_branch(branch)

    branch_congestion = branch_congestion[branch_congestion[risk_or_bind] > 0]
    branch_congestion.sort_values(by=[risk_or_bind])
    branch_map = project_branch(branch_congestion)
    min_val = branch_congestion[risk_or_bind].min() if vmin is None else vmin
    max_val = branch_congestion[risk_or_bind].max() if vmax is None else vmax
    mapper = linear_cmap(field_name=risk_or_bind,
                         palette=palette,
                         low=min_val,
                         high=max_val)
    multi_line_source = ColumnDataSource({
        "xs":
        branch_map[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_map[["from_y", "to_y"]].values.tolist(),
        risk_or_bind:
        branch_map[risk_or_bind],
        "value":
        branch_map[risk_or_bind].round(),
        "cap": (branch_map["capacity"] * select_branch_scale_factor / 1000 +
                select_branch_min_width),
        "capacity":
        branch_map.rateA.round(),
    })

    # Create canvas
    canvas = create_map_canvas(figsize=figsize)

    # Add state borders
    default_state_borders_kwargs = {"fill_alpha": 0.0, "background_map": True}
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # Add color bar
    if show_color_bar:
        color_bar = ColorBar(
            color_mapper=mapper["transform"],
            width=color_bar_width,
            height=5,
            location=(0, 0),
            title=risk_or_bind_units,
            orientation="horizontal",
            padding=5,
        )
        canvas.add_layout(color_bar, "center")

    canvas.multi_line(
        branch_map_all[["from_x", "to_x"]].to_numpy().tolist(),
        branch_map_all[["from_y", "to_y"]].to_numpy().tolist(),
        color="gray",
        line_width=(branch_map_all["rateA"] * all_branch_scale_factor / 1000 +
                    all_branch_min_width),
        alpha=0.5,
    )
    lines = canvas.multi_line("xs",
                              "ys",
                              color=mapper,
                              line_width="cap",
                              source=multi_line_source)
    hover = HoverTool(
        tooltips=[
            ("Capacity MW", "@capacity"),
            (risk_or_bind_units, "@value"),
        ],
        renderers=[lines],
    )
    canvas.add_tools(hover)
    return canvas
def map_utilization(
        scenario=None,
        utilization_df=None,
        branch=None,
        vmin=None,
        vmax=None,
        color_bar_width=500,
        palette=None,
        branch_scale_factor=0.5,
        branch_min_width=0.2,
        figsize=(1400, 800),
        show_color_bar=True,
        state_borders_kwargs=None,
):
    """Make map showing utilization. Utilization input can either be medians
    only, or can be normalized utilization dataframe.
    Either ``scenario`` XOR (``utilization_df`` AND ``branch``) must be specified.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario to analyze.
    :param pandas.DataFrame utilization_df: utilization returned by
        :func:`postreise.analyze.transmission.utilization.get_utilization`
    :param pandas.DataFrame branch: branch data frame.
    :param int/float vmin: minimum value for color range. If None, use data minimum.
    :param int/float vmax: maximum value for color range. If None, use data maximum.
    :param int color_bar_width: width of color bar (pixels).
    :param iterable palette: sequence of colors used for color range, passed as
        `palette` kwarg to :func:`bokeh.transform.linear_cmap`.
        If None, default to `postreise.plot.colors.traffic_palette`.
    :param int/float branch_scale_factor: scale factor for branches (pixels/GW).
    :param int/float branch_min_width: minimum width for branches (pixels).
    :param tuple(int, int) figsize: size of the bokeh figure (in pixels).
    :param dict state_borders_kwargs: keyword arguments to be passed to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :raises ValueError: if (``scenario`` XOR (``utilization_df`` AND ``branch``)) are
        not properly specified.
    :return: (*bokeh.plotting.figure*) -- map of lines with median utilization color
        coded.
    """
    # Check that we've appropriately specified:
    #    `scenario` XOR (`utilization_df` AND `branch`)
    if scenario is not None:
        branch = scenario.state.get_grid().branch
        utilization_df = get_utilization(branch,
                                         scenario.state.get_pf(),
                                         median=True)
    elif utilization_df is not None and branch is not None:
        pass
    else:
        raise ValueError(
            "Either scenario XOR (utilization_df AND branch) must be specified"
        )

    if palette is None:
        palette = list(traffic_palette)

    branch_mask = branch.rateA != 0
    median_util = utilization_df[branch.loc[branch_mask].index].median()
    branch_utilization = pd.concat(
        [branch.loc[branch_mask],
         median_util.rename("median_utilization")],
        axis=1)
    lines = branch_utilization.loc[(
        branch_utilization["branch_device_type"] == "Line")]

    min_val = lines["median_utilization"].min() if vmin is None else vmin
    max_val = lines["median_utilization"].max() if vmax is None else vmax

    mapper = linear_cmap(
        field_name="median_utilization",
        palette=palette,
        low=min_val,
        high=max_val,
    )

    branch_map = project_branch(branch_utilization)
    branch_map = branch_map.sort_values(by=["median_utilization"])
    branch_map = branch_map[~branch_map.isin([np.nan, np.inf, -np.inf]).any(1)]

    multi_line_source = ColumnDataSource({
        "xs":
        branch_map[["from_x", "to_x"]].values.tolist(),
        "ys":
        branch_map[["from_y", "to_y"]].values.tolist(),
        "median_utilization":
        branch_map.median_utilization,
        "width":
        branch_map.rateA * branch_scale_factor / 1000 + branch_min_width,
        "util":
        branch_map.median_utilization.round(2),
        "capacity":
        branch_map.rateA.round(),
    })

    # Create canvas
    canvas = create_map_canvas(figsize=figsize)

    # Add state borders
    default_state_borders_kwargs = {"fill_alpha": 0.0, "line_width": 2}
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # Add color bar
    if show_color_bar:
        color_bar = ColorBar(
            color_mapper=mapper["transform"],
            width=color_bar_width,
            height=5,
            location=(0, 0),
            title="median utilization",
            orientation="horizontal",
            padding=5,
        )
        canvas.add_layout(color_bar, "center")

    lines = canvas.multi_line("xs",
                              "ys",
                              color=mapper,
                              line_width="width",
                              source=multi_line_source)
    hover = HoverTool(
        tooltips=[
            ("Capacity MW", "@capacity"),
            ("Utilization", "@util{f0.00}"),
        ],
        renderers=[lines],
    )
    canvas.add_tools(hover)
    return canvas
예제 #11
0
def test_check_func_kwargs():
    def func(a=1, b=2, c=3):
        return a * b * c

    assert _check_func_kwargs(func, ["a", "b"], "dummy") is None
    assert _check_func_kwargs(func, ["a", "b", "c"], "dummy") is None
예제 #12
0
def map_plant_capacity(
    scenario,
    resources,
    figsize=(1400, 800),
    x_range=None,
    y_range=None,
    disaggregation=None,
    state_borders_kwargs=None,
    min_capacity=1,
    size_factor=1,
    alpha=0.5,
    legend_font_size=12,
    legend_location="bottom_right",
):
    """Make map of plant capacities, optionally disaggregated by new/existing. Area
    is proportional to capacity.

    :param powersimdata.scenario.scenario.Scenario scenario: scenario instance.
    :param iterable resources: which types of resources to plot.
    :param tuple figsize: size of the bokeh figure (in pixels).
    :param tuple x_range: x range to zoom plot to (EPSG:3857).
    :param tuple y_range: y range to zoom plot to (EPSG:3857).
    :param str disaggregation: method used to disaggregate plants:
        if "new_vs_existing_plants": separates plants into added vs. existing.
        if None, no disaggregation.
    :param dict state_borders_kwargs: keyword arguments to pass to
        :func:`postreise.plot.plot_states.add_state_borders`.
    :param float/int min_capacity: minimum bus capacity (MW) for markers to be plotted.
    :param float/int size_factor: scale size of glyphs.
    :param float/int alpha: opacity of circles (between 0 and 1).
    :param int/float legend_font_size: font size for legend.
    :param str legend_location: location for legend.
    :return: (*bokeh.plotting.figure.Figure*) -- map with color-coded upgrades.
    """
    _check_scenario_is_in_analyze_state(scenario)

    # create canvas
    canvas = create_map_canvas(figsize=figsize,
                               x_range=x_range,
                               y_range=y_range)

    # add state borders
    default_state_borders_kwargs = {
        "line_color": "gray",
        "line_width": 2,
        "fill_alpha": 0,
        "background_map": True,
    }
    all_state_borders_kwargs = ({
        **default_state_borders_kwargs,
        **state_borders_kwargs
    } if state_borders_kwargs is not None else default_state_borders_kwargs)
    _check_func_kwargs(add_state_borders, set(all_state_borders_kwargs),
                       "state_borders_kwargs")
    canvas = add_state_borders(canvas, **all_state_borders_kwargs)

    # add plant capacity
    add_plant_capacity(
        canvas,
        scenario,
        resources,
        disaggregation,
        min_capacity,
        size_factor,
        alpha,
    )

    canvas.legend.label_text_font_size = f"{legend_font_size}pt"
    canvas.legend.location = legend_location

    return canvas