from typing import List, Union
import pandas as pd
from ._exceptions import PowerWorldError
from ._enums import YesNo, SolverMethod, FilterKeyword, KeyFieldType, FilterType, format_filter
[docs]
class PowerflowMixin:
[docs]
def SolvePowerFlow(self, SolMethod: Union[SolverMethod, str] = SolverMethod.RECTNEWT) -> None:
"""Performs a single power flow solution.
If the DC method is selected, the case is switched to DC power flow mode.
If one of the other AC methods is selected, the case is switched to AC power
flow mode. It may be difficult to solve a case with an AC power flow method
once the case has been switched to DC power flow mode.
Parameters
----------
SolMethod : Union[SolverMethod, str], optional
The solution method to use for the power flow calculation:
- ``RECTNEWT``: Rectangular Newton-Raphson (default)
- ``POLARNEWT``: Polar Newton-Raphson
- ``GAUSSSEIDEL``: Gauss-Seidel
- ``FASTDEC``: Fast Decoupled
- ``ROBUST``: Attempts the robust solution process
- ``DC``: DC power flow (switches case to DC mode)
Returns
-------
None
Raises
------
PowerWorldError
If the SimAuto call fails or the power flow does not converge.
"""
method = SolMethod.value if isinstance(SolMethod, SolverMethod) else SolMethod.upper()
return self._run_script("SolvePowerFlow", method)
[docs]
def ClearPowerFlowSolutionAidValues(self):
"""Clears internal power flow solution aid values.
PowerWorld Simulator maintains several internal flags that track which
branches are closed or opened, as well as information to help estimate
the generation change needed after making changes to load or generation.
This information relates to angle smoothing and generator MW estimation
features of the power flow solution.
Typically, this information aids in getting successful power flow solutions.
However, in some circumstances you may be using an AUX file to edit
information you know is good and would not want PowerWorld to modify
the initial bus voltage and angle nor the generator MW outputs before
a solution is attempted. Call this command to clear all internally stored
information so PowerWorld does not perform these pre-processing steps.
"""
self._run_script("ClearPowerFlowSolutionAidValues")
[docs]
def ResetToFlatStart(self):
"""Resets all bus voltages to 1.0 per unit and angles to 0.
This is a wrapper for the ``ResetToFlatStart`` script command.
"""
self._run_script("ResetToFlatStart")
[docs]
def SetMVATolerance(self, tol: float = 0.1) -> None:
"""Sets the MVA Tolerance for Newton-Raphson convergence.
Parameters
----------
tol : float, optional
The MVA tolerance value. Defaults to 0.1.
"""
self.ChangeParametersSingleElement("Sim_Solution_Options", ["ConvergenceTol:2"], [str(tol)])
[docs]
def SetDoOneIteration(self, enable: bool = True) -> None:
"""Sets the 'Do One Iteration' power flow option.
Parameters
----------
enable : bool, optional
If True, power flow will only perform one iteration. Defaults to True.
"""
value = YesNo.from_bool(enable)
self.ChangeParametersSingleElement("Sim_Solution_Options", ["DoOneIteration"], [value])
[docs]
def SetInnerLoopCheckMVars(self, enable: bool = True) -> None:
"""Sets the 'Check Mvar Limits Immediately' power flow option.
Parameters
----------
enable : bool, optional
If True, the inner loop of the power flow will check Mvar limits
before proceeding to the outer loop. Defaults to True.
"""
value = YesNo.from_bool(enable)
self.ChangeParametersSingleElement("Sim_Solution_Options", ["ChkVars"], [value])
[docs]
def GetMinPUVoltage(self) -> float:
"""Gets the minimum per-unit voltage magnitude in the case.
Returns
-------
float
The minimum p.u. voltage.
"""
s = self.GetParametersSingleElement("PWCaseInformation", ["BusPUVolt:1"], [""])
return float(s.iloc[0])
[docs]
def UpdateIslandsAndBusStatus(self):
"""Updates islands and bus status without requiring a power flow solution.
Changes to branch and generator status impact islands and whether or not
buses are connected. Islands and bus status are always updated at the
beginning of a power flow solution if necessary, but this command makes
it convenient to update this information without requiring a power flow
solution.
"""
return self._run_script("UpdateIslandsAndBusStatus")
[docs]
def ZeroOutMismatches(self, object_type: str = "BUSSHUNT"):
"""Forces mismatches to zero by changing bus shunts or loads.
Bus shunts or loads are changed at each bus that has a mismatch greater
than the MVA convergence tolerance so that the mismatch at that bus is
forced to zero.
Parameters
----------
object_type : str, optional
How to adjust the mismatch:
- ``BUSSHUNT``: Adjust Bus Shunt fields at each bus (default)
- ``LOAD``: Add a new load at each bus with mismatch (ID starting with Q1)
"""
return self._run_script("ZeroOutMismatches", object_type)
[docs]
def ConditionVoltagePockets(self, voltage_threshold: float, angle_threshold: float, filter_name: FilterType = FilterKeyword.ALL):
"""Finds pockets of buses with bad initial voltage estimates and conditions them.
Identifies pockets of buses bounded by branches that meet the condition that
the absolute value of the voltage difference across the branch is greater than
``voltage_threshold`` or the absolute value of the angle difference is greater
than ``angle_threshold``. The tool then estimates better voltages for buses
in each pocket using known good values outside the pocket.
Parameters
----------
voltage_threshold : float
Per-unit voltage difference (absolute value) threshold for identifying
branches that bound voltage pockets.
angle_threshold : float
Angle difference in degrees (absolute value) threshold for identifying
branches that bound voltage pockets.
filter_name : str, optional
Filter specifying which branches to check. Defaults to "ALL".
"""
filt = format_filter(filter_name)
return self._run_script("ConditionVoltagePockets", voltage_threshold, angle_threshold, filt)
[docs]
def EstimateVoltages(self, filter_name: str):
"""Estimates voltages and angles at buses meeting the filter.
Parameters
----------
filter_name : str
Filter specifying which buses should have their voltages estimated.
"""
filt = format_filter(filter_name)
return self._run_script("EstimateVoltages", filt)
[docs]
def GenForceLDC_RCC(self, filter_name: str = ""):
"""Forces generators onto line drop / reactive current compensation.
Parameters
----------
filter_name : str, optional
Filter specifying which generators to force. Defaults to all generators.
"""
return self._run_script("GenForceLDC_RCC", f'"{filter_name}"')
[docs]
def SaveGenLimitStatusAction(self, filename: str):
"""Saves Mvar information about generators to a text file.
Parameters
----------
filename : str
Path to the output text file.
"""
return self._run_script("SaveGenLimitStatusAction", f'"{filename}"')
[docs]
def DiffCaseClearBase(self):
"""Clears the base case for the difference case comparison abilities of Simulator."""
return self._run_script("DiffCaseClearBase")
[docs]
def DiffCaseSetAsBase(self):
"""Sets the present case as the base case for difference case comparison."""
return self._run_script("DiffCaseSetAsBase")
[docs]
def DiffCaseKeyType(self, key_type: Union[KeyFieldType, str]):
"""Changes the key type used when comparing fields in difference case mode.
Parameters
----------
key_type : str
Key type to use: ``PRIMARY``, ``SECONDARY``, or ``LABEL``.
"""
return self._run_script("DiffCaseKeyType", key_type)
[docs]
def DiffCaseShowPresentAndBase(self, show: bool):
"""Toggles 'Show Present|Base in Difference and Change Mode'."""
yn = YesNo.from_bool(show)
return self._run_script("DiffCaseShowPresentAndBase", yn)
[docs]
def DiffCaseMode(self, mode: str):
"""Changes the mode for difference case comparison.
Parameters
----------
mode : str
Display mode: ``PRESENT``, ``BASE``, ``DIFFERENCE``, or ``CHANGE``.
"""
return self._run_script("DiffCaseMode", mode)
[docs]
def DiffCaseRefresh(self):
"""Refreshes the linking between the base case and the present case.
Call this before saving data that identifies objects as being added or
removed, especially if any topological differences have been made that
affect the comparison.
"""
return self._run_script("DiffCaseRefresh")
[docs]
def DiffCaseWriteCompleteModel(self, filename: str, append: bool = False, save_added: bool = True, save_removed: bool = True, save_both: bool = True, key_fields: Union[KeyFieldType, str] = KeyFieldType.PRIMARY, export_format: str = "", use_area_zone: bool = False, use_data_maintainer: bool = False, assume_base_meet: bool = True, include_clear_pf_aids: bool = True, delete_branches_flip: bool = False):
"""Creates an auxiliary file with difference case comparison information.
Creates an auxiliary file containing information about objects that have been
added or removed when comparing the present case to the base case. Fields
that have changed for objects that exist in both cases can also be written.
This auxiliary file can then be used to apply these same changes to other cases.
Parameters
----------
filename : str
Name of the auxiliary file to create.
append : bool, optional
If True, append to existing file. Defaults to False.
save_added : bool, optional
If True, save added objects to the file. Defaults to True.
save_removed : bool, optional
If True, save removed objects to the file. Defaults to True.
save_both : bool, optional
If True, save changed fields for objects in both cases. Defaults to True.
key_fields : str, optional
Key field identifiers to use: ``PRIMARY`` or ``SECONDARY``. Defaults to "PRIMARY".
export_format : str, optional
Name of Auxiliary File Export Format Description to use. Defaults to "".
use_area_zone : bool, optional
If True, use Area/Zone/Owner filter for including objects. Defaults to False.
use_data_maintainer : bool, optional
If True, use Data Maintainer filter. Defaults to False.
assume_base_meet : bool, optional
If True, assume base case areas/zones/owners meet filters. Defaults to True.
include_clear_pf_aids : bool, optional
If True, include ClearPowerFlowSolutionAidValues command. Defaults to True.
delete_branches_flip : bool, optional
If True, treat branches with flipped bus order as removed and added. Defaults to False.
"""
app = YesNo.from_bool(append)
sa = YesNo.from_bool(save_added)
sr = YesNo.from_bool(save_removed)
sb = YesNo.from_bool(save_both)
uaz = YesNo.from_bool(use_area_zone)
udm = YesNo.from_bool(use_data_maintainer)
abm = YesNo.from_bool(assume_base_meet)
icp = YesNo.from_bool(include_clear_pf_aids)
dbf = YesNo.from_bool(delete_branches_flip)
return self._run_script("DiffCaseWriteCompleteModel", f'"{filename}"', app, sa, sr, sb, key_fields, f'"{export_format}"', uaz, udm, abm, icp, dbf)
[docs]
def DiffCaseWriteBothEPC(self, filename: str, ge_file_type: str = "GE19", use_area_zone: bool = False, base_area_zone_meet: bool = True, append: bool = False, export_format: str = "", use_data_maintainer: bool = False):
"""Saves elements that exist in both base and present cases in GE EPC format.
Parameters
----------
filename : str
Name of the EPC file to create.
ge_file_type : str, optional
GE EPC file version (e.g., "GE18", "GE19", "PTI33"). Defaults to "GE19".
use_area_zone : bool, optional
If True, use Area/Zone/Owner filter. Defaults to False.
base_area_zone_meet : bool, optional
If True, assume base case meets filters. Defaults to True.
append : bool, optional
If True, append to existing file. Defaults to False.
export_format : str, optional
Export format name. Defaults to "".
use_data_maintainer : bool, optional
If True, use Data Maintainer filter. Defaults to False.
"""
uaz = YesNo.from_bool(use_area_zone)
baz = YesNo.from_bool(base_area_zone_meet)
app = YesNo.from_bool(append)
udm = YesNo.from_bool(use_data_maintainer)
return self._run_script("DiffCaseWriteBothEPC", f'"{filename}"', ge_file_type, uaz, baz, app, f'"{export_format}"', udm)
[docs]
def DiffCaseWriteNewEPC(self, filename: str, ge_file_type: str = "GE19", use_area_zone: bool = False, base_area_zone_meet: bool = True, append: bool = False, use_data_maintainer: bool = False):
"""Saves elements that are new (added) in GE EPC format.
Parameters
----------
filename : str
Name of the EPC file to create.
ge_file_type : str, optional
GE EPC file version (e.g., "GE18", "GE19", "PTI33"). Defaults to "GE19".
use_area_zone : bool, optional
If True, use Area/Zone/Owner filter. Defaults to False.
base_area_zone_meet : bool, optional
If True, assume base case meets filters. Defaults to True.
append : bool, optional
If True, append to existing file. Defaults to False.
use_data_maintainer : bool, optional
If True, use Data Maintainer filter. Defaults to False.
"""
uaz = YesNo.from_bool(use_area_zone)
baz = YesNo.from_bool(base_area_zone_meet)
app = YesNo.from_bool(append)
udm = YesNo.from_bool(use_data_maintainer)
return self._run_script("DiffCaseWriteNewEPC", f'"{filename}"', ge_file_type, uaz, baz, app, udm)
[docs]
def DiffCaseWriteRemovedEPC(self, filename: str, ge_file_type: str = "GE19", use_area_zone: bool = False, base_area_zone_meet: bool = True, append: bool = False, use_data_maintainer: bool = False):
"""Saves elements that were removed in GE EPC format.
Parameters
----------
filename : str
Name of the EPC file to create.
ge_file_type : str, optional
GE EPC file version (e.g., "GE18", "GE19", "PTI33"). Defaults to "GE19".
use_area_zone : bool, optional
If True, use Area/Zone/Owner filter. Defaults to False.
base_area_zone_meet : bool, optional
If True, assume base case meets filters. Defaults to True.
append : bool, optional
If True, append to existing file. Defaults to False.
use_data_maintainer : bool, optional
If True, use Data Maintainer filter. Defaults to False.
"""
uaz = YesNo.from_bool(use_area_zone)
baz = YesNo.from_bool(base_area_zone_meet)
app = YesNo.from_bool(append)
udm = YesNo.from_bool(use_data_maintainer)
return self._run_script("DiffCaseWriteRemovedEPC", f'"{filename}"', ge_file_type, uaz, baz, app, udm)
[docs]
def DoCTGAction(self, action: str):
"""Applies a contingency action without the full contingency analysis framework.
Parameters
----------
action : str
The contingency action string to execute.
"""
return self._run_script("DoCTGAction", action)
[docs]
def InterfacesCalculatePostCTGMWFlows(self):
"""Updates Interface MW Flow fields on Contingent Interfaces.
Calculates the post-contingency MW flows for interfaces that have
contingent elements defined.
"""
return self._run_script("InterfacesCalculatePostCTGMWFlows")
[docs]
def VoltageConditioning(self):
"""Performs voltage conditioning based on the Voltage Conditioning tool options.
Uses the configured Voltage Conditioning options to improve initial voltage
estimates throughout the network, which can help power flow convergence.
"""
return self._run_script("VoltageConditioning")
[docs]
def SaveState(self) -> None:
"""Saves the current state of the PowerWorld case.
This creates an unnamed snapshot of the case that can be restored later
using `LoadState`.
"""
return self._com_call("SaveState")
[docs]
def LoadState(self) -> None:
"""Loads the last saved state of the PowerWorld case."""
return self._com_call("LoadState")