Source code for postreise.analyze.transmission.utilization

import numpy as np
import pandas as pd
from powersimdata.utility.distance import great_circle_distance


[docs]def get_utilization(branch, pf, median=False): """Generate utilization table to be used as input for congestion analyses. :param pandas.DataFrame branch: branch data frame. :param pandas.DataFrame pf: power flow data frame. :param boolean median: take medians of pf for utilization calculation :return: (*pandas.DataFrame*) -- power flow data frame (per-unit). """ pf = pf.abs() if median: pf = pd.DataFrame(pf.median()).T return pf.divide(branch.rateA).replace(np.inf, 0)
def _count_hours_gt_threshold(utilization, threshold): """Calculate number of hours above a given utilization threshold. :param pandas.DataFrame utilization: normalized power flow data frame as returned by :func:`get_utilization`. :param float threshold: utilization threshold ([0,1]). :return: (*pandas.Series*) -- number of hours above utilization threshold. """ return (utilization > threshold).sum() def _flag(statistics, utilname, threshold, uflagname): """Flag branches that meet screening criteria. :param pandas.DataFrame statistics: congestion statistics as returned by :func:`generate_cong_stats`. :param string utilname: field with percent of time above ``threshold``. :param float threshold: threshold for percent time for flag level. :param string uflagname: name of flag. :return: (*pandas.DataFrame*) -- data frame with *'uflagname'*. """ return (statistics[utilname] >= threshold).astype(int).rename(uflagname)
[docs]def generate_cong_stats(pf, branch, util=None, threshold=None): """Generate congestion/utilization statistics from powerflow data (WECC congestion reports' analyses are the inspiration for these analyses and are the source of the default parameters). The report is available `here <https://www.wecc.org/Reliability/TAS_PathReports_Combined_FINAL.pdf>`_. :param pandas.DataFrame pf: power flow data frame :param pandas.DataFrame branch: branch data frame :param list util: utilization (float) flag level 1, 2, 3. Default values are values used by WECC: 0.75, 0.9, 99. :param list threshold: threshold for proportion time, for flag level 1, 2, 3. Default values are values used by WECC: 0.5, 0.2, 0.05. :return: (*pandas.DataFrame*) -- congestion statistics. *'per_util1'*, *'per_util2'*, *'per_util3'*, *'u1flag'*, *'u2flag'*, *'u3flag'*, *'sumflag'*, *'bind'*, *'risk'*. """ if util is None: util = [0.75, 0.9, 0.99] if threshold is None: threshold = [0.5, 0.2, 0.05] print("Removing non line branches") branch = branch[branch.branch_device_type == "Line"] pf = pf.loc[:, branch.index] print("Removing lines that never are >75% utilized") pf = pf.loc[:, pf.abs().max().divide(branch.rateA).replace(np.inf, 0) > 0.75] branch = branch.loc[pf.columns, :] print("Getting utilization") utilization_df = get_utilization(branch, pf) print("Counting hours") n_hours = len(utilization_df) print("Getting fraction of hours above threshold") per_util1 = _count_hours_gt_threshold(utilization_df, util[0]) / n_hours per_util2 = _count_hours_gt_threshold(utilization_df, util[1]) / n_hours per_util3 = _count_hours_gt_threshold(utilization_df, util[2]) / n_hours print("Calculating binding hours") bind = (utilization_df >= 1).sum() print("Calculating risk") risk = (pf[utilization_df > util[1]].sum()).fillna(0) print("Combining branch and utilization info") statistics = pd.concat( [ branch["rateA"], branch["branch_device_type"], per_util1, per_util2, per_util3, bind, risk, ], axis=1, ) statistics.loc[statistics.rateA == 0, ["rateA"]] = np.nan statistics.columns = [ "capacity", "branch_device_type", "per_util1", "per_util2", "per_util3", "bind", "risk", ] vals = [ ["per_util1", threshold[0], "uflag1"], ["per_util2", threshold[1], "uflag2"], ["per_util3", threshold[2], "uflag3"], ] for x in vals: statistics = pd.concat( [statistics, _flag(statistics, x[0], x[1], x[2])], axis=1 ) col_list = ["uflag1", "uflag2", "uflag3"] statistics["sumflag"] = statistics[col_list].sum(axis=1) print("Calculating distance and finalizing results") distance = branch.apply(great_circle_distance, axis=1).rename("dist") statistics = pd.concat([statistics, distance], axis=1) return statistics