示例#1
0
def test_calculate_ac_inv_costs_not_summed(mock_grid):
    inflation_2010 = calculate_inflation(2010)
    inflation_2020 = calculate_inflation(2020)
    expected_ac_cost = {
        # ((reg_mult1 + reg_mult2) / 2) * sum(basecost * rateA * miles)
        "line_cost": {
            10:
            0,  # This branch would normally be dropped by calculate_ac_inv_costs
            11:
            ((1 + 2.25) / 2) * 3666.67 * 10 * 679.179925842 * inflation_2010,
            12:
            ((1 + 2.25) / 2) * 1500 * 1100 * 680.986501516 * inflation_2010,
            15: ((1 + 1) / 2) * 2333.33 * 50 * 20.003889808 * inflation_2010,
        },
        # for each: rateA * basecost * regional multiplier
        "transformer_cost": {
            13: (30 * 7670 * 1) * inflation_2020,
            14: (40 * 8880 * 2.25) * inflation_2020,
        },
    }
    ac_cost = _calculate_ac_inv_costs(mock_grid, sum_results=False)
    for branch_type, upgrade_costs in expected_ac_cost.items():
        assert set(upgrade_costs.keys()) == set(ac_cost[branch_type].index)
        for branch, cost in upgrade_costs.items():
            assert cost == pytest.approx(ac_cost[branch_type].loc[branch])
def test_calculate_ac_inv_costs(mock_grid):
    expected_ac_cost = {
        # ((reg_mult1 + reg_mult2) / 2) * sum(basecost * rateA * miles)
        "line_cost":
        (((1 + 2.25) / 2) *
         (3666.67 * 10 * 679.179925842 + 1500 * 1100 * 680.986501516) *
         calculate_inflation(2010)),
        # for each: rateA * basecost * regional multiplier
        "transformer_cost":
        ((30 * 7670 * 1) + (40 * 8880 * 2.25)) * calculate_inflation(2020),
    }
    ac_cost = _calculate_ac_inv_costs(mock_grid)
    assert ac_cost.keys() == expected_ac_cost.keys()
    for k in ac_cost.keys():
        assert ac_cost[k] == pytest.approx(expected_ac_cost[k])
示例#3
0
def test_calculate_ac_inv_costs_transformers_only(mock_grid):
    expected_ac_cost = {
        # ((reg_mult1 + reg_mult2) / 2) * sum(basecost * rateA * miles)
        "line_cost":
        0,
        # for each: rateA * basecost * regional multiplier
        "transformer_cost":
        ((30 * 7670 * 1) + (40 * 8880 * 2.25)) * calculate_inflation(2020),
    }
    this_grid = copy.deepcopy(mock_grid)
    this_grid.branch = this_grid.branch.query(
        "branch_device_type == 'Transformer'")
    ac_cost = _calculate_ac_inv_costs(this_grid)
    assert ac_cost.keys() == expected_ac_cost.keys()
    for k in ac_cost.keys():
        assert ac_cost[k] == pytest.approx(expected_ac_cost[k])
示例#4
0
def test_calculate_ac_inv_costs_lines_only(mock_grid):
    expected_ac_cost = {
        # ((reg_mult1 + reg_mult2) / 2) * sum(basecost * rateA * miles)
        "line_cost":
        (calculate_inflation(2010) *
         ((((1 + 2.25) / 2) *
           (3666.67 * 10 * 679.179925842 + 1500 * 1100 * 680.986501516)) +
          ((1 + 1) / 2) * 2333.33 * 50 * 20.003889808)),
        # for each: rateA * basecost * regional multiplier
        "transformer_cost":
        0,
    }
    this_grid = copy.deepcopy(mock_grid)
    this_grid.branch = this_grid.branch.query("branch_device_type == 'Line'")
    ac_cost = _calculate_ac_inv_costs(this_grid)
    assert ac_cost.keys() == expected_ac_cost.keys()
    for k in ac_cost.keys():
        assert ac_cost[k] == pytest.approx(expected_ac_cost[k])
示例#5
0
def _identify_mesh_branch_upgrades(
    ref_scenario,
    upgrade_n=100,
    allow_list=None,
    deny_list=None,
    congestion_metric="quantile",
    cost_metric="branches",
    quantile=None,
):
    """Identify the N most congested branches in a previous scenario, based on
    the quantile value of congestion duals, where N is specified by ``upgrade_n``. A
    quantile value of 0.95 obtains the branches with highest dual in top 5% of hours.

    :param powersimdata.scenario.scenario.Scenario ref_scenario: the reference
        scenario to be used to determine the most congested branches.
    :param int upgrade_n: the number of branches to upgrade.
    :param list/set/tuple/None allow_list: only select from these branch IDs.
    :param list/set/tuple/None deny_list: never select any of these branch IDs.
    :param str congestion_metric: numerator method: 'quantile' or 'mean'.
    :param str cost_metric: denominator method: 'branches', 'cost', 'MW', or
        'MWmiles'.
    :param float quantile: if ``congestion_metric`` == 'quantile', this is the quantile
        to use to judge branch congestion (otherwise it is unused). If None, a default
        value of 0.95 is used, i.e. we evaluate the shadow price for the worst 5% of
        hours.
    :raises ValueError: if ``congestion_metric`` or ``cost_metric`` is not recognized,
        ``congestion_metric`` == 'mean' but a ``quantile`` is specified, or
        ``congestion_metric`` == 'quantile' but there are not enough branches which are
        congested at the desired frequency based on the ``quantile`` specified.
    :return: (*set*) -- A set of ints representing branch indices.
    """

    # How big does a dual value have to be to be 'real' and not barrier cruft?
    cong_significance_cutoff = 1e-6  # $/MWh
    # If we rank by MW-miles, what 'length' do we give to zero-length branches?
    zero_length_value = 1  # miles
    # If the quantile is not provided, what should we default to?
    default_quantile = 0.95

    # Validate congestion_metric input
    allowed_congestion_metrics = ("mean", "quantile")
    if congestion_metric not in allowed_congestion_metrics:
        allowed_list = ", ".join(allowed_congestion_metrics)
        raise ValueError(f"congestion_metric must be one of: {allowed_list}")
    if congestion_metric == "mean" and quantile is not None:
        raise ValueError("quantile cannot be specified if congestion_metric is 'mean'")
    if congestion_metric == "quantile" and quantile is None:
        quantile = default_quantile

    # Validate cost_metric input
    allowed_cost_metrics = ("branches", "MW", "MWmiles", "cost")
    if cost_metric not in allowed_cost_metrics:
        allowed_list = ", ".join(allowed_cost_metrics)
        raise ValueError(f"cost_metric must be one of: {allowed_list}")

    # Get raw congestion dual values, add them
    ref_cong_abs = ref_scenario.state.get_congu() + ref_scenario.state.get_congl()
    all_branches = set(ref_cong_abs.columns.tolist())
    # Create validated composite allow list, and filter shadow price data frame
    composite_allow_list = _construct_composite_allow_list(
        all_branches, allow_list, deny_list
    )
    ref_cong_abs = ref_cong_abs.filter(items=composite_allow_list)

    if congestion_metric == "mean":
        congestion_metric_values = ref_cong_abs.mean()
    if congestion_metric == "quantile":
        congestion_metric_values = ref_cong_abs.quantile(quantile)

    # Filter out 'insignificant' values
    congestion_metric_values = congestion_metric_values.where(
        congestion_metric_values > cong_significance_cutoff
    ).dropna()
    # Filter based on composite allow list
    congested_indices = list(congestion_metric_values.index)

    # Ensure that we have enough congested branches to upgrade
    num_congested = len(congested_indices)
    if num_congested < upgrade_n:
        err_msg = "not enough congested branches: "
        err_msg += f"{upgrade_n} desired, but only {num_congested} congested."
        if congestion_metric == "quantile":
            err_msg += (
                f" The quantile used is {quantile}; increasing this value will increase"
                " the number of branches which qualify as having 'frequent-enough'"
                " congestion and can be selected for upgrades."
            )
        raise ValueError(err_msg)

    # Calculate selected cost metric for congested branches
    if cost_metric == "cost":
        # Calculate costs for an upgrade dataframe containing only composite_allow_list
        base_grid = ref_scenario.get_base_grid()
        base_grid.branch = base_grid.branch.filter(items=congested_indices, axis=0)
        upgrade_costs = _calculate_ac_inv_costs(base_grid, sum_results=False)
        # Merge the individual line/transformer data into a single Series
        merged_upgrade_costs = pd.concat([v for v in upgrade_costs.values()])
    if cost_metric in ("MW", "MWmiles"):
        ref_grid = ref_scenario.state.get_grid()
        branch_ratings = ref_grid.branch.loc[congested_indices, "rateA"]
        # Calculate 'original' branch capacities, since that's our increment
        ref_ct = ref_scenario.state.get_ct()
        try:
            branch_ct = ref_ct["branch"]["branch_id"]
        except KeyError:
            branch_ct = {}
        branch_prev_scaling = pd.Series(
            {i: (branch_ct[i] if i in branch_ct else 1) for i in congested_indices}
        )
        branch_ratings = branch_ratings / branch_prev_scaling
    # Then, apply this metric
    if cost_metric == "MW":
        branch_metric = congestion_metric_values / branch_ratings
    elif cost_metric == "MWmiles":
        branch_lengths = ref_grid.branch.loc[congested_indices].apply(
            lambda x: haversine((x.from_lat, x.from_lon), (x.to_lat, x.to_lon)), axis=1
        )
        # Replace zero-length branches by designated default, don't divide by 0
        branch_lengths = branch_lengths.replace(0, value=zero_length_value)
        branch_metric = congestion_metric_values / (branch_ratings * branch_lengths)
    elif cost_metric == "cost":
        branch_metric = congestion_metric_values / merged_upgrade_costs
    else:
        # By process of elimination, all that's left is method 'branches'
        branch_metric = congestion_metric_values

    # Sort by our metric, grab indexes for N largest values (tail), return
    ranked_branches = set(branch_metric.sort_values().tail(upgrade_n).index)
    return ranked_branches
示例#6
0
def _identify_mesh_branch_upgrades(
    ref_scenario,
    upgrade_n=100,
    quantile=0.95,
    allow_list=None,
    deny_list=None,
    method="branches",
):
    """Identify the N most congested branches in a previous scenario, based on
    the quantile value of congestion duals. A quantile value of 0.95 obtains
    the branches with highest dual in top 5% of hours.

    :param powersimdata.scenario.scenario.Scenario ref_scenario: the reference
        scenario to be used to determine the most congested branches.
    :param int upgrade_n: the number of branches to upgrade.
    :param float quantile: the quantile to use to judge branch congestion.
    :param list/set/tuple/None allow_list: only select from these branch IDs.
    :param list/set/tuple/None deny_list: never select any of these branch IDs.
    :param str method: prioritization method: 'branches', 'MW', or 'MWmiles'.
    :raises ValueError: if 'method' not recognized, or not enough branches to
        upgrade.
    :return: (*set*) -- A set of ints representing branch indices.
    """

    # How big does a dual value have to be to be 'real' and not barrier cruft?
    cong_significance_cutoff = 1e-6  # $/MWh
    # If we rank by MW-miles, what 'length' do we give to zero-length branches?
    zero_length_value = 1  # miles

    # Validate method input
    allowed_methods = ("branches", "MW", "MWmiles", "cost")
    if method not in allowed_methods:
        allowed_list = ", ".join(allowed_methods)
        raise ValueError(f"method must be one of: {allowed_list}")

    # Get raw congestion dual values, add them
    ref_cong_abs = ref_scenario.state.get_congu(
    ) + ref_scenario.state.get_congl()
    all_branches = set(ref_cong_abs.columns.tolist())
    # Create validated composite allow list
    composite_allow_list = _construct_composite_allow_list(
        all_branches, allow_list, deny_list)

    # Parse 2-D array to vector of quantile values
    ref_cong_abs = ref_cong_abs.filter(items=composite_allow_list)
    quantile_cong_abs = ref_cong_abs.quantile(quantile)
    # Filter out insignificant values
    significance_bitmask = quantile_cong_abs > cong_significance_cutoff
    quantile_cong_abs = quantile_cong_abs.where(significance_bitmask).dropna()
    # Filter based on composite allow list
    congested_indices = list(quantile_cong_abs.index)

    # Ensure that we have enough congested branches to upgrade
    num_congested = len(quantile_cong_abs)
    if num_congested < upgrade_n:
        err_msg = "not enough congested branches: "
        err_msg += f"{upgrade_n} desired, but only {num_congested} congested."
        raise ValueError(err_msg)

    # Calculate selected metric for congested branches
    if method == "cost":
        # Calculate costs for an upgrade dataframe containing only composite_allow_list
        base_grid = Grid(ref_scenario.info["interconnect"],
                         ref_scenario.info["grid_model"])
        base_grid.branch = base_grid.branch.filter(items=congested_indices,
                                                   axis=0)
        upgrade_costs = _calculate_ac_inv_costs(base_grid, sum_results=False)
        # Merge the individual line/transformer data into a single Series
        merged_upgrade_costs = pd.concat([v for v in upgrade_costs.values()])
    if method in ("MW", "MWmiles"):
        ref_grid = ref_scenario.state.get_grid()
        branch_ratings = ref_grid.branch.loc[congested_indices, "rateA"]
        # Calculate 'original' branch capacities, since that's our increment
        ref_ct = ref_scenario.state.get_ct()
        try:
            branch_ct = ref_ct["branch"]["branch_id"]
        except KeyError:
            branch_ct = {}
        branch_prev_scaling = pd.Series({
            i: (branch_ct[i] if i in branch_ct else 1)
            for i in congested_indices
        })
        branch_ratings = branch_ratings / branch_prev_scaling
    if method == "MW":
        branch_metric = quantile_cong_abs / branch_ratings
    elif method == "MWmiles":
        branch_lengths = ref_grid.branch.loc[congested_indices].apply(
            lambda x: haversine((x.from_lat, x.from_lon),
                                (x.to_lat, x.to_lon)),
            axis=1)
        # Replace zero-length branches by designated default, don't divide by 0
        branch_lengths = branch_lengths.replace(0, value=zero_length_value)
        branch_metric = quantile_cong_abs / (branch_ratings * branch_lengths)
    elif method == "cost":
        branch_metric = quantile_cong_abs / merged_upgrade_costs
    else:
        # By process of elimination, all that's left is method 'branches'
        branch_metric = quantile_cong_abs

    # Sort by our metric, grab indexes for N largest values (tail), return
    ranked_branches = set(branch_metric.sort_values().tail(upgrade_n).index)
    return ranked_branches