def calculate_ac_inv_costs(scenario, sum_results=True, exclude_branches=None): """Calculate cost of upgrading AC lines and/or transformers in a scenario. NEEM regions are used to find regional multipliers. :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :param bool sum_results: whether to sum data frame for each branch type. Defaults to True. :return: (*dict*) -- keys are {'line_cost', 'transformer_cost'}, values are either float if ``sum_results``, or pandas Series indexed by branch ID. Whether summed or not, values are $USD, inflation-adjusted to today. """ base_grid = Grid(scenario.info["interconnect"].split("_")) grid = scenario.state.get_grid() # find upgraded AC lines grid_new = cp.deepcopy(grid) # Reindex so that we don't get NaN when calculating upgrades for new branches base_grid.branch = base_grid.branch.reindex(grid_new.branch.index).fillna(0) grid_new.branch.rateA = grid.branch.rateA - base_grid.branch.rateA grid_new.branch = grid_new.branch[grid_new.branch.rateA != 0.0] if exclude_branches is not None: present_exclude_branches = set(exclude_branches) & set(grid_new.branch.index) grid_new.branch.drop(index=present_exclude_branches, inplace=True) costs = _calculate_ac_inv_costs(grid_new, sum_results) return costs
def calculate_ac_inv_costs(scenario, sum_results=True, exclude_branches=None): """Given a Scenario object, calculate the total cost of building that scenario's upgrades of lines and transformers. Currently uses NEEM regions to find regional multipliers. Currently ignores financials, but all values are in 2010 $-year. Need to test that there aren't any na values in regional multipliers (some empty parts of table) :param powersimdata.scenario.scenario.Scenario scenario: scenario instance. :param boolean sum_results: if True, sum dataframe for each category. :return: (*dict*) -- Total costs (line costs, transformer costs) (in $2010). """ base_grid = Grid(scenario.info["interconnect"].split("_")) grid = scenario.state.get_grid() # find upgraded AC lines grid_new = cp.deepcopy(grid) # Reindex so that we don't get NaN when calculating upgrades for new branches base_grid.branch = base_grid.branch.reindex( grid_new.branch.index).fillna(0) grid_new.branch.rateA = grid.branch.rateA - base_grid.branch.rateA grid_new.branch = grid_new.branch[grid_new.branch.rateA != 0.0] if exclude_branches is not None: present_exclude_branches = set(exclude_branches) & set( grid_new.branch.index) grid_new.branch.drop(index=present_exclude_branches, inplace=True) costs = _calculate_ac_inv_costs(grid_new, sum_results) return costs
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