# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
from __future__ import annotations
from typing import Union, Callable, List
import numpy as np
from VeraGridEngine.DataStructures.numerical_circuit import NumericalCircuit
from VeraGridEngine.Simulations.ContingencyAnalysis.contingency_analysis_results import ContingencyAnalysisResults
from VeraGridEngine.Simulations.PowerFlow.power_flow_worker import multi_island_pf_nc
from VeraGridEngine.Simulations.LinearFactors.linear_analysis import LinearAnalysis, LinearMultiContingencies
from VeraGridEngine.Simulations.ContingencyAnalysis.contingency_analysis_options import ContingencyAnalysisOptions
from VeraGridEngine.basic_structures import Logger, IntVec, StrVec
[docs]
def nonlinear_contingency_analysis(nc: NumericalCircuit,
options: ContingencyAnalysisOptions,
linear_multiple_contingencies: LinearMultiContingencies,
area_names: StrVec | List[str],
bus_area_indices: IntVec,
F: IntVec,
T: IntVec,
report_text: Callable[[str], None] | None,
report_progress2: Callable[[int, int], None] | None,
is_cancel: Callable[[], bool] | None,
t_idx: Union[None, int] = None,
t_prob: float = 1.0,
logger: Logger | None = None, ) -> ContingencyAnalysisResults:
"""
Run a contingency analysis using the power flow options
:param nc: NumericalCircuit
:param options: ContingencyAnalysisOptions
:param linear_multiple_contingencies: LinearMultiContingencies
:param area_names:
:param bus_area_indices:
:param F:
:param T:
:param report_text:
:param report_progress2;
:param is_cancel:
:param t_idx: time index, if None the snapshot is used
:param t_prob: probability of te time
:param logger: logger instance
:return: returns the results
"""
if logger is None:
logger = Logger()
# declare the results
results = ContingencyAnalysisResults(
ncon=len(linear_multiple_contingencies.contingency_groups_used),
nbr=nc.nbr,
nbus=nc.nbus,
branch_names=nc.passive_branch_data.names,
bus_names=nc.bus_data.names,
bus_types=nc.bus_data.bus_types,
con_names=linear_multiple_contingencies.get_contingency_group_names()
)
# get contingency groups dictionary
mon_idx = nc.passive_branch_data.get_monitor_enabled_indices()
# run 0
base_res = multi_island_pf_nc(nc=nc, options=options.pf_options)
results.Sf_base = base_res.Sf
if options.use_srap:
# we need the PTDF for this
linear_analysis = LinearAnalysis(nc=nc,
distributed_slack=options.lin_options.distribute_slack,
correct_values=options.lin_options.correct_values)
linear_multiple_contingencies.compute(lin=linear_analysis,
ptdf_threshold=options.lin_options.ptdf_threshold,
lodf_threshold=options.lin_options.lodf_threshold)
PTDF = linear_analysis.PTDF
else:
PTDF = None
available_power = nc.generator_data.get_injections_per_bus().real
# for each contingency group
for ic, contingency_group in enumerate(linear_multiple_contingencies.contingency_groups_used):
# get the group's contingencies
contingencies = linear_multiple_contingencies.contingency_group_dict[contingency_group.idtag]
# set the status
nc.set_con_or_ra_status(contingencies)
# report progress
if report_text is not None:
report_text(f'Contingency group: {contingency_group.name}')
if report_progress2 is not None:
report_progress2(ic, len(linear_multiple_contingencies.contingency_groups_used) * 100)
# run
con_pf_res = multi_island_pf_nc(nc=nc,
options=options.pf_options,
V_guess=base_res.voltage,
logger=logger)
results.Sf[ic, :] = con_pf_res.Sf
results.Sbus[ic, :] = con_pf_res.Sbus
results.loading[ic, :] = con_pf_res.loading
results.voltage[ic, :] = con_pf_res.voltage
multi_contingency = linear_multiple_contingencies.multi_contingencies[ic] if options.use_srap else None
# NOTE: this is accounted for to be in the normal rate base in the analyze method
contingency_loadings = np.abs(con_pf_res.Sf / (nc.passive_branch_data.rates + 1e-9))
results.report.analyze(t=t_idx,
t_prob=t_prob,
mon_idx=mon_idx,
nc=nc,
base_flow=np.abs(base_res.Sf),
base_loading=np.abs(base_res.loading),
contingency_flows=np.abs(con_pf_res.Sf),
contingency_loadings=contingency_loadings,
contingency_group_idx=ic,
contingency_group=contingency_group,
using_srap=options.use_srap,
srap_ratings=nc.passive_branch_data.protection_rates,
srap_max_power=options.srap_max_power,
srap_deadband=options.srap_deadband,
contingency_deadband=options.contingency_deadband,
multi_contingency=multi_contingency,
PTDF=PTDF,
available_power=available_power,
srap_used_power=results.srap_used_power,
F=F,
T=T,
bus_area_indices=bus_area_indices,
area_names=area_names,
top_n=options.srap_top_n)
# set the status
nc.set_con_or_ra_status(contingencies, revert=True)
if is_cancel is not None:
if is_cancel():
return results
return results