Source code for hynet.opf.result

"""
Representation of an optimal power flow result.
"""

import logging

import numpy as np

from hynet.types_ import SolverType, SolverStatus
from hynet.system.result import SystemResult
import hynet.config as config

_log = logging.getLogger(__name__)


[docs]class OPFResult(SystemResult): """ Result of an optimal power flow calculation. **Remark:** In the data frames below, the respective column for the dual variables of a type of constraint (e.g., voltage drop) is only present if at least one constraint of this constraint type appears in the problem formulation. Parameters ---------- model : OPFModel Model for the processed optimal power flow problem. empty : bool ``True`` if the object does not contain any result data and ``False`` otherwise. solver : SolverInterface Solver object by which the result was obtained. solver_status : SolverStatus Status reported by the solver. solver_time : float Duration of the call to the solver in seconds. optimal_value : float Optimal objective value or ``numpy.nan`` if the solver failed. total_time : float or numpy.nan Total time for the OPF calculation, including the modeling, solving, and result assembly. If not provided, this time is set to ``numpy.nan``. reconstruction_mse : float Unavailable if the result is empty and, otherwise, the mean squared error of the reconstructed bus voltages in case of a relaxation and ``numpy.nan`` otherwise. bus : pandas.DataFrame, optional Unavailable if the result is empty and, otherwise, a data frame with the bus result data, indexed by the *bus ID*, which comprises the following columns: ``v``: (``hynet_complex_``) Bus voltage rms phasor (AC) or bus voltage magnitude (DC). ``s_shunt``: (``hynet_complex_``) Shunt apparent power in MVA. The real part constitutes the shunt losses in MW and the *negated* imaginary part constitutes the reactive power *injection*. ``bal_err``: (``hynet_complex_``) Power balance residual in MVA, i.e., the evaluation of the complex-valued power balance equation at the system state. Theoretically, this should be identical to zero, but due to a limited solver accuracy and/or inexactness of the relaxation it is only approximately zero. This residual supports the assessment of solution accuracy and validity. ``dv_bal_p``: (``hynet_float_``) Dual variable or KKT multiplier of the active power balance constraint in $/MW. In case of exactness of the relaxation (or zero duality gap in the QCQP), these dual variables equal the locational marginal prices (LMPs) for active power, cf. [1]_. ``dv_bal_q``: (``hynet_float_``) Dual variable or KKT multiplier of the reactive power balance constraint in $/Mvar. In case of exactness of the relaxation (or zero duality gap in the QCQP), these dual variables equal the locational marginal prices (LMPs) for reactive power, cf. [1]_. ``dv_v_min``: (``hynet_float_``) Dual variable or KKT multiplier of the voltage lower bound in $/p.u.. ``dv_v_max``: (``hynet_float_``) Dual variable or KKT multiplier of the voltage upper bound in $/p.u.. branch : pandas.DataFrame, optional Unavailable if the result is empty and, otherwise, a data frame with the branch result data, indexed by the *branch ID*, which comprises the following columns: ``s_src``: (``hynet_complex_``) Apparent power flow in MVA at the source bus (measured as a flow *into* the branch). ``s_dst``: (``hynet_complex_``) Apparent power flow in MVA at the destination bus (measured as a flow *into* the branch). ``i_src``: (``hynet_complex_``) Current flow in p.u. at the source bus (measured as a flow *into* the branch). ``i_dst``: (``hynet_complex_``) Current flow in p.u. at the destination bus (measured as a flow *into* the branch). ``v_drop``: (``hynet_float_``) Relative voltage magnitude drop from the source bus to the destination bus. ``angle_diff``: (``hynet_float_``) Bus voltage angle difference in degrees between the source and destination bus. ``effective_rating``: (``hynet_float_``) Ampacity in terms of a long-term MVA rating at the *actual* bus voltage. If no rating is available, it is set to ``numpy.nan``. ``rel_err``: (``hynet_float_``) Branch-related relative reconstruction error :math:`\\kappa_k(V^\\star)` as defined in equation (24) in [1]_ in case of a relaxed OPF or ``numpy.nan`` otherwise. ``dv_i_max_src``: (``hynet_float_``) Dual variable or KKT multiplier of the ampacity constraint at the source bus in $/p.u. or ``numpy.nan`` if unavailable. ``dv_i_max_dst``: (``hynet_float_``) Dual variable or KKT multiplier of the ampacity constraint at the destination bus in $/p.u. or ``numpy.nan`` if unavailable. ``dv_angle_min``: (``hynet_float_``) Dual variable or KKT multiplier of the angle difference lower bound constraint or ``numpy.nan`` if unavailable. ``dv_angle_max``: (``hynet_float_``) Dual variable or KKT multiplier of the angle difference upper bound constraint or ``numpy.nan`` if unavailable. ``dv_real_part``: (``hynet_float_``) Dual variable or KKT multiplier of the +/-90 degrees constraint on the angle difference (cf. equation (27) in [2]_) or ``numpy.nan`` if unavailable. ``dv_drop_min``: (``hynet_float_``) Dual variable or KKT multiplier of the voltage drop lower bound constraint or ``numpy.nan`` if unavailable. ``dv_drop_max``: (``hynet_float_``) Dual variable or KKT multiplier of the voltage drop upper bound constraint or ``numpy.nan`` if unavailable. converter : pandas.DataFrame Unavailable if the result is empty and, otherwise, a data frame with the converter result data, indexed by the *converter ID*, which comprises the following columns: ``p_src``: (``hynet_complex_``) Active power flow in MW at the source bus *into the converter*. ``p_dst``: (``hynet_complex_``) Active power flow in MW at the destination bus *into the converter*. ``q_src``: (``hynet_complex_``) Reactive power injection in Mvar at the source bus *into the grid*. ``q_dst``: (``hynet_complex_``) Reactive power injection in Mvar at the destination bus *into the grid*. ``loss_err``: (``hynet_float_``) Loss error in MW due to noncomplementary modes of the converter. ``loss_err_pre``: (``hynet_float_``) Only available if the OPF QCQP was pre-solved to detect and fix the converter modes. Loss error in MW in the pre-solution due to noncomplementary modes of the converter. ``dv_p_fwd_min``: (``hynet_float_``) Dual variable or KKT multiplier of the lower bound on the converter's forward mode active power flow in $/MW or ``numpy.nan`` if unavailable. ``dv_p_fwd_max``: (``hynet_float_``) Dual variable or KKT multiplier of the upper bound on the converter's forward mode active power flow in $/MW or ``numpy.nan`` if unavailable. ``dv_p_bwd_min``: (``hynet_float_``) Dual variable or KKT multiplier of the lower bound on the converter's backward mode active power flow in $/MW or ``numpy.nan`` if unavailable. ``dv_p_bwd_max``: (``hynet_float_``) Dual variable or KKT multiplier of the upper bound on the converter's backward mode active power flow in $/MW or ``numpy.nan`` if unavailable. ``dv_cap_src_q_min``: (``hynet_float_``) Dual variable or KKT multiplier of the reactive power lower bound of the capability region at the source bus in $/Mvar or ``numpy.nan`` if unavailable. ``dv_cap_src_q_max``: (``hynet_float_``) Dual variable or KKT multiplier of the reactive power upper bound of the capability region at the source bus in $/Mvar or ``numpy.nan`` if unavailable. ``dv_cap_dst_q_min``: (``hynet_float_``) Dual variable or KKT multiplier of the reactive power lower bound of the capability region at the destination bus in $/Mvar or ``numpy.nan`` if unavailable. ``dv_cap_dst_q_max``: (``hynet_float_``) Dual variable or KKT multiplier of the reactive power upper bound of the capability region at the destination bus in $/Mvar or ``numpy.nan`` if unavailable. ``dv_cap_src_lt``: (``hynet_float_``) Dual variable or KKT multiplier of the left-top half-space of of the capability region at the source bus in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_src_rt``: (``hynet_float_``) Dual variable or KKT multiplier of the right-top half-space of of the capability region at the source bus in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_src_lb``: (``hynet_float_``) Dual variable or KKT multiplier of the left-bottom half-space of of the capability region at the source bus in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_src_rb``: (``hynet_float_``) Dual variable or KKT multiplier of the right-bottom half-space of of the capability region at the source bus in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_dst_lt``: (``hynet_float_``) Dual variable or KKT multiplier of the left-top half-space of of the capability region at the destination bus in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_dst_rt``: (``hynet_float_``) Dual variable or KKT multiplier of the right-top half-space of of the capability region at the destination bus in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_dst_lb``: (``hynet_float_``) Dual variable or KKT multiplier of the left-bottom half-space of of the capability region at the destination bus in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_dst_rb``: (``hynet_float_``) Dual variable or KKT multiplier of the right-bottom half-space of of the capability region at the destination bus in $/MVA or ``numpy.nan`` if unavailable. injector : pandas.DataFrame Unavailable if the result is empty and, otherwise, a data frame with the injector result data, indexed by the *injector ID*, which comprises the following columns: ``s``: (``hynet_complex_``) Apparent power injection in MVA. ``cost_p``: (``hynet_float_``) Cost of the active power injection in dollars or ``numpy.nan`` if no cost function was provided. ``cost_q``: (``hynet_float_``) Cost of the reactive power injection in dollars or ``numpy.nan`` if no cost function was provided. ``dv_cap_p_min``: (``hynet_float_``) Dual variable or KKT multiplier of the active power lower bound of the capability region in $/MW or ``numpy.nan`` if unavailable. ``dv_cap_q_min``: (``hynet_float_``) Dual variable or KKT multiplier of the reactive power lower bound of the capability region in $/Mvar or ``numpy.nan`` if unavailable. ``dv_cap_p_max``: (``hynet_float_``) Dual variable or KKT multiplier of the active power upper bound of the capability region in $/MW or ``numpy.nan`` if unavailable. ``dv_cap_q_max``: (``hynet_float_``) Dual variable or KKT multiplier of the reactive power upper bound of the capability region in $/Mvar or ``numpy.nan`` if unavailable. ``dv_cap_lt``: (``hynet_float_``) Dual variable or KKT multiplier of the left-top half-space of the capability region in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_rt``: (``hynet_float_``) Dual variable or KKT multiplier of the right-top half-space of the capability region in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_lb``: (``hynet_float_``) Dual variable or KKT multiplier of the left-bottom half-space of the capability region in $/MVA or ``numpy.nan`` if unavailable. ``dv_cap_rb``: (``hynet_float_``) Dual variable or KKT multiplier of the right-bottom half-space of the capability region in $/MVA or ``numpy.nan`` if unavailable. References ---------- .. [1] M. Hotz and W. Utschick, "The Hybrid Transmission Grid Architecture: Benefits in Nodal Pricing," in IEEE Trans. Power Systems, vol. 33, no. 2, pp. 1431-1442, Mar. 2018. .. [2] M. Hotz and W. Utschick, "A Hybrid Transmission Grid Architecture Enabling Efficient Optimal Power Flow," in IEEE Trans. Power Systems, vol. 31, no. 6, pp. 4504-4516, Nov. 2016. """ def __init__(self, model, qcqp_result, total_time=np.nan, qcqp_result_pre=None): """ Create an OPF result object. Parameters ---------- model : hynet.opf.model.OPFModel Model for the processed OPF problem. qcqp_result : hynet.qcqp.result.QCQPResult Solution of the OPF QCQP. total_time : .hynet_float_, optional Total time for solving the OPF. qcqp_result_pre : QCQPResult, optional Pre-solution of the OPF QCQP for converter mode detection. """ super().__init__(model, qcqp_result, total_time=total_time, qcqp_result_pre=qcqp_result_pre) self._result_title = 'Optimal Power Flow' self._unit_dv_bal_p = '$/MWh' self._unit_dv_bal_q = '$/Mvarh' def __repr__(self): """Return a summary of the result.""" t = "" t += self._get_header() t += "|> Data Source " + "-"*63 + "<|\n" t += "|" + " "*78 + "|\n" t += self._get_data_source_summary() t += "|" + " "*78 + "|\n" t += "|> Grid Information " + "-"*58 + "<|\n" t += "|" + " "*78 + "|\n" t += self._get_grid_structure_summary() t += "|" + " "*78 + "|\n" t += self._get_grid_injection_summary() t += "|" + " "*78 + "|\n" if not self.empty: t += "|> Results " + "-"*67 + "<|\n" t += "|" + " "*78 + "|\n" t += self._get_injection_and_loss_summary() t += "| Loss price:{:12.3f} $/MWh{:>13s}Loss penalty:{:14.3f} k$/h{:>4s}\n"\ .format(self.scenario.loss_price, "| ", self.get_dynamic_losses() * self.scenario.loss_price / 1e3, "|") t += "|" + " "*40 + "-+----+-" + " "*30 + "|\n" t += self._get_nodal_and_utilization_summary() t += "|" + " "*78 + "|\n" t += "|> Solution Process " + "-"*58 + "<|\n" t += "|" + " "*78 + "|\n" t += self._get_solver_info() t += self._get_solution_accuracy_info() t += self._get_pathological_price_profile_info() t += "+" + "-"*78 + "+\n" return t def _get_pathological_price_profile_info(self): t = "" if not (self.scenario.verify_hybrid_architecture_conditions(log=None) and self.solver_status == SolverStatus.SOLVED and self.solver.type != SolverType.QCQP and not self.has_valid_power_balance and self.reconstruction_mse >= 1e-8 and config.OPF['pathological_price_profile_info']): return t t += "|" + " "*78 + "|\n" t += "| This scenario exhibits a pathological price profile. Prominent causes are: |\n" t += "| (a) Zero marginal cost injectors: This can induce near-zero LMPs for |\n" t += "| active power. To add loss minimization, see Scenario.loss_price. |\n" t += "| (b) Shortage of inductive reactive power: This can induce negative LMPs |\n" t += "| for reactive power. To add compensation, see Scenario.add_compensation. |\n" t += "| To analyze the cause of inexactness, see hynet.show_power_balance_error. |\n" t += "| In some cases, Scenario.set_minimum_series_resistance may be supportive. |\n" return t