[docs]
class SimulationEMT:
"""
PowerFactory EMT Simulation Class.
author: Ilya Burlakin, Elisabeth Scheiner
"""
def __init__(self, app):
self.app = app
self.inc = None
self.sim = None
self.events = None
self.results = None
self.inc_settings_are_set = False
self.sim_settings_are_set = False
[docs]
def init_emt(self, inc: bool = True, sim: bool = True, events: bool = True,
results: bool = True) -> None:
"""
Initialize EMT simulation.
TODO: Handling for creating new result files.
TODO: Handling if Language is set to German.
:param inc: decision if the initial conditions of the emt \
model have to be set
:type inc: bool
:param sim: decision if the simulation parameters of the \
emt model have to be set
:type sim: bool
:param events: decision if there are events in the power \
system simulation
:type events: bool
:param results: decision if the result data structure will \
be stored or not
:type results: bool
"""
if inc:
self.inc = self.app.GetFromStudyCase('ComInc')
if sim:
self.sim = self.app.GetFromStudyCase('ComSim')
if events:
self.events = self.app.GetFromStudyCase(
"Simulationsereignisse/Fehler.IntEvt"
)
if results:
self.results = self.app.GetFromStudyCase(
"Alle Berechnungsarten.ElmRes"
)
[docs]
def clear_events(self) -> None:
"""
Clear list of simulation events.
"""
# check if the data structure of events is not empty
if self.events is not None:
# iterate over all events
for element in self.events.GetContents():
# delete each event
element.Delete()
[docs]
def clear_results(self) -> None:
"""
Clear simulation results.
"""
# check if the result data structure is not empty
if self.results is not None:
# iterate over each result element
for element in self.results.GetContents():
# delete the result element
element.Delete()
[docs]
def clear(self, events: bool = True, results: bool = True) -> None:
"""
Clear simulation objects.
TODO: define events and results if events, results is None
:param events: decision if the power system simulation \
contains events
:type events: bool
:param results: decision if the power system simulation \
results will be cleared
:type results: bool
"""
if events and self.events is not None:
self.clear_events()
if results and self.results is not None:
self.clear_results()
"""
Short-Circuit Event Handling
"""
[docs]
@staticmethod
def shc_settings(
event: object, event_time: float, fault_location: float,
fault_type: int, fault_impedance_r: float, fault_impedance_x: float
) -> None:
"""
Set Short-Circuit event settings.
TODO: Extend fault settings and put parameters in dict, \
check if obj is bus (fault_location)
TODO: check type of event object
:param event: powerfactory event object to be modified
:type event: object
:param event_time: time at which the event occurs
:type event_time: float
:param fault_location: relative position of the fault \
location on a powersystem object (e.g. overheadline)
:type fault_location: float
:param fault_type: integer representing the fault type \
of the simulated short circuit. Possible entries: \
0: 3ph, 1: 2phwoG, 2: 1phG, 3: 2phG, 4 clear fault
:type fault_type: int
:param fault_impedance_r: resistance of the power system \
fault
:type fault_impedance_r: float
:param fault_impedance_x: reactance of the power system \
fault
:type fault_impedance_x: float
"""
event.time = event_time
event.shcLocation = fault_location
event.i_shc = fault_type
# Impedance is set to zero since the input option for
# resistance and reactance is used
event.Zfaultlnp = 0
event.R_f = fault_impedance_r
event.X_f = fault_impedance_x
[docs]
def add_shc_event(
self, obj: object, event_time: float = 0.0,
fault_location: float = 50.0, fault_type: int = 0,
fault_impedance_r: float = 0.0, fault_impedance_x: float = 0.0
) -> None:
"""
Add Short-Circuit Event and set fault settings.
TODO: Handling if object is a Load.
TODO: check type of obj
:param obj: powerfactory object to attach the shc event to
:type obj: object
:param event_time: time at which the event occurs
:type event_time: float
:param fault_location: relative position of the fault \
location on a powersystem object (e.g. overheadline)
:type fault_location: float
:param fault_type: integer representing the fault type \
of the simulated short circuit. Possible entries: \
0: 3ph, 1: 2phwoG, 2: 1phG, 3: 2phG, 4: clear fault
:type fault_type: int
:param fault_impedance_r: resistance of the power system \
fault
:type fault_impedance_r: float
:param fault_impedance_x: reactance of the power system \
fault
:type fault_impedance_x: float
"""
# check if the initialization of the emt simulation already has
# been done
if self.events is None:
# if not initialize the emt simulation
self.init_emt(inc=False, sim=False, events=True, results=False)
# create event
event = self.events.CreateObject("EvtShc", obj.loc_name)
# set target object
event.p_target = obj
# set the event settings of the previously created event object
self.shc_settings(
event=event,
event_time=event_time,
fault_location=fault_location,
fault_type=fault_type,
fault_impedance_r=fault_impedance_r,
fault_impedance_x=fault_impedance_x
)
"""
Load Event Handling
"""
#
[docs]
@staticmethod
def load_event_settings(
event: object, event_time: float, event_type: int, load_dp: float,
load_dq: float
) -> None:
"""
Set Single Load event settings.
TODO: Extend fault settings and put parameters in dict, \
add ramp event.
TODO: check event type
:param event: powerfactory event object to be modified
:type event: object
:param event_time: time at which the event occurs
:type event_time: float
:param event_type: integer representing the event type \
of the simulated load change. Possible entries: \
0: Step, 1: Ramp
:type event_type: int
:param load_dp: relative change of the active power of the \
by the event effected load
:type load_dp: float
:param load_dq: relative change of the reactive power of \
the by the event effected load
:type load_dq: float
"""
event.time = event_time
event.iopt_type = event_type
event.dP = load_dp
event.dQ = load_dq
[docs]
def add_load_event(
self, obj: object, event_time: float = 0.0, event_type: int = 0,
load_dp: float = 0.0, load_dq: float = 0.0,
) -> None:
"""
Add Load Event and set event settings.
TODO: Handling if object is not a Load.
TODO: check type of obj
:param obj: powerfactory object to attach the load event to
:type obj: object
:param event_time: time at which the event occurs
:type event_time: float
:param event_time: time at which the event occurs
:type event_time: float
:param event_type: integer representing the event type \
of the simulated load change. Possible entries: \
0: Step, 1: Ramp
:type event_type: int
:param load_dp: relative change of the active power of the \
by the event effected load
:type load_dp: float
:param load_dq: relative change of the reactive power of \
the by the event effected load
:type load_dq: float
"""
# check if the initialization of the emt simulation already has
# been done
if self.events is None:
# if not initialize the emt simulation
self.init_emt(inc=False, sim=False, events=True, results=False)
# create event
event = self.events.CreateObject("EvtLod", obj.loc_name)
# set target object
event.p_target = obj
# set the event settings of the previously created event object
self.load_event_settings(
event=event,
event_time=event_time,
event_type=event_type,
load_dp=load_dp,
load_dq=load_dq
)
"""
Results Handling
"""
[docs]
def add_variables(self, objects: list[object],
attributes: list[str]) -> None:
"""
Add Variables to result file
:param objects: List of power factory objects to be \
considered in the creation of the result file
:type objects: list[object]
:param attributes: List of attributes of the entered power \
factory objects to be attached to the result file
:type attributes: list[str]
"""
# check if the initialization of the emt simulation already has
# been done
if self.results is None:
# if not initialize the emt simulation
self.init_emt(inc=False, sim=False, events=False, results=True)
# check if objects and attributes are lists.
if not isinstance(objects, list):
objects = [objects]
if not isinstance(attributes, list):
attributes = [attributes]
# add attributes as variables to the results object of the emt
# simulation
for obj in objects:
for attr in attributes:
self.results.AddVariable(obj, attr)
"""
Initial Condition Settings
"""
[docs]
def inc_settings(
self, verify_inc: bool = True, step_size: float = 0.01,
start_time: float = -0.1, synch: bool = True
) -> None:
"""
Set initial condition settings of the emt simulation.
TODO: Extend settings.
TODO: Add Loadflow
:param verify_inc: bool indicating if the inserted \
initial conditions shall be checked or not
:type verify_inc: bool
:param step_size: float holding the temporal resolution of \
time emt simulation to be run later
:type step_size: float
:param start_time: start point of the simulation in seconds
:type start_time: float
:param synch: bool indicating if the simulation results \
shall be recorded synchronized
:type synch: bool
"""
# check if the initialization of the emt simulation already has
# been done
if self.inc is None:
# if not initialize the emt simulation
self.init_emt(inc=True, sim=False, events=False, results=False)
# define the simulation type possible entries rms & ins
self.inc.iopt_sim = 'ins'
# check if the emt simulation event data structure already
# exists
if self.events is None:
# if not initialize the emt simulation to create the events
# data structure
self.init_emt(inc=False, sim=False, events=True, results=False)
self.inc.p_event = self.events
# check if the emt simulation results data structure already
# exists
if self.results is None:
# if not initialize the emt simulation to create the
# results data structure
self.init_emt(inc=False, sim=False, events=False, results=True)
self.inc.p_resvar = self.results
# further options
self.inc.iopt_show = verify_inc # Verify initial conditions
self.inc.dtgrd = step_size # Integration step size
self.inc.tstart = start_time # Start time
if synch:
self.inc.iopt_sync = synch # Enforced synchronization
self.inc.syncperiod = step_size # Sync Period
self.inc.ciopt_sample = 2 # Record results after every simulation step
else:
self.inc.iopt_sync = synch
self.inc.ciopt_sample = 0
self.inc_settings_are_set = True
"""
Simulation Settings
"""
[docs]
def sim_settings(self, stop_time: float = 5.0) -> None:
"""
Set Simulation settings.
:param stop_time: End time of the power system simulation \
in seconds
:type stop_time: float
"""
# check if the initialization of the emt simulation already has
# been done
if self.sim is None:
# if not initialize the emt simulation to create the sim
# data structure
self.init_emt(inc=False, sim=True, events=False, results=False)
# Set the simulation end time in seconds
self.sim.tstop = stop_time
self.sim_settings_are_set = True
"""
Run Simulation
"""
[docs]
def run(self) -> int:
"""
Run RMS Simulation. Return 1 if RMS Simulation is
successful.
TODO: Add Error Handling, if inc/sim are not defined or
init-errors.
:return: **-** (int) - integer representing the exit \
status of the simulation run
"""
run_inc = self.inc.Execute() # inc_run=0 if success
run_sim = self.sim.Execute() # inc_sim=0 if success
return not run_inc and not run_sim
"""
Export simulation results
"""
[docs]
def export_to_csv(self, file_name: str) -> bool:
"""
Export simulation results as csv files. Return True if
successful.
:param file_name: Name of the csv file created within this \
method
:type file_name: str
:return: **-** (bool) - bool representing the exit \
status of the result file creation process
"""
# get the result structure from Study Case
export = self.app.GetFromStudyCase("ComRes")
export.pResult = self.results # export from
# set the export type to csv file
export.iopt_exp = 6
# enter the csv file name
export.f_name = file_name
# use the csv file seperator set on the user's operating system
export.iopt_sep = 0
# change the col seperator to semicolon
export.col_Sep = ';'
# change the decimal seperator to point
export.dec_Sep = '.'
export.iopt_csel = 0 # export all variables
export.iopt_tsel = 0 # 0 deactivate export user def. interval (adv opt)
export.resultobj = [None]
return not export.Execute()