Source code for hynet.reduction.large_scale.evaluation

"""
Evaluation of the network reduction accuracy.
"""

import logging

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from matplotlib.ticker import MaxNLocator

from hynet.types_ import hynet_float_
from hynet.opf.result import OPFResult

_log = logging.getLogger(__name__)


[docs]def evaluate_reduction(opf_reference, opf_reduction, name=None): """ Return an evaluation of the extent and accuracy of the reduction. Parameters ---------- opf_reference : OPFResult OPF result of the scenario *before* the reduction. opf_reduction : OPFResult OPF result of the scenario *after* the reduction. name : object, optional Name of the returned series. This may be set, e.g., to the name of the reduction stage or characteristic parameter value. Returns ------- evaluation : pandas.Series Series with information on the extent and accuracy of the reduction with the following entries: ``bus_reduction``: (``hynet_float_``) Ratio of the number of reduced buses w.r.t. the total number of buses before the reduction. ``branch_reduction``: (``hynet_float_``) Ratio of the number of reduced branches w.r.t. the total number of branches before the reduction. ``cycle_reduction``: (``hynet_float_``) Ratio of the number of reduced cycles w.r.t. the total number of cycles before the reduction. ``error_disp``: (``hynet_float_``) Contribution-weighted mean relative *active* power dispatch error as defined in equation (1) in [1]_. ``error_disp_s``: (``hynet_float_``) Contribution-weighted mean relative *apparent* power dispatch error. ``error_flow``: (``hynet_float_``) Contribution-weighted mean relative *active* power branch flow error as defined in equation (2) in [1]_. ``error_flow_s``: (``hynet_float_``) Contribution-weighted mean relative *apparent* power branch flow error. References ---------- .. [1] J. Sistermanns, M. Hotz, D. Hewes, R. Witzmann, and W. Utschick, "Feature- and Structure-Preserving Network Reduction for Large-Scale Transmission Grids," 13th IEEE PES PowerTech Conference, Milano, Italy, Jun. 2019. """ scenario_ref = opf_reference.scenario scenario_red = opf_reduction.scenario if opf_reference.empty or opf_reduction.empty: raise ValueError("The evaluation requires nonempty OPF results.") if not (opf_reference.is_physical and opf_reduction.is_physical): _log.warning("The evaluation is performed on a non-physical solution.") if not opf_reduction.injector.index.equals(opf_reference.injector.index): raise ValueError("The injectors of the two systems do not match.") if not scenario_red.branch.index.isin(scenario_ref.branch.index).all(): raise ValueError("The branches of the reduction are not a subset " "of the branches of the reference system.") evaluation = pd.Series(dtype=hynet_float_) if name is not None: evaluation.name = name # Dispatch error dispatch_ref = opf_reference.injector['s'].to_numpy() dispatch_red = opf_reduction.injector['s'].to_numpy() dispatch_diff = dispatch_red - dispatch_ref evaluation.at['error_disp'] = \ np.sum(np.abs(dispatch_diff.real)) / np.sum(np.abs(dispatch_ref.real)) evaluation.at['error_disp_s'] = \ np.sum(np.abs(dispatch_diff)) / np.sum(np.abs(dispatch_ref)) # Branch flow error idx = opf_reduction.branch.index flow_ref = opf_reference.branch.loc[idx, ['s_src', 's_dst']] flow_red = opf_reduction.branch.loc[idx, ['s_src', 's_dst']] flow_ref_p = np.maximum(np.abs(flow_ref['s_src'].to_numpy().real), np.abs(flow_ref['s_dst'].to_numpy().real)) flow_red_p = np.maximum(np.abs(flow_red['s_src'].to_numpy().real), np.abs(flow_red['s_dst'].to_numpy().real)) flow_ref_s = flow_ref.abs().max(axis=1).to_numpy() flow_red_s = flow_red.abs().max(axis=1).to_numpy() if flow_ref_p.size: evaluation.at['error_flow'] = \ np.sum(np.abs(flow_red_p - flow_ref_p)) / np.sum(flow_ref_p) evaluation.at['error_flow_s'] = \ np.sum(np.abs(flow_red_s - flow_ref_s)) / np.sum(flow_ref_s) else: evaluation.at['error_flow'] = np.nan evaluation.at['error_flow_s'] = np.nan # Reduction of system entities buses_ref = scenario_ref.num_buses buses_red = scenario_red.num_buses evaluation.at['bus_reduction'] = (buses_ref - buses_red) / buses_ref branches_ref = scenario_ref.num_branches branches_red = scenario_red.num_branches if branches_ref: evaluation.at['branch_reduction'] = \ (branches_ref - branches_red) / branches_ref else: evaluation.at['branch_reduction'] = 0.0 cycles_ref = scenario_ref.analyze_cycles()['num_cycles'].sum() cycles_red = scenario_red.analyze_cycles()['num_cycles'].sum() if cycles_ref: evaluation.at['cycle_reduction'] = \ (cycles_ref - cycles_red) / cycles_ref else: evaluation.at['cycle_reduction'] = 0.0 return evaluation
[docs]def show_reduction_evaluation(evaluation): """ Show the evaluation of a series of reduction stages or a parameter sweep. Parameters ---------- evaluation : pandas.DataFrame Data frame with information on the extent and accuracy of the individual reduction stages or parameters. This data frame must be indexed by the stage names / parameter values and comprise the following columns: ``bus_reduction``: (``hynet_float_``) Ratio of the number of reduced buses w.r.t. the total number of buses before the reduction. ``branch_reduction``: (``hynet_float_``) Ratio of the number of reduced branches w.r.t. the total number of branches before the reduction. ``cycle_reduction``: (``hynet_float_``) Ratio of the number of reduced cycles w.r.t. the total number of cycles before the reduction. ``error_disp``: (``hynet_float_``) Contribution-weighted mean relative *active* power dispatch error as defined in equation (1) in [1]_. ``error_flow``: (``hynet_float_``) Contribution-weighted mean relative *active* power branch flow error as defined in equation (2) in [1]_. Returns ------- fig : matplotlib.figure.Figure References ---------- .. [1] J. Sistermanns, M. Hotz, D. Hewes, R. Witzmann, and W. Utschick, "Feature- and Structure-Preserving Network Reduction for Large-Scale Transmission Grids," 13th IEEE PES PowerTech Conference, Milano, Italy, Jun. 2019. """ if np.issubdtype(evaluation.index.dtype, np.number): index = evaluation.index.to_numpy() labels = None else: index = 1 + np.arange(len(evaluation.index)) labels = evaluation.index.to_numpy() def set_x_labels(ax): ax.margins(x=0) if labels is None: return ax.xaxis.set_major_locator(MaxNLocator(integer=True)) ax.set_xticklabels(['' if not (i.is_integer() and 1 <= i <= len(labels)) else str(labels[int(i - 1)]) for i in ax.get_xticks()]) fig = plt.figure(figsize=(10, 6)) ax = fig.add_subplot(2, 1, 1) ax.plot(index, evaluation['bus_reduction'].to_numpy() * 100, color='xkcd:red', marker='.', linestyle='-', linewidth=1, label='Buses') ax.plot(index, evaluation['branch_reduction'].to_numpy() * 100, color='xkcd:sea blue', marker='.', linestyle='-', linewidth=1, label='Branches') ax.plot(index, evaluation['cycle_reduction'].to_numpy() * 100, color='xkcd:green', marker='.', linestyle='-', linewidth=1, label='Cycles') set_x_labels(ax) ax.set_ylabel('Reduction in %') ax.legend(loc='lower right', frameon=True) if hasattr(evaluation, 'name'): ax.set_title(evaluation.name) ax = fig.add_subplot(2, 1, 2) ax.plot(index, evaluation['error_disp'].to_numpy() * 100, color='xkcd:red', marker='.', linestyle='-', linewidth=1, label='Dispatch') ax.plot(index, evaluation['error_flow'].to_numpy() * 100, color='xkcd:sea blue', marker='.', linestyle='-', linewidth=1, label='Branch flow') set_x_labels(ax) ax.set_ylabel('Error in %') ax.legend(loc='lower right', frameon=True) if evaluation.index.name: ax.set_xlabel(evaluation.index.name) fig.tight_layout() fig.show() return fig