From 5e3c9c07aa802d45bb7d1495436bde468a44e229 Mon Sep 17 00:00:00 2001 From: Fuhong Xie Date: Fri, 18 Oct 2024 09:30:00 -0600 Subject: [PATCH 01/14] bugfix --- pypsse/cli/explore.py | 4 ++ pypsse/cli/run.py | 7 ++- pypsse/helics_interface.py | 40 +++++++++++++++-- pypsse/modes/abstract_mode.py | 5 +++ pypsse/modes/dynamic.py | 4 +- pypsse/modes/snap.py | 7 +-- pypsse/simulation_controller.py | 4 +- pypsse/simulator.py | 46 ++++++++++++++++--- pypsse/utils/dynamic_utils.py | 80 ++++++++++++++++++++------------- 9 files changed, 150 insertions(+), 47 deletions(-) diff --git a/pypsse/cli/explore.py b/pypsse/cli/explore.py index fdb5984..d8c3867 100644 --- a/pypsse/cli/explore.py +++ b/pypsse/cli/explore.py @@ -107,6 +107,10 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load, } results = x.sim.read_subsystems(quantities, buses) + # print(results.keys()) + print(results["LOAD_P"]) + # quit() + had_comp_models = False if "Loads_FmA" in results: had_comp_models = True diff --git a/pypsse/cli/run.py b/pypsse/cli/run.py index 1a67dcf..04df06d 100644 --- a/pypsse/cli/run.py +++ b/pypsse/cli/run.py @@ -7,6 +7,7 @@ from loguru import logger import click import toml +import os from pypsse.models import SimulationSettings from pypsse.common import SIMULATION_SETTINGS_FILENAME @@ -34,11 +35,15 @@ def run(project_path, simulations_file=None): simulation_settiings = toml.load(file_path) simulation_settiings = SimulationSettings(**simulation_settiings) - + if simulation_settiings.log.clear_old_log_file: + log_path = Path(project_path) / "Logs" / "pypsse.log" + if os.path.exists(log_path): + os.remove(log_path) logger.level(simulation_settiings.log.logging_level.value) if simulation_settiings.log.log_to_external_file: log_path = Path(project_path) / "Logs" / "pypsse.log" logger.add(log_path) + x = Simulator.from_setting_files(file_path) diff --git a/pypsse/helics_interface.py b/pypsse/helics_interface.py index f668bc8..0abcb3d 100644 --- a/pypsse/helics_interface.py +++ b/pypsse/helics_interface.py @@ -1,5 +1,6 @@ import ast - +import os +import time import helics as h import pandas as pd from loguru import logger @@ -253,18 +254,26 @@ def request_time(self, _) -> (bool, float): bool: flag for iteration requrirement (rerun same time step) float: current helics time in seconds """ - + r_seconds = self.sim.get_total_seconds() # - self._dss_solver.GetStepResolutionSeconds() + logger.info(f"Time requested: {r_seconds}") + # logger.info(f"self.settings.simulation.simulation_step_resolution.total_seconds(): {self.settings.simulation.simulation_step_resolution.total_seconds()}") + # os.system('PAUSE') if self.sim.get_time() not in self.all_sub_results: self.all_sub_results[self.sim.get_time()] = {} self.all_pub_results[self.sim.get_time()] = {} if not self.settings.helics.iterative_mode: + # logger.info(f"not self.settings.helics.iterative_mode") + # os.system("PAUSE") while self.c_seconds < r_seconds: self.c_seconds = h.helicsFederateRequestTime(self.psse_federate, r_seconds) logger.info(f"Time requested: {r_seconds} - time granted: {self.c_seconds} ") + # os.system('PAUSE') return True, self.c_seconds else: + # logger.info(f"self.settings.helics.iterative_mode") + # os.system("PAUSE") itr = 0 while True: @@ -375,11 +384,18 @@ def subscribe(self) -> dict: for sub_tag, sub_data in self.subscriptions.items(): if isinstance(sub_data["property"], str): sub_data["value"] = h.helicsInputGetDouble(sub_data["subscription"]) + # print(f"##################################################") + # print(f"##################################################") + # print(f"sub_data is {sub_data}") self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][ sub_data["property"] ] = (sub_data["value"], sub_data["scaler"]) elif isinstance(sub_data["property"], list): sub_data["value"] = h.helicsInputGetVector(sub_data["subscription"]) + # print(f"##################################################") + # print(f"##################################################") + # print(f"##################################################") + # print(f"sub_data is {sub_data}") if isinstance(sub_data["value"], list) and len(sub_data["value"]) == len(sub_data["property"]): for i, p in enumerate(sub_data["property"]): self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][p] = ( @@ -395,28 +411,46 @@ def subscribe(self) -> dict: sub_data["dStates"].insert(0, sub_data["dStates"].pop()) all_values = {} for b, b_info in self.psse_dict.items(): + # print(f"##################################################") + # print(f"b is {b}") + # print(f"b_info is {b_info}") for t, t_info in b_info.items(): for i, v_dict in t_info.items(): values = {} j = 0 + # print(f"##################################################") + # print(f"i is {i}") + # print(f"v_dict is {v_dict}") for p, v_raw in v_dict.items(): if isinstance(v_raw, tuple): + # print(f"##################################################") + # print(f"v_raw is {v_raw}") v, scale = v_raw all_values[f"{t}.{b}.{i}.{p}"] = v if isinstance(p, str): ppty = f"realar{PROFILE_VALIDATION[t].index(p) + 1}" values[ppty] = v * scale + # if isinstance(scale, (float, int)): + # values[ppty] = v * scale + # else: + # values[ppty] = v + elif isinstance(p, list): for _, ppt in enumerate(p): ppty = f"realar{PROFILE_VALIDATION[t].index(ppt) + 1}" values[ppty] = v * scale + # if isinstance(scale, (float, int)): + # values[ppty] = v * scale + # else: + # values[ppty] = v j += 1 - is_empty = [0 if not vx else 1 for vx in values.values()] + logger.debug(f"current HELICE time: {self.c_seconds}") if ( sum(is_empty) != 0 and sum(values.values()) < VALUE_UPDATE_BOUND and sum(values.values()) > -VALUE_UPDATE_BOUND + and self.c_seconds > 0.02 ): self.sim.update_object(t, b, i, values) logger.debug(f"{t}.{b}.{i} = {values}") diff --git a/pypsse/modes/abstract_mode.py b/pypsse/modes/abstract_mode.py index 9723b38..a73d5b5 100644 --- a/pypsse/modes/abstract_mode.py +++ b/pypsse/modes/abstract_mode.py @@ -2,6 +2,7 @@ import numpy as np from loguru import logger +import os from pypsse.common import CASESTUDY_FOLDER, VALUE_UPDATE_BOUND from pypsse.enumerations import ModelTypes, WritableModelTypes @@ -994,6 +995,10 @@ def update_object(self, dtype, bus, element_id, values: dict): if val > -VALUE_UPDATE_BOUND and val < VALUE_UPDATE_BOUND: if dtype == WritableModelTypes.LOAD.value: ierr = self.psse.load_chng_5(ibus=int(bus), id=element_id, **values) + # ierr, cmpval = self.psse.loddt2(int(bus),element_id,'MVA','ACT') + # if ierr == 0: + # logger.info(f"recheck load value: {cmpval}") + # os.system("PAUSE") elif dtype == WritableModelTypes.GENERATOR.value: ierr = self.psse.induction_machine_data(ibus=int(bus), id=element_id, **values) elif dtype == WritableModelTypes.MACHINE.value: diff --git a/pypsse/modes/dynamic.py b/pypsse/modes/dynamic.py index 80cf661..91475ae 100644 --- a/pypsse/modes/dynamic.py +++ b/pypsse/modes/dynamic.py @@ -118,7 +118,9 @@ def step(self, t): self.time = self.time + self.incTime self.xTime = 0 - return self.psse.run(0, t, 1, 1, 1) + # self.psse.run(0, t + self.incTime.total_seconds(), 1, 1, 1) + # self.psse.run(0, t + self.incTime.total_seconds(), 1, 1, 1) + return self.psse.run(0, t + self.incTime.total_seconds(), 1, 1, 1) # @kapil do you need this? diff --git a/pypsse/modes/snap.py b/pypsse/modes/snap.py index 7f5dca7..1440162 100644 --- a/pypsse/modes/snap.py +++ b/pypsse/modes/snap.py @@ -41,7 +41,7 @@ def init(self, bus_subsystems): ierr = self.psse.rstr(str(self.settings.simulation.snp_file)) # - # The following logic only runs when the helics interface is enabled + # # The following logic only runs when the helics interface is enabled self.disable_load_models_for_coupled_buses() self.disable_generation_for_coupled_buses() # self.save_model() @@ -78,16 +78,17 @@ def init(self, bus_subsystems): self.psse.delete_all_plot_channels() self.setup_all_channels() - + # print("111111111111111111111111111111111111111111111111111111111111111111") logger.debug("pyPSSE initialization complete!") self.initialization_complete = True + # print("222222222222222222222222222222222222222222222222222222222222222222") return self.initialization_complete def step(self, t): "Increments the simulation" self.time = self.time + self.incTime self.xTime = 0 - return self.psse.run(0, t, 1, 1, 1) + return self.psse.run(0, t + self.incTime.total_seconds(), 1, 1, 1) def resolve_step(self, t): "Resolves the current time step" diff --git a/pypsse/simulation_controller.py b/pypsse/simulation_controller.py index 84b0063..a59086a 100644 --- a/pypsse/simulation_controller.py +++ b/pypsse/simulation_controller.py @@ -33,9 +33,11 @@ def sim_controller( """ sim_modes = {"Dynamic": Dynamic, "Steady-state": Static, "Snap": Snap, "ProductionCostModel": ProductionCostModel} - + # print("0000000000000000000000000000000000000000000000000000") sim = sim_modes[settings.simulation.simulation_mode.value]( psse, dyntools, settings, export_settings, subsystem_buses, raw_data ) + # print("33333333333333333333333333333333333333333333333333333") logger.debug(f"Simulator contoller of type {settings.simulation.simulation_mode.value} created") + # print("4444444444444444444444444444444444444444444444444444444") return sim diff --git a/pypsse/simulator.py b/pypsse/simulator.py index b134b9e..5e7a1c1 100644 --- a/pypsse/simulator.py +++ b/pypsse/simulator.py @@ -17,7 +17,7 @@ import toml from loguru import logger from networkx import Graph - +import pssepath import pypsse.contingencies as c import pypsse.simulation_controller as sc from pypsse.common import ( @@ -48,13 +48,14 @@ from pypsse.result_container import Container from pypsse.models import Contingencies from pypsse.enumerations import PSSE_VERSIONS +from pypsse.utils.dynamic_utils import DynamicUtils USING_NAERM = 0 N_BUS = 200000 -class Simulator: +class Simulator(DynamicUtils): "Base class for the simulator" _status: SimulationStatus = SimulationStatus.NOT_INITIALIZED @@ -77,6 +78,7 @@ def __init__( self.settings = settings logger.debug(f"Instantiating psse version {psse_version}") + pssepath.add_pssepath(35.4) __import__(psse_version, fromlist=[""]) # noqa: F401 import dyntools @@ -376,25 +378,55 @@ def step(self, t: float) -> dict: if self.settings.simulation.use_profile_manager: self.pm.update() ctime = time.time() - self.simStartTime + ierr, rval = self.psse.dsrval('TIME', 0) logger.debug( - f"Simulation time: {t} seconds\nRun time: {ctime}\npsse time: {self.sim.get_time()}" + f"Simulation time: {t} seconds\nRun time: {ctime}\npsse time: {self.sim.get_time()} \nsolver time: {rval}" ) if self.settings.helics and self.settings.helics.cosimulation_mode: - if self.settings.helics.create_subscriptions: + if self.settings.helics.create_subscriptions: + # logger.debug(f"Time requested: {t}") + # self.inc_time, helics_time = self.update_federate_time(t) + # logger.debug(f"Time granted: {helics_time}") self.update_subscriptions() - logger.debug(f"Time requested: {t}") - self.inc_time, helics_time = self.update_federate_time(t) - logger.debug(f"Time granted: {helics_time}") + + # pypsse_update_agian = "y" + # while pypsse_update_agian != "no": + # pypsse_update_agian = input('enter your pypsse update command: ') + # if pypsse_update_agian == "y": + # if self.settings.helics and self.settings.helics.cosimulation_mode: + # if self.settings.helics.create_subscriptions: + # self.update_subscriptions() + # logger.debug(f"Time requested: {t}") + # self.inc_time, helics_time = self.update_federate_time(t) + # logger.debug(f"Time granted: {helics_time}") + # elif pypsse_update_agian == "u": + # if self.settings.helics and self.settings.helics.cosimulation_mode: + # self.publish_data() if self.inc_time: + logger.debug(f"run PSSE simulation at time: {t}") self.sim.step(t) + # os.system("PAUSE") else: self.sim.resolve_step() if self.settings.helics and self.settings.helics.cosimulation_mode: self.publish_data() + if self.settings.helics and self.settings.helics.cosimulation_mode: + if self.settings.helics.create_subscriptions: + # self.update_subscriptions() + logger.debug(f"Time requested: {t}") + self.inc_time, helics_time = self.update_federate_time(t) + logger.debug(f"Time granted: {helics_time}") + curr_results = self.update_result_container(t) + # if t >= 0.2: + # The following logic only runs when the helics interface is enabled + # self.disable_load_models_for_coupled_buses() + # self.disable_generation_for_coupled_buses() + # os.system("PAUSE") + return curr_results def update_result_container(self, t: float) -> dict: diff --git a/pypsse/utils/dynamic_utils.py b/pypsse/utils/dynamic_utils.py index d520d47..cf1b7e7 100644 --- a/pypsse/utils/dynamic_utils.py +++ b/pypsse/utils/dynamic_utils.py @@ -58,24 +58,27 @@ def disable_generation_for_coupled_buses(self): ] self.psse.machine_chng_2(bus_id, machine, intgar, realar) logger.info(f"Machine disabled: {bus_id}_{machine}") + # os.system("PAUSE") def disable_load_models_for_coupled_buses(self): """Disables loads of coupled buses (co-simulation mode only)""" if self.settings.helics and self.settings.helics.cosimulation_mode: sub_data = pd.read_csv(self.settings.simulation.subscriptions_file) sub_data = sub_data[sub_data["element_type"] == "Load"] - - self.psse_dict = {} - for _, row in sub_data.iterrows(): - bus = row["bus"] - load = row["element_id"] - ierr = self.psse.ldmod_status(0, int(bus), str(load), 1, 0) - if ierr == 0: - logger.info(f"Dynamic model for load {load} connected to bus {bus} has been disabled") - elif ierr == 5: - logger.error(f"No dynamic model found for load {load} connected to bus {bus}") - else: - raise Exception(f"error={ierr}") + logger.info(f"break the load") + self.break_loads(None, ['FmD']) + # self.psse_dict = {} + # for _, row in sub_data.iterrows(): + # bus = row["bus"] + # load = row["element_id"] + # ierr = self.psse.ldmod_status(0, int(bus), str(load), 1, 0) + # if ierr == 0: + # logger.info(f"Dynamic model for load {load} connected to bus {bus} has been disabled") + # elif ierr == 5: + # logger.error(f"No dynamic model found for load {load} connected to bus {bus}") + # else: + # raise Exception(f"error={ierr}") + # os.system("PAUSE") def break_loads(self, loads: list = None, components_to_replace: List[str] = []): """Implements the load split logic @@ -84,14 +87,23 @@ def break_loads(self, loads: list = None, components_to_replace: List[str] = []) loads (list, optional): list of coupled loads. Defaults to None. components_to_replace (List[str], optional): components to be simulated on distribution side. Defaults to []. """ - + logger.info("##################### break_loads #########################") + os.system("PAUSE") components_to_stay = [x for x in self.dynamic_params if x not in components_to_replace] + logger.info(f"components_to_stay: {components_to_stay}") + logger.info(f"components_to_replace: {components_to_replace}") + os.system("PAUSE") if loads is None: loads = self._get_coupled_loads() + os.system("PAUSE") loads = self._get_load_static_data(loads) + os.system("PAUSE") loads = self._get_load_dynamic_data(loads) + os.system("PAUSE") loads = self._replicate_coupled_load(loads, components_to_replace) + os.system("PAUSE") self._update_dynamic_parameters(loads, components_to_stay, components_to_replace) + os.system("PAUSE") def _update_dynamic_parameters(self, loads: dict, components_to_stay: list, components_to_replace: list): """Updates dynamic parameters of composite old / replicated load models @@ -101,7 +113,7 @@ def _update_dynamic_parameters(self, loads: dict, components_to_stay: list, comp components_to_stay (list): components to be simulated on transmission side components_to_replace (list): components to be simulated on distribution side """ - + logger.info(f"_update_dynamic_parameters") new_percentages = {} for load in loads: count = 0 @@ -120,7 +132,7 @@ def _update_dynamic_parameters(self, loads: dict, components_to_stay: list, comp # self.psse.change_ldmod_con(load['bus'], 'XX' ,r"""CMLDBLU2""" ,idx ,v) values = list(settings.values()) self.psse.add_load_model(load["bus"], "XX", 0, 1, r"""CMLDBLU2""", 2, [0, 0], ["", ""], 133, values) - logger.info(f"Dynamic model parameters for load {load['id']} at bus 'XX' changed.") + logger.info(f"Dynamic model parameters for load {load['id']} at bus 'XX' changed. New settings are {values}.") def _get_load_dynamic_properties(self, load): "Returns dynamic parameters of composite load models" @@ -144,7 +156,7 @@ def _replicate_coupled_load(self, loads: dict, components_to_replace: list): Returns: dict: updated load dictionary """ - + logger.info(f"2222222222222222222222222") for load in loads: dynamic_percentage = load["FmA"] + load["FmB"] + load["FmC"] + load["FmD"] + load["Fel"] static_percentage = 1.0 - dynamic_percentage @@ -159,7 +171,7 @@ def _replicate_coupled_load(self, loads: dict, components_to_replace: list): load["bus"], "XX", realar=[total_transmission_load.real, total_transmission_load.imag, 0.0, 0.0, 0.0, 0.0], - lodtyp="replica", + # lodtyp="replica", ) # ierr, cmpval = self.psse.loddt2(load["bus"], "XX" ,"MVA" , "ACT") # modify old load @@ -167,7 +179,7 @@ def _replicate_coupled_load(self, loads: dict, components_to_replace: list): load["bus"], str(load["id"]), realar=[total_distribution_load.real, total_distribution_load.imag, 0.0, 0.0, 0.0, 0.0], - lodtyp="original", + # lodtyp="original", ) # ierr, cmpval = self.psse.loddt2(load["bus"], load["id"] ,"MVA" , "ACT") logger.info(f"Original load {load['id']} @ bus {load['bus']}: {total_load}") @@ -175,21 +187,24 @@ def _replicate_coupled_load(self, loads: dict, components_to_replace: list): logger.info(f"Load {load['id']} @ bus {load['bus']} updated : {total_distribution_load}") load["distribution"] = total_distribution_load load["transmission"] = total_transmission_load + logger.info(f"{loads}") return loads def _get_coupled_loads(self) -> list: - """Returns a list of all coupled loads ina give simualtion + """Returns a list of all coupled loads in a give simualtion Returns: list: list of coupled loads """ - - sub_data = pd.read_csv( - os.path.join( - self.settings["Simulation"]["Project Path"], "Settings", self.settings["HELICS"]["Subscriptions file"] - ) - ) + + # sub_data = pd.read_csv( + # os.path.join( + # self.settings["Simulation"]["Project Path"], "Settings", self.settings["HELICS"]["Subscriptions file"] + # ) # C:\Users\FXIE\Documents\GitHub\NEARM_TnD\dynamic_cosim_test_cases-model\WECC\Transmission\pypsse_model\Subscriptions.csv + # ) + sub_data = pd.read_csv(self.settings.simulation.subscriptions_file) load = [] + logger.info(f"_get_coupled_loads") for _, row in sub_data.iterrows(): if row["element_type"] == "Load": load.append( @@ -199,6 +214,7 @@ def _get_coupled_loads(self) -> list: "bus": row["bus"], } ) + logger.info(f"load is {load}") return load def _get_load_static_data(self, loads: list) -> dict: @@ -210,12 +226,13 @@ def _get_load_static_data(self, loads: list) -> dict: Returns: dict: mapping load to static values """ - + logger.info(f"_get_load_static_data") values = ["MVA", "IL", "YL", "TOTAL"] for load in loads: for v in values: ierr, cmpval = self.psse.loddt2(load["bus"], str(load["id"]), v, "ACT") load[v] = cmpval + logger.info(f"{loads}") return loads def _get_load_dynamic_data(self, loads: list) -> dict: @@ -227,7 +244,7 @@ def _get_load_dynamic_data(self, loads: list) -> dict: Returns: dict: mapping load to dynamic values """ - + logger.info(f"_get_load_dynamic_data") values = dyn_only_options["Loads"]["lmodind"] for load in loads: for v, con_ind in values.items(): @@ -243,6 +260,7 @@ def _get_load_dynamic_data(self, loads: list) -> dict: ierr, value = self.psse.dsrval("CON", act_con_index) assert ierr == 0, f"error={ierr}" load[v] = value + logger.info(f"{loads}") return loads def setup_machine_channels(self, machines: dict, properties: list): @@ -261,7 +279,7 @@ def setup_machine_channels(self, machines: dict, properties: list): if qty in MACHINE_CHANNELS: self.channel_map[nqty][f"{b}_{mch}"] = [self.chnl_idx] chnl_id = MACHINE_CHANNELS[qty] - logger.info(f"{qty} for machine {b}_{mch} added to channel {self.chnl_idx}") + # logger.info(f"{qty} for machine {b}_{mch} added to channel {self.chnl_idx}") self.psse.machine_array_channel([self.chnl_idx, chnl_id, int(b)], mch, "") self.chnl_idx += 1 @@ -281,7 +299,7 @@ def setup_load_channels(self, loads: list): self.channel_map["LOAD_Q"][f"{b}_{ld}"] = [self.chnl_idx + 1] self.psse.load_array_channel([self.chnl_idx, 1, int(b)], ld, "") self.psse.load_array_channel([self.chnl_idx + 1, 2, int(b)], ld, "") - logger.info(f"P and Q for load {b}_{ld} added to channel {self.chnl_idx} and {self.chnl_idx + 1}") + # logger.info(f"P and Q for load {b}_{ld} added to channel {self.chnl_idx} and {self.chnl_idx + 1}") self.chnl_idx += 2 def setup_bus_channels(self, buses: list, properties: list): @@ -299,12 +317,12 @@ def setup_bus_channels(self, buses: list, properties: list): if qty == "frequency": self.channel_map[qty][b] = [self.chnl_idx] self.psse.bus_frequency_channel([self.chnl_idx, int(b)], "") - logger.info(f"Frequency for bus {b} added to channel { self.chnl_idx}") + # logger.info(f"Frequency for bus {b} added to channel { self.chnl_idx}") self.chnl_idx += 1 elif qty == "voltage_and_angle": self.channel_map[qty][b] = [self.chnl_idx, self.chnl_idx + 1] self.psse.voltage_and_angle_channel([self.chnl_idx, -1, -1, int(b)], "") - logger.info(f"Voltage and angle for bus {b} added to channel {self.chnl_idx} and {self.chnl_idx+1}") + # logger.info(f"Voltage and angle for bus {b} added to channel {self.chnl_idx} and {self.chnl_idx+1}") self.chnl_idx += 2 def poll_channels(self) -> dict: From ae2249aac0571e94bbcc94defd62642e26316e18 Mon Sep 17 00:00:00 2001 From: Fuhong Xie Date: Wed, 4 Jun 2025 21:57:35 -0600 Subject: [PATCH 02/14] bug fix and add steady state cosim --- pypsse/__init__.py | 2 +- pypsse/cli/create_profiles.py | 19 +- pypsse/cli/explore.py | 16 +- pypsse/cli/profiles.py | 43 ++++ pypsse/cli/pypsse.py | 2 + pypsse/common.py | 1 + pypsse/defaults/simulation_settings.toml | 2 +- pypsse/modes/abstract_mode.py | 305 +++++++++++++++++++++-- pypsse/modes/constants.py | 5 +- pypsse/modes/snap.py | 13 +- pypsse/profile_manager/profile.py | 2 +- pypsse/profile_manager/profile_store.py | 10 +- pypsse/profile_manager_interface.py | 76 ++++++ pypsse/result_container.py | 2 + pypsse/simulator.py | 10 +- pypsse/utils/dynamic_utils.py | 2 +- 16 files changed, 461 insertions(+), 49 deletions(-) create mode 100644 pypsse/cli/profiles.py create mode 100644 pypsse/profile_manager_interface.py diff --git a/pypsse/__init__.py b/pypsse/__init__.py index 0b2f79d..9b102be 100644 --- a/pypsse/__init__.py +++ b/pypsse/__init__.py @@ -1 +1 @@ -__version__ = "1.1.3" +__version__ = "1.1.5" diff --git a/pypsse/cli/create_profiles.py b/pypsse/cli/create_profiles.py index eb29a13..7548133 100644 --- a/pypsse/cli/create_profiles.py +++ b/pypsse/cli/create_profiles.py @@ -19,7 +19,7 @@ PROFILE_VALIDATION, ) from pypsse.profile_manager.profile_store import ProfileManager - +from pypsse.models import SimulationSettings @click.argument( "project-path", @@ -84,38 +84,41 @@ def create_profiles( project_path, csv_file_path, profile_folder, profile_name, profile_type, start_time, profile_res, profile_info ): """Creates profiles for PyPSSE project.""" - settings_file = os.path.join(project_path, "Settings", SIMULATION_SETTINGS_FILENAME) + settings_file = os.path.join(project_path, SIMULATION_SETTINGS_FILENAME) if os.path.exists(settings_file): if csv_file_path and os.path.exists(csv_file_path): settings = toml.load(settings_file) + settings = SimulationSettings.model_validate(settings) + a = ProfileManager(None, settings) a.add_profiles_from_csv( csv_file=csv_file_path, name=profile_name, - pType=profile_type, - startTime=dt.datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S.%f").astimezone(None), + p_type=profile_type, + start_time=dt.datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S.%f").astimezone(None), resolution_sec=profile_res, info=profile_info, ) logger.info(f"Profile '{profile_name}' added to group '{profile_type}'") elif os.path.exists(profile_folder): settings = toml.load(settings_file) + settings = SimulationSettings.model_validate(settings) a = ProfileManager(None, settings) for _, _, files in os.walk(profile_folder): for file in files: if file.endswith(".csv"): filename = file.replace(".csv", "") if "__" in filename: - dtype, p_name = filename.split("__") + p_type, p_name = filename.split("__") a.add_profiles_from_csv( csv_file=os.path.join(profile_folder, file), name=p_name, - pType=dtype, - startTime=dt.datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S.%f").astimezone(None), + p_type=p_type, + start_time=dt.datetime.strptime(start_time, "%Y-%m-%d %H:%M:%S.%f"), resolution_sec=profile_res, info=profile_info, ) - msg = f"Profile '{p_name}'' added to group '{dtype}'" + msg = f"Profile '{p_name}'' added to group '{p_type}'" logger.info(msg) else: msg = "Value for either -f or -p flag has to be passed" diff --git a/pypsse/cli/explore.py b/pypsse/cli/explore.py index d8c3867..5b975cb 100644 --- a/pypsse/cli/explore.py +++ b/pypsse/cli/explore.py @@ -1,5 +1,5 @@ from pathlib import Path - +import os from loguru import logger import pandas as pd import click @@ -94,21 +94,27 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load, msg = "Simulation file not found. Use -s to choose a valid settings file" "if its name differs from the default file name." assert file_path.exists(), msg + # print(1111111111) simulation_settings = toml.load(file_path) simulation_settings = SimulationSettings(**simulation_settings) simulation_settings.helics.cosimulation_mode = False + # print(2222222222) x = Simulator(simulation_settings) + # print(3333333333) buses = set(x.raw_data.buses) + # print(4444444444) quantities = { 'Loads': ['MVA', "IL", "YL", 'FmA', 'FmB', 'FmC', 'FmD', 'Fel', 'PFel'], 'Induction_generators': ['MVA'], 'Machines': ['MVA', 'PERCENT'], } results = x.sim.read_subsystems(quantities, buses) + # print(55555555555555) + # os.system("PAUSE") # print(results.keys()) - print(results["LOAD_P"]) + # print(results["LOAD_P"]) # quit() had_comp_models = False @@ -172,7 +178,7 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load, results["is load comp"].append(is_comp_load[bus] if bus in is_comp_load else False) results["total P load [MW]"].append(bus_load_real[bus] if bus in bus_load_real else 0) results["total Q load [MVar]"].append(bus_load_imag[bus] if bus in bus_load_imag else 0) - results["has generation"].append(True if bus in generator_dict else False) + results["has generation"].append(True if (bus in generator_dict and bus_gen[bus] > 0)else False) results["total generation [MVA]"].append(bus_gen[bus] if bus in bus_gen else 0) @@ -204,8 +210,8 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load, results=results[(results["total P load [MW]"] >= load_lower) & (results["total P load [MW]"] <= load_upper)] results=results[(results["total generation [MVA]"] >= gen_lower) & (results["total generation [MVA]"] <= gen_upper)] - print(results) - results.to_csv(export_file_path) + # print(results) + results.to_csv(export_file_path,index=False) logger.info(f"Results exported to {export_file_path.absolute()}") diff --git a/pypsse/cli/profiles.py b/pypsse/cli/profiles.py new file mode 100644 index 0000000..c1ff35e --- /dev/null +++ b/pypsse/cli/profiles.py @@ -0,0 +1,43 @@ +""" + CLI to run a PyDSS project + """ + +from pathlib import Path + +from loguru import logger +import click +import toml + +from pypsse.models import SimulationSettings +from pypsse.common import SIMULATION_SETTINGS_FILENAME +from pypsse.profile_manager_interface import ProfileManagerInterface + +@click.argument( + "project-path", +) +@click.option( + "-s", + "--simulations-file", + required=False, + default=SIMULATION_SETTINGS_FILENAME, + show_default=True, + help="scenario toml file to run (over rides default)", +) +@click.command() +def get_profiles(project_path, simulations_file=None): + """Runs a valid PyPSSE simulation.""" + file_path = Path(project_path) / simulations_file + msg = "Simulation file not found. Use -s to choose a valid settings file" + "if its name differs from the default file name." + assert file_path.exists(), msg + + simulation_settings = toml.load(file_path) + simulation_settings = SimulationSettings(**simulation_settings) + + logger.level(simulation_settings.log.logging_level.value) + if simulation_settings.log.log_to_external_file: + log_path = Path(project_path) / "Logs" / "pypsse.log" + logger.add(log_path) + logger.info(f"file_path: {file_path}") + profile_interface = ProfileManagerInterface.from_setting_files(file_path) + profile_interface.get_profiles() \ No newline at end of file diff --git a/pypsse/cli/pypsse.py b/pypsse/cli/pypsse.py index a2d277e..9c26271 100644 --- a/pypsse/cli/pypsse.py +++ b/pypsse/cli/pypsse.py @@ -7,6 +7,7 @@ from pypsse.cli.create_project import create_project from pypsse.cli.explore import explore from pypsse.cli.run import run +from pypsse.cli.profiles import get_profiles server_dependencies_installed = True @@ -28,5 +29,6 @@ def cli(): cli.add_command(run) cli.add_command(create_profiles) cli.add_command(explore) +cli.add_command(get_profiles) if server_dependencies_installed: cli.add_command(serve) diff --git a/pypsse/common.py b/pypsse/common.py index 4fc8b4c..bf66b2d 100644 --- a/pypsse/common.py +++ b/pypsse/common.py @@ -25,6 +25,7 @@ DEFAULT_LOG_FILE = "psse.log" DEFAULT_GRAPH_FILE = "network.gpickle" DEFAULT_COORDINATES_FILE = "coordinates.csv" +DEFAULT_PROFILE_EXPORT_FILE = "profiles.csv" MAPPED_CLASS_NAMES = { "buses": "Buses", diff --git a/pypsse/defaults/simulation_settings.toml b/pypsse/defaults/simulation_settings.toml index b20bbba..f56befd 100644 --- a/pypsse/defaults/simulation_settings.toml +++ b/pypsse/defaults/simulation_settings.toml @@ -41,7 +41,7 @@ asset_properties = [ "FREQ", "PU",] [log] disable_psse_logging = true -logging_level = 10 +logging_level = "DEBUG" log_to_external_file = true display_on_screen = true clear_old_log_file = true diff --git a/pypsse/modes/abstract_mode.py b/pypsse/modes/abstract_mode.py index a73d5b5..299ad5a 100644 --- a/pypsse/modes/abstract_mode.py +++ b/pypsse/modes/abstract_mode.py @@ -421,7 +421,6 @@ def get_a_loadmw_in_substation(self, sub_number, subsystem_buses): while ierr == 0: ierr, val = self.psse.loddt2(int(b), load_id, "TOTAL", "ACT") - if isinstance(val, complex): load_mw += val.real ierr, load_id = self.psse.nxtlod(int(b)) @@ -510,7 +509,7 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma ext_string2_info = {} if mapping_dict is None: mapping_dict = {} - + # logger.debug(f" subsystem_buses : {subsystem_buses}") results = {} area_numbers = self.get_area_numbers(subsystem_buses) zone_numbers = self.get_zone_numbers(subsystem_buses) @@ -518,12 +517,18 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma substation_numbers = self.get_substation_numbers(subsystem_buses) for class_name, var_list in quantities.items(): + # logger.debug(f"class_name : {class_name}, var_list : {var_list}") + # logger.debug(f"self.func_options : {self.func_options}") if class_name in self.func_options: funcs = self.func_options[class_name] + # logger.debug(f"funcs : {funcs}") for id_, v in enumerate(var_list): for func_name, settinsgs in funcs.items(): + # logger.debug(f"func_name : {func_name}, settinsgs : {settinsgs}") if v in settinsgs: q = f"{class_name}_{v}" + # logger.debug(f"q : {q}") + # os.system("PAUSE") if len(mapping_dict) != 0: if class_name in mapping_dict: @@ -592,6 +597,7 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma else: for b in subsystem_buses: + # logger.debug(f"bus : {b}") if func_name in [ "busdat", "busdt2", @@ -664,6 +670,7 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma elif func_name in ["inddt1", "inddt2", "indnofunc"]: ierr = self.psse.iniind(int(b)) if ierr: + # os.system("PAUSE") logger.info("No induction machine in the model") else: ierr, ind_id = self.psse.nxtind(int(b)) @@ -671,39 +678,222 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma if func_name == "indnofunc": if v in ["BUSNUM", "INDID"]: val = {"INDID": ind_id, "BUSNUM": int(b)}[v] - results = self.add_result(results, q, val, f"{ind_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{ind_id}") elif v == "BUSNAME": irr, val = self.psse.notona(int(b)) - results = self.add_result(results, q, val, f"{ind_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{ind_id}") else: ierr, val = getattr(self.psse, func_name)(int(b), ind_id, v) - results = self.add_result(results, q, val, f"{ind_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{ind_id}") ierr, ind_id = self.psse.nxtind(int(b)) elif func_name in ["loddt2", "lodnofunc", "lodint"]: ierr = self.psse.inilod(int(b)) - + # logger.debug(f"func_name : {func_name}") + # logger.debug(f"bus : {b}") ierr, load_id = self.psse.nxtlod(int(b)) + # logger.debug(f"first load_id : {load_id}") while load_id is not None: if func_name == "lodnofunc": if v in ["BUSNUM", "LOADID"]: val = {"LOADID": load_id, "BUSNUM": int(b)}[v] - results = self.add_result(results, q, val, f"{load_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{load_id}") elif v == "BUSNAME": ierr, val = self.psse.notona(int(b)) - - results = self.add_result(results, q, val, f"{load_id}_{b}") + + results = self.add_result(results, q, val, f"{b}_{load_id}") elif func_name == "loddt2": ierr, val = getattr(self.psse, func_name)(int(b), load_id, v, "ACT") - - results = self.add_result(results, q, val, f"{load_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{load_id}") + # if int(b) == 10446 or int(b) == 10466 or int(b) == 10493: + # logger.debug(f"loddt2 -> int(b): {int(b)}, load_id: {load_id}, v: {v}, ierr : {ierr}, val : {val}") + # ierr, state_index = self.psse.lmodind(int(b), load_id, "CHARAC", "STATE") + # assert ierr == 0, f"error={ierr}" + # if state_index is not None: + # act_state_index = state_index + 20 + # ierr, value = self.psse.dsrval("STATE", act_state_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: non-restartable compressor motor A temperature - Load: {int(b)}-{load_id} -> index: {act_state_index}, value:{value}") + # act_state_index = state_index + 21 + # ierr, value = self.psse.dsrval("STATE", act_state_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: restartable compressor motor B temperature - Load: {int(b)}-{load_id} -> index: {act_state_index}, value:{value}") + # ierr, var_index = self.psse.lmodind(int(b), load_id, "CHARAC", "VAR") + # assert ierr == 0, f"error={ierr}" + # if var_index is not None: + # act_var_index = var_index + 0 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Load MVA base - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 12 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Transformer tap - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 13 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Transformer low side voltage real part - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 14 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Transformer low side voltage imaginary part - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 15 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Load bus voltage real part - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 16 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Load bus voltage imaginary part - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 20 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Sum of substation shunt admittance and feeder compensation admittance at substation end - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 21 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Substation shunt admittance - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 24 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Transformer reactance (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 25 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Static load real part (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 26 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Static load reactive part (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 29 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Load bus voltage magnitude - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 30 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Low side bus voltage magnitude - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 37 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D P (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 38 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D Q (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 53 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Initial value of Motor D P (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 54 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Initial value of Motor D Q (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 55 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Feeder compensation admittance at substation end - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 56 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Feeder compensation admittance at far end - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 60 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Calculated Vstallbrk - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 66 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Initial value of Motor D MVA base - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 117 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Bus voltage (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 118 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Bus frequency (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 119 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Aggregated AC unit real power (MW) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 120 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Aggregated AC unit reactive power (MVAr) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 121 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Aggregated AC unit current (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 124 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Terminal current component on network real axis on system MVA base (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 125 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Terminal current comp on network imaginary axis on system MVA base (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 126 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: non-restartable motor A and restartable motor B Initial Temperature - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 131 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: KthA non-restartable compressor motor A fraction not tripped by thermal protection - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 133 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: non-restartable Motor A run / stall state (run=1/stall=0) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 134 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: restartable Motor B run / stall state (run=1/stall=0) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 135 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: KthB restartable compressor motor B fraction not tripped by thermal protection - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 136 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Internal variable used for determining non-restartable motor A temperature - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 137 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Internal variable used for determining restartable motor B temperature - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 138 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Real component of voltage at pervious time step (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 139 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Reactive component of voltage at previous time step (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 141 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Internal variable, P0 for active power at 1.0 pu voltage - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 142 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Internal variable, Q0 for reactive power at 1.0 pu voltage - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 143 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Motor D: Computed motor MVA base - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") + + # os.system("PAUSE") elif func_name == "lodint": ierr, val = getattr(self.psse, func_name)(int(b), load_id, v) - results = self.add_result(results, q, val, f"{load_id}_{b}") - + results = self.add_result(results, q, val, f"{b}_{load_id}") + # logger.debug(f"val : {val}") + # logger.debug(f"v : {v}") ierr, load_id = self.psse.nxtlod(int(b)) + # logger.debug(f"next load_id : {load_id}") + # logger.debug(f"results : {results}") + # os.system("PAUSE") elif func_name in ["macdat", "macdt2", "macnofunc", "macint"]: ierr = self.psse.inimac(int(b)) @@ -712,19 +902,19 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma if func_name == "macnofunc": if v in ["BUSNUM", "MACID"]: val = {"BUSNUM": int(b), "MACID": mach_id}[v] - results = self.add_result(results, q, val, f"{mach_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{mach_id}") elif v == "BUSNAME": ierr, val = self.psse.notona(int(b)) - results = self.add_result(results, q, val, f"{mach_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{mach_id}") elif v == "SUBNUMBER": ierr, val = self.psse.busint(int(b), "STATION") - results = self.add_result(results, q, val, f"{mach_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{mach_id}") elif v == "AREANUMBER": ierr, val = self.psse.busint(int(b), "AREA") - results = self.add_result(results, q, val, f"{mach_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{mach_id}") elif v in ["SUBLATITUDE", "SUBLONGITUDE"]: sub_dict = {"SUBLATITUDE": "LATI", "SUBLONGITUDE": "LONG"} @@ -735,9 +925,82 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma results = self.add_result(results, q, val, b) else: + logger.debug(f"machine -> int(b): {int(b)}, mach_id: {mach_id}, func_name: {func_name}, v: {v}") + # if mach_id == "PV": + # ierr, state_index = self.psse.windmind(int(b), mach_id, 'WGEN', 'STATE') + # assert ierr == 0, f"error={ierr}" + # if state_index is not None: + # act_state_index = state_index + 0 + # ierr, value = self.psse.dsrval("STATE", act_state_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"voltage measurement lag: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") + # act_state_index = state_index + 1 + # ierr, value = self.psse.dsrval("STATE", act_state_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"generator power measurement lag: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") + # act_state_index = state_index + 2 + # ierr, value = self.psse.dsrval("STATE", act_state_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"first order lag for reactive current: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") + # act_state_index = state_index + 3 + # ierr, value = self.psse.dsrval("STATE", act_state_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"lag block representing inner current loop for iq: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") + # act_state_index = state_index + 4 + # ierr, value = self.psse.dsrval("STATE", act_state_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"lag block for output of multiplier block: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") + # act_state_index = state_index + 8 + # ierr, value = self.psse.dsrval("STATE", act_state_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"first order lag for Pord: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") + # act_state_index = state_index + 9 + # ierr, value = self.psse.dsrval("STATE", act_state_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"lag block representing inner current loop for id: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") + # ierr, var_index = self.psse.windmind(int(b), mach_id, 'WGEN', 'VAR') + # assert ierr == 0, f"error={ierr}" + # if var_index is not None: + # act_var_index = var_index + 0 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"bus voltage reference (Vref0): {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 1 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"power factor angle reference (pfaref), radians: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 3 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"active power reference (Pref): {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 4 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"reactive power reference (Qref): {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 7 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Ipcmd: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 8 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"Iqcmd: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 11 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"output of undervoltage voltage multiplier block: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 12 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"output of overvoltage voltage multiplier block: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") + # act_var_index = var_index + 22 + # ierr, value = self.psse.dsrval("VAR", act_var_index) + # assert ierr == 0, f"error={ierr}" + # logger.debug(f"reactive current injection, Iqinj: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") + # # os.system("PAUSE") ierr, val = getattr(self.psse, func_name)(int(b), mach_id, v) - results = self.add_result(results, q, val, f"{mach_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{mach_id}") ierr, mach_id = self.psse.nxtmac(int(b)) elif func_name in ["fxsdt2", "fxsnofunc"]: @@ -749,15 +1012,15 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma if func_name == "fxsnofunc": if v in ["BUSNUM", "FXSHID"]: val = {"BUSNUM": int(b), "FXSHID": fx_id}[v] - results = self.add_result(results, q, val, f"{fx_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{fx_id}") elif v == "BUSNAME": ierr, val = self.psse.notona(int(b)) - results = self.add_result(results, q, val, f"{fx_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{fx_id}") else: ierr, val = getattr(self.psse, func_name)(int(b), fx_id, v) - results = self.add_result(results, q, val, f"{fx_id}_{b}") + results = self.add_result(results, q, val, f"{b}_{fx_id}") ierr, fx_id = self.psse.nxtfxs(int(b)) elif func_name in ["swsdt1", "swsnofunc"]: diff --git a/pypsse/modes/constants.py b/pypsse/modes/constants.py index ca91232..a722b68 100644 --- a/pypsse/modes/constants.py +++ b/pypsse/modes/constants.py @@ -205,7 +205,10 @@ def return_data_type(data_list, cls_type): status_translation = {1: "connected", 0: "notconnected"} if param.lower() == "status": for key, value in sub_dict.items(): - sub_dict[key] = status_translation[value] + if value == "connected" or "notconnected": + sub_dict[key] = value + else: + sub_dict[key] = status_translation[value] if class_name in complex_conversion_dict: conv_dict = complex_conversion_dict[class_name] diff --git a/pypsse/modes/snap.py b/pypsse/modes/snap.py index 1440162..81c6e69 100644 --- a/pypsse/modes/snap.py +++ b/pypsse/modes/snap.py @@ -1,5 +1,6 @@ import numpy as np from loguru import logger +import os from pypsse.models import SimulationSettings, ExportFileOptions from pypsse.modes.abstract_mode import AbstractMode @@ -134,12 +135,20 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma ext_string2_info = {} if mapping_dict is None: mapping_dict = {} + results = super().read_subsystems( quantities, subsystem_buses, mapping_dict=mapping_dict, ext_string2_info=ext_string2_info ) - + # logger.debug(f"snap.py results : {results}") + # logger.debug(f"quantities : {quantities}") + # logger.debug(f"subsystem_buses : {subsystem_buses}") + # logger.debug(f"mapping_dict : {mapping_dict}") + # logger.debug(f"ext_string2_info : {ext_string2_info}") + # raise Exception(1) poll_results = self.poll_channels() results.update(poll_results) + # logger.debug(f"snap.py updated results : {results}") + # os.system("PAUSE") """ Add """ for class_name, var_list in quantities.items(): if class_name in dyn_only_options: @@ -169,6 +178,6 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma obj_name = f"{bus}_{ld_id}" results[res_base][obj_name] = value else: - logger.warning("Extend function 'read_subsystems' in the Snap class (Snap.py)") + logger.warning(f"{class_name} not in the option") return results diff --git a/pypsse/profile_manager/profile.py b/pypsse/profile_manager/profile.py index 06d1f9f..1cbaa00 100644 --- a/pypsse/profile_manager/profile.py +++ b/pypsse/profile_manager/profile.py @@ -8,7 +8,7 @@ class Profile: - "Class defination fora single profile" + "Class defination for single profile" DEFAULT_SETTINGS = {"multiplier": 1, "normalize": False, "interpolate": False} diff --git a/pypsse/profile_manager/profile_store.py b/pypsse/profile_manager/profile_store.py index 573036a..f132c5d 100644 --- a/pypsse/profile_manager/profile_store.py +++ b/pypsse/profile_manager/profile_store.py @@ -194,9 +194,11 @@ def add_profiles_from_csv( ValueError: rasied if invalid profile type passed """ - if p_type not in ProfileTypes: + if p_type not in [p.value for p in ProfileTypes]: msg = f"Valid profile types are: {list(PROFILE_VALIDATION.keys())}" raise ValueError(msg) + + p_type = getattr(ProfileTypes,[p.name for p in ProfileTypes if p.value == p_type][0]) logger.debug("Reading profile") data = pd.read_csv(csv_file) @@ -246,7 +248,7 @@ def add_profiles( def create_metadata( self, d_set: str, - start_time: datetime.date, + start_time: datetime.datetime, resolution: float, data: object, units: str, @@ -298,5 +300,5 @@ def update(self) -> dict: return results - def __del__(self): - self.store.flush() + # def __del__(self): + # self.store.flush() diff --git a/pypsse/profile_manager_interface.py b/pypsse/profile_manager_interface.py new file mode 100644 index 0000000..57b2322 --- /dev/null +++ b/pypsse/profile_manager_interface.py @@ -0,0 +1,76 @@ +from datetime import timedelta +from pathlib import Path + +from loguru import logger +import pandas as pd +import numpy as np +import h5py +import toml + +from pypsse.common import DEFAULT_PROFILE_MAPPING_FILENAME, DEFAULT_PROFILE_STORE_FILENAME, PROFILES_FOLDER, DEFAULT_PROFILE_EXPORT_FILE +from pypsse.models import SimulationSettings + + + +class ProfileManagerInterface: + + def __init__(self, settings: SimulationSettings): + assert settings.simulation.use_profile_manager, "Profile manager is not enabled in the simulation settings" + + project_path = settings.simulation.project_path + store_file = project_path / PROFILES_FOLDER / DEFAULT_PROFILE_STORE_FILENAME + toml_file = project_path / PROFILES_FOLDER / DEFAULT_PROFILE_MAPPING_FILENAME + self._store = h5py.File(store_file, 'r') + self._toml_dict = toml.load(toml_file) + self._start_time = settings.simulation.start_time + self._simulation_duration = settings.simulation.simulation_time + self._end_time = self._start_time + self._simulation_duration + self._export_file = project_path / PROFILES_FOLDER / DEFAULT_PROFILE_EXPORT_FILE + + @classmethod + def from_setting_files(cls, simulation_settings_file: Path): + simulation_settiings = toml.load(simulation_settings_file) + simulation_settiings = SimulationSettings(**simulation_settiings) + return cls(simulation_settiings) + + def get_profiles(self) -> list: + all_datasets = [] + for model_type, model_info in self._toml_dict.items(): + logger.info(f"model_type {model_type}") + logger.info(f"model_info {model_info}") + for profile_id, model_maps in model_info.items(): + for model_map in model_maps: + bus_id : str = model_map["bus"] + model_id : str = model_map["id"] + mult : float = model_map.get("multiplier") + norm: True = model_map.get("normalize") + dataset = self._store[f"{model_type}/{profile_id}"] + data = np.array(np.array(dataset).tolist()) + + if norm: + data_max = np.array(dataset.attrs["max"]) + data = data / data_max + if mult: + data = data * mult + + data = pd.DataFrame(data) + even_sum = data.iloc[:, ::2].sum(axis=1) + odd_sum = data.iloc[:, 1::2].sum(axis=1) + data = [even_sum, odd_sum] + final_df = pd.concat(data, axis=1) + final_df.columns = [f"{model_type}_{model_id}_{bus_id}_P", f"{model_type}_{model_id}_{bus_id}_Q"] + + start_time = str(dataset.attrs["sTime"].decode('utf-8')) + end_time = str(dataset.attrs["eTime"].decode('utf-8')) + timestep = timedelta(seconds=dataset.attrs["resTime"]) + date_range =pd.date_range( + start_time, + end_time, + freq=timestep) + final_df.index = date_range[:-1] + filtered_df = final_df.loc[(final_df.index >= self._start_time) & (final_df.index <= self._end_time)] + all_datasets.append(filtered_df) + + final_df = pd.concat(all_datasets, axis=1) + final_df.to_csv(self._export_file) + logger.info(f"Profiles exported to {self._export_file}") \ No newline at end of file diff --git a/pypsse/result_container.py b/pypsse/result_container.py index 58d02f5..7943693 100644 --- a/pypsse/result_container.py +++ b/pypsse/result_container.py @@ -102,8 +102,10 @@ def update(self, bus_data: dict, _, time: datetime.datetime, has_converged: bool """ if self.export_settings.file_format not in self.BULK_WRITE_MODES: + # logger.debug("1111111111111111111") self.dataWriter.write(time, bus_data, has_converged) else: + # logger.debug("2222222222222222222222") for variable_name, _ in bus_data.items(): if not isinstance(self.results[f"{variable_name}"], pd.DataFrame): self.results[f"{variable_name}"] = pd.DataFrame(bus_data[variable_name], index=[0]) diff --git a/pypsse/simulator.py b/pypsse/simulator.py index 5e7a1c1..d0d5997 100644 --- a/pypsse/simulator.py +++ b/pypsse/simulator.py @@ -164,10 +164,10 @@ def start_simulation(self): self.hi = None self.simStartTime = time.time() - - if self.settings.simulation.case_study.exists(): + + if self.settings.simulation.case_study is not None and self.settings.simulation.case_study.exists(): self.psse.case(str(self.settings.simulation.case_study)) - elif self.settings.simulation.raw_file.exists(): + elif self.settings.simulation.raw_file is not None and self.settings.simulation.raw_file.exists(): self.psse.read(0, str(self.settings.simulation.raw_file)) else: msg = "Please pass a RAW or SAV file in the settings dictionary" @@ -324,7 +324,7 @@ def run(self): bokeh_server_proc = None logger.debug( - f"Running dynamic simulation for time {self.settings.simulation.simulation_time.total_seconds()} sec" + f"Running {self.settings.simulation.simulation_mode.value} simulation for time {self.settings.simulation.simulation_time.total_seconds()} sec" ) total_simulation_time = ( self.settings.simulation.simulation_time.total_seconds() @@ -440,6 +440,7 @@ def update_result_container(self, t: float) -> dict: """ if self.export_settings.defined_subsystems_only: + logger.debug(f"self.exp_vars : {self.exp_vars}") curr_results = self.sim.read_subsystems( self.exp_vars, self.all_subsysten_buses ) @@ -450,6 +451,7 @@ def update_result_container(self, t: float) -> dict: if not USING_NAERM: if not self.export_settings.export_results_using_channels: + # logger.debug(f"curr_results : {curr_results}") self.results.update( curr_results, t, diff --git a/pypsse/utils/dynamic_utils.py b/pypsse/utils/dynamic_utils.py index cf1b7e7..66e4d43 100644 --- a/pypsse/utils/dynamic_utils.py +++ b/pypsse/utils/dynamic_utils.py @@ -299,7 +299,7 @@ def setup_load_channels(self, loads: list): self.channel_map["LOAD_Q"][f"{b}_{ld}"] = [self.chnl_idx + 1] self.psse.load_array_channel([self.chnl_idx, 1, int(b)], ld, "") self.psse.load_array_channel([self.chnl_idx + 1, 2, int(b)], ld, "") - # logger.info(f"P and Q for load {b}_{ld} added to channel {self.chnl_idx} and {self.chnl_idx + 1}") + logger.info(f"P and Q for load {b}_{ld} added to channel {self.chnl_idx} and {self.chnl_idx + 1}") self.chnl_idx += 2 def setup_bus_channels(self, buses: list, properties: list): From 45e9428a735adce8b20a0c81752abf5f24997880 Mon Sep 17 00:00:00 2001 From: Fuhong Xie Date: Wed, 3 Sep 2025 13:49:44 -0600 Subject: [PATCH 03/14] integrate aggregatedderapp --- pypsse/enumerations.py | 4 ++ pypsse/models.py | 5 ++- pypsse/modes/abstract_mode.py | 5 ++- pypsse/modes/snap.py | 5 ++- pypsse/profile_manager/profile.py | 4 +- pypsse/simulation_controller.py | 3 +- pypsse/simulator.py | 22 +++------- pypsse/utils/dynamic_utils.py | 68 ++++++++++++++++++++++++++----- 8 files changed, 85 insertions(+), 31 deletions(-) diff --git a/pypsse/enumerations.py b/pypsse/enumerations.py index 4a6c37c..21377c8 100644 --- a/pypsse/enumerations.py +++ b/pypsse/enumerations.py @@ -28,6 +28,10 @@ class SimulationModes(str, Enum): STATIC = "Steady-state" DYNAMIC = "Dynamic" +class GenerationLevel(str, Enum): + "Valid generation level setting modes" + TRANSMISSION = "transmission" + DISTRIBUTION = "distribution" class HelicsCoreTypes(str, Enum): "HELICS core types" diff --git a/pypsse/models.py b/pypsse/models.py index 8631be0..f50b11e 100644 --- a/pypsse/models.py +++ b/pypsse/models.py @@ -33,6 +33,7 @@ UseModes, WritableModelTypes, ZoneProperties, + GenerationLevel, ) @@ -55,6 +56,9 @@ class SimSettings(BaseModel): user_models: List[str] = [] setup_files: List[str] = [] simulation_mode: SimulationModes + disable_generation_on_coupled_buses: bool = True + generation_model_level: GenerationLevel + generation_std: str = "psse" @model_validator(mode="after") def sim_res_smaller_than_sim_time(self): @@ -173,7 +177,6 @@ class HelicsSettings(BaseModel): max_coiterations: int = Field(15, ge=1) broker_ip: IPvAnyAddress = "127.0.0.1" broker_port: int = 23404 - disable_generation_on_coupled_buses: bool = True publications: List[PublicationDefination] diff --git a/pypsse/modes/abstract_mode.py b/pypsse/modes/abstract_mode.py index 299ad5a..f1b9240 100644 --- a/pypsse/modes/abstract_mode.py +++ b/pypsse/modes/abstract_mode.py @@ -15,6 +15,7 @@ def __init__( self, psse, dyntools, + der, settings: SimulationSettings, export_settings: ExportFileOptions, subsystem_buses, @@ -34,6 +35,7 @@ def __init__( self.sub_buses = subsystem_buses self.dyntools = dyntools + self.der = der self.settings = settings self.export_settings = export_settings self.func_options = { @@ -1272,7 +1274,8 @@ def update_object(self, dtype, bus, element_id, values: dict): ierr = 1 if ierr == 0: - logger.info(f"Profile Manager: {dtype} '{element_id}' on bus '{bus}' has been updated. {values}") + # logger.info(f"Profile Manager: {dtype} '{element_id}' on bus '{bus}' has been updated. {values}") + pass else: logger.error(f"Profile Manager: Error updating {dtype} '{element_id}' on bus '{bus}'.") diff --git a/pypsse/modes/snap.py b/pypsse/modes/snap.py index 81c6e69..81bbc16 100644 --- a/pypsse/modes/snap.py +++ b/pypsse/modes/snap.py @@ -15,12 +15,13 @@ def __init__( self, psse, dyntools, + der, settings: SimulationSettings, export_settings: ExportFileOptions, subsystem_buses, raw_data, ): - super().__init__(psse, dyntools, settings, export_settings, subsystem_buses, raw_data) + super().__init__(psse, dyntools, der, settings, export_settings, subsystem_buses, raw_data) self.time = settings.simulation.start_time self._StartTime = settings.simulation.start_time self.incTime = settings.simulation.simulation_step_resolution @@ -87,8 +88,10 @@ def init(self, bus_subsystems): def step(self, t): "Increments the simulation" + logger.debug(f"snap.py step : {t}") self.time = self.time + self.incTime self.xTime = 0 + self.der.update_ibr() return self.psse.run(0, t + self.incTime.total_seconds(), 1, 1, 1) def resolve_step(self, t): diff --git a/pypsse/profile_manager/profile.py b/pypsse/profile_manager/profile.py index 1cbaa00..126cfe1 100644 --- a/pypsse/profile_manager/profile.py +++ b/pypsse/profile_manager/profile.py @@ -65,8 +65,10 @@ def update(self, update_object_properties=True): else: value_f = value * mult value_f = self.fill_missing_values(value_f) + # if self.dtype == "Machine": + # value_f["realar1"] = value_f["realar1"] self.solver.update_object(self.dtype, bus, object_id, value_f) - logger.debug(f"Object updated: {object_id}.{bus}.{self.dtype}={value_f}") + # logger.debug(f"Object updated: {object_id}.{bus}.{self.dtype}={value_f}") return value def fill_missing_values(self, value): diff --git a/pypsse/simulation_controller.py b/pypsse/simulation_controller.py index a59086a..a27d60d 100644 --- a/pypsse/simulation_controller.py +++ b/pypsse/simulation_controller.py @@ -13,6 +13,7 @@ def sim_controller( psse: object, dyntools: object, + der: object, settings: SimulationSettings, export_settings: ExportFileOptions, subsystem_buses: dict, @@ -35,7 +36,7 @@ def sim_controller( sim_modes = {"Dynamic": Dynamic, "Steady-state": Static, "Snap": Snap, "ProductionCostModel": ProductionCostModel} # print("0000000000000000000000000000000000000000000000000000") sim = sim_modes[settings.simulation.simulation_mode.value]( - psse, dyntools, settings, export_settings, subsystem_buses, raw_data + psse, dyntools, der, settings, export_settings, subsystem_buses, raw_data ) # print("33333333333333333333333333333333333333333333333333333") logger.debug(f"Simulator contoller of type {settings.simulation.simulation_mode.value} created") diff --git a/pypsse/simulator.py b/pypsse/simulator.py index d0d5997..19aa530 100644 --- a/pypsse/simulator.py +++ b/pypsse/simulator.py @@ -50,6 +50,8 @@ from pypsse.enumerations import PSSE_VERSIONS from pypsse.utils.dynamic_utils import DynamicUtils +from aggregatedderapp import App + USING_NAERM = 0 N_BUS = 200000 @@ -107,6 +109,7 @@ def __init__( self.dyntools = dyntools self.psse = psspy + self.der = App(showProgress=True) # logger.debug('Initializing PSS/E. connecting to license server') self.start_simulation() @@ -190,6 +193,7 @@ def start_simulation(self): self.sim = sc.sim_controller( self.psse, self.dyntools, + self.der, self.settings, self.export_settings, valid_buses, @@ -389,20 +393,6 @@ def step(self, t: float) -> dict: # logger.debug(f"Time granted: {helics_time}") self.update_subscriptions() - # pypsse_update_agian = "y" - # while pypsse_update_agian != "no": - # pypsse_update_agian = input('enter your pypsse update command: ') - # if pypsse_update_agian == "y": - # if self.settings.helics and self.settings.helics.cosimulation_mode: - # if self.settings.helics.create_subscriptions: - # self.update_subscriptions() - # logger.debug(f"Time requested: {t}") - # self.inc_time, helics_time = self.update_federate_time(t) - # logger.debug(f"Time granted: {helics_time}") - # elif pypsse_update_agian == "u": - # if self.settings.helics and self.settings.helics.cosimulation_mode: - # self.publish_data() - if self.inc_time: logger.debug(f"run PSSE simulation at time: {t}") self.sim.step(t) @@ -440,7 +430,7 @@ def update_result_container(self, t: float) -> dict: """ if self.export_settings.defined_subsystems_only: - logger.debug(f"self.exp_vars : {self.exp_vars}") + # logger.debug(f"self.exp_vars : {self.exp_vars}") curr_results = self.sim.read_subsystems( self.exp_vars, self.all_subsysten_buses ) @@ -493,7 +483,7 @@ def get_results(self, params: Union[ExportAssetTypes, dict]) -> dict: Returns: dict: simulation results """ - + logger.debug(f"self.exp_vars : {self.exp_vars}") self._status = SimulationStatus.STARTING_RESULT_EXPORT self.exp_vars = self.results.update_export_variables(params) curr_results = ( diff --git a/pypsse/utils/dynamic_utils.py b/pypsse/utils/dynamic_utils.py index 66e4d43..b3b737b 100644 --- a/pypsse/utils/dynamic_utils.py +++ b/pypsse/utils/dynamic_utils.py @@ -6,7 +6,7 @@ from pypsse.common import MACHINE_CHANNELS from pypsse.modes.constants import dyn_only_options - +import re class DynamicUtils: "Utility functions for dynamic simulations" @@ -15,15 +15,13 @@ class DynamicUtils: def disable_generation_for_coupled_buses(self): """Disables generation of coupled buses (co-simulation mode only)""" - if ( - self.settings.helics - and self.settings.helics.cosimulation_mode - and self.settings.helics.disable_generation_on_coupled_buses - ): + if ((self.settings.helics and self.settings.helics.cosimulation_mode and self.settings.simulation.disable_generation_on_coupled_buses) # cosim mode, load in distribution level + or (self.settings.simulation.disable_generation_on_coupled_buses and self.settings.simulation.generation_model_level.lower() == "transmission" and self.settings.simulation.generation_std.lower() == "ieee2800")): sub_data = pd.read_csv(self.settings.simulation.subscriptions_file) sub_data = sub_data[sub_data["element_type"] == "Load"] generators = {} generator_list = {} + generator_data = {} for gen_bus, gen_id in self.raw_data.generators: if gen_bus not in generator_list: @@ -56,10 +54,54 @@ def disable_generation_for_coupled_buses(self): self._f, self._f, ] + ierr, machine_pg = self.psse.macdat(bus_id,machine,'P') + ierr, machine_qg = self.psse.macdat(bus_id,machine,'Q') + ierr, machine_base = self.psse.macdat(bus_id,machine,'MBASE') + if ierr == 0: + generator_data[f"{bus_id}_{machine}"] = { + "pg": machine_pg, + "qg": machine_qg, + "base": machine_base + } self.psse.machine_chng_2(bus_id, machine, intgar, realar) - logger.info(f"Machine disabled: {bus_id}_{machine}") + logger.info(f"Machine disabled: {bus_id}_{machine} (pg={machine_pg},qg={machine_qg},base={machine_base})") + + if self.settings.simulation.generation_model_level.lower() == "transmission": + # generation is still in transmission level but use different modeling method + if self.settings.simulation.generation_std.lower() == "ieee2800": + logger.debug(f"Added IBR with IEEE STD 2800") + for bus_id, machines in generators.items(): + for machine in machines: + machine_id = f"{bus_id}_{machine}" + if machine_id in generator_data.keys(): + ibr_data = generator_data[f"{bus_id}_{machine}"] + pg = ibr_data["pg"] + qg = ibr_data["qg"] + base = ibr_data["base"] + ibr_id = f"ir" + logger.info(f"IBR added: {bus_id}_{machine} (pg={machine_pg},qg={machine_qg},base={machine_base})") + ibr_dt = self.settings.simulation.simulation_step_resolution.total_seconds() + self.der.add_ibr([bus_id],[pg],[qg],[ibr_id],ibr_dt) + # logger.info("##################### add channel #########################") + # self.update_loadchannel_asset_list("loads", [ibr_id, f"{bus_id}"]) + else: + # TODO + raise Exception("STD has not been implemented") + # os.system("PAUSE") + def update_loadchannel_asset_list(self,assset_type, asset): + for n in range(len(self.export_settings.channel_setup)): + channel = self.export_settings.channel_setup[n] + method_type = channel.asset_type + if method_type == assset_type: + logger.info(channel) + asset_list = channel.asset_list + if asset not in asset_list: + asset_list.append(asset) + self.export_settings.channel_setup[n].asset_list = asset_list + + def disable_load_models_for_coupled_buses(self): """Disables loads of coupled buses (co-simulation mode only)""" if self.settings.helics and self.settings.helics.cosimulation_mode: @@ -293,12 +335,13 @@ def setup_load_channels(self, loads: list): if "LOAD_P" not in self.channel_map: self.channel_map["LOAD_P"] = {} self.channel_map["LOAD_Q"] = {} - + # logger.info(f"loads {loads}") for ld, b in loads: + logger.info(f"P and Q for load {b}_{ld}") self.channel_map["LOAD_P"][f"{b}_{ld}"] = [self.chnl_idx] self.channel_map["LOAD_Q"][f"{b}_{ld}"] = [self.chnl_idx + 1] - self.psse.load_array_channel([self.chnl_idx, 1, int(b)], ld, "") - self.psse.load_array_channel([self.chnl_idx + 1, 2, int(b)], ld, "") + ierr = self.psse.load_array_channel([self.chnl_idx, 1, int(b)], ld, ""); assert ierr == 0, f"error={ierr}" + ierr = self.psse.load_array_channel([self.chnl_idx + 1, 2, int(b)], ld, ""); assert ierr == 0, f"error={ierr}" logger.info(f"P and Q for load {b}_{ld} added to channel {self.chnl_idx} and {self.chnl_idx + 1}") self.chnl_idx += 2 @@ -333,6 +376,7 @@ def poll_channels(self) -> dict: """ results = {} + # logger.info(f"self.channel_map: {self.channel_map}") for ppty, b_dict in self.channel_map.items(): ppty_new = ppty.split("_and_") for b, indices in b_dict.items(): @@ -357,6 +401,8 @@ def setup_all_channels(self): self.chnl_idx = 1 if not self.export_settings.channel_setup: return + ierr = self.psse.delete_all_plot_channels(); assert ierr == 0, f"error={ierr}" + # logger.info(f"self.export_settings.channel_setup {self.export_settings.channel_setup}") for channel in self.export_settings.channel_setup: method_type = channel.asset_type @@ -368,3 +414,5 @@ def setup_all_channels(self): elif method_type == "machines": machine_list = [[x, int(y)] for x, y in channel.asset_list] self.setup_machine_channels(machine_list, channel.asset_properties) + + From 47c3e56c9194e5ec2ecf91a326a9420680439c7a Mon Sep 17 00:00:00 2001 From: Fuhong Xie Date: Tue, 9 Sep 2025 23:48:31 -0600 Subject: [PATCH 04/14] update steady-state cosim --- pypsse/modes/abstract_mode.py | 4 ++-- pypsse/modes/dynamic.py | 3 ++- pypsse/modes/pcm.py | 4 ++-- pypsse/modes/static.py | 4 ++-- pypsse/profile_manager/profile.py | 2 +- pypsse/utils/dynamic_utils.py | 1 - 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/pypsse/modes/abstract_mode.py b/pypsse/modes/abstract_mode.py index f1b9240..a6c34ef 100644 --- a/pypsse/modes/abstract_mode.py +++ b/pypsse/modes/abstract_mode.py @@ -1269,7 +1269,7 @@ def update_object(self, dtype, bus, element_id, values: dict): elif dtype == WritableModelTypes.MACHINE.value: ierr = self.psse.machine_data_2(i=int(bus), id=element_id, **values) elif dtype == WritableModelTypes.PLANT.value: - ierr = self.psse.plant_data_4(ibus=int(bus), inode=element_id, **values) + ierr = self.psse.plant_data_4(ibus=int(bus), inode=0, intgar=[self._i, self._i], **values) else: ierr = 1 @@ -1277,7 +1277,7 @@ def update_object(self, dtype, bus, element_id, values: dict): # logger.info(f"Profile Manager: {dtype} '{element_id}' on bus '{bus}' has been updated. {values}") pass else: - logger.error(f"Profile Manager: Error updating {dtype} '{element_id}' on bus '{bus}'.") + logger.error(f"Profile Manager: Error updating {dtype} '{element_id}' on bus '{bus}'. Error code is {ierr}") def has_converged(self): return self.psse.solved() diff --git a/pypsse/modes/dynamic.py b/pypsse/modes/dynamic.py index 91475ae..f8236cb 100644 --- a/pypsse/modes/dynamic.py +++ b/pypsse/modes/dynamic.py @@ -13,12 +13,13 @@ def __init__( self, psse, dyntools, + der, settings: SimulationSettings, export_settings: ExportFileOptions, subsystem_buses, raw_data, ): - super().__init__(psse, dyntools, settings, export_settings, subsystem_buses, raw_data) + super().__init__(psse, dyntools, der, settings, export_settings, subsystem_buses, raw_data) self.time = settings.simulation.start_time self._StartTime = settings.simulation.start_time self.incTime = settings.simulation.simulation_step_resolution diff --git a/pypsse/modes/pcm.py b/pypsse/modes/pcm.py index caf5b60..ef01f51 100644 --- a/pypsse/modes/pcm.py +++ b/pypsse/modes/pcm.py @@ -10,8 +10,8 @@ class ProductionCostModel(AbstractMode): - def __init__(self, psse, dyntools, settings, export_settings, subsystem_buses, raw_data): - super().__init__(psse, dyntools, settings, export_settings, subsystem_buses, raw_data) + def __init__(self, psse, dyntools, der, settings, export_settings, subsystem_buses, raw_data): + super().__init__(psse, dyntools, der, settings, export_settings, subsystem_buses, raw_data) self.time = datetime.datetime.strptime(settings["Simulation"]["Start time"], "%m/%d/%Y %H:%M:%S").astimezone( None ) diff --git a/pypsse/modes/static.py b/pypsse/modes/static.py index 0368aeb..ecf4c2d 100644 --- a/pypsse/modes/static.py +++ b/pypsse/modes/static.py @@ -8,10 +8,10 @@ class Static(AbstractMode): - def __init__(self, psse, dyntools, settings, export_settings, subsystem_buses, raw_data): + def __init__(self, psse, dyntools, der, settings, export_settings, subsystem_buses, raw_data): "Class defination for steady-state simulation mode" - super().__init__(psse, dyntools, settings, export_settings, subsystem_buses, raw_data) + super().__init__(psse, dyntools, der, settings, export_settings, subsystem_buses, raw_data) self.time = settings.simulation.start_time self._StartTime = settings.simulation.start_time self.incTime = settings.simulation.simulation_step_resolution diff --git a/pypsse/profile_manager/profile.py b/pypsse/profile_manager/profile.py index 126cfe1..fc2dd79 100644 --- a/pypsse/profile_manager/profile.py +++ b/pypsse/profile_manager/profile.py @@ -68,7 +68,7 @@ def update(self, update_object_properties=True): # if self.dtype == "Machine": # value_f["realar1"] = value_f["realar1"] self.solver.update_object(self.dtype, bus, object_id, value_f) - # logger.debug(f"Object updated: {object_id}.{bus}.{self.dtype}={value_f}") + logger.info(f"Object updated: {object_id}.{bus}.{self.dtype}={value_f}") return value def fill_missing_values(self, value): diff --git a/pypsse/utils/dynamic_utils.py b/pypsse/utils/dynamic_utils.py index b3b737b..490ff15 100644 --- a/pypsse/utils/dynamic_utils.py +++ b/pypsse/utils/dynamic_utils.py @@ -82,7 +82,6 @@ def disable_generation_for_coupled_buses(self): logger.info(f"IBR added: {bus_id}_{machine} (pg={machine_pg},qg={machine_qg},base={machine_base})") ibr_dt = self.settings.simulation.simulation_step_resolution.total_seconds() self.der.add_ibr([bus_id],[pg],[qg],[ibr_id],ibr_dt) - # logger.info("##################### add channel #########################") # self.update_loadchannel_asset_list("loads", [ibr_id, f"{bus_id}"]) else: # TODO From a9ed15a71c455098880d5db6601b9e8885ee7103 Mon Sep 17 00:00:00 2001 From: Fuhong Xie Date: Fri, 19 Sep 2025 11:03:04 -0600 Subject: [PATCH 05/14] update IBR app --- pypsse/models.py | 81 ++++++++++++++++++++++++++- pypsse/modes/snap.py | 8 ++- pypsse/simulator.py | 3 +- pypsse/utils/dynamic_utils.py | 100 +++++++++++++++++++++++----------- 4 files changed, 155 insertions(+), 37 deletions(-) diff --git a/pypsse/models.py b/pypsse/models.py index f50b11e..8633776 100644 --- a/pypsse/models.py +++ b/pypsse/models.py @@ -58,7 +58,8 @@ class SimSettings(BaseModel): simulation_mode: SimulationModes disable_generation_on_coupled_buses: bool = True generation_model_level: GenerationLevel - generation_std: str = "psse" + transmission_ibrs: List[str] = [] + transmission_ibrs_std: List[str] = [] @model_validator(mode="after") def sim_res_smaller_than_sim_time(self): @@ -486,3 +487,81 @@ class ApiWebSocketRequest(BaseModel): class Contingencies(BaseModel): contingencies: List[Union[BusFault, BusTrip, LineFault, LineTrip, MachineTrip]] + + +class REGCA1_data_model(BaseModel): + iqmax: float=1 + iqmin: float=-1 + Tg: float=0.02 + xf: float=0.25 + + +class REECA1_data_model(BaseModel): + ppriorityflag:bool=False + Trv:float=0.02 + dbd1:float=-0.02 + dbd2:float=0.02 + Kqv:float=1.0 + iqh1:float=1.2 + iql1:float=-1.2 + Vref:float=1.0 + Tiq:float=0.02 + dpmin:float=-1.2 + dpmax:float=1.2 + Pmax:float=2 + Pmin:float=0 + imax:float=1.2 + Tpord:float=0.02 + ipmax:float=1.2 + ipmin:float=0 + iqmax:float=1.2 + iqmin:float=-1.2 + +class REPCA1_data_model(BaseModel): + refflag:int=0 + fflag:bool=True + Tfilt:float=0.02 + Kp:float=1 + Ki:float=1 + Tft:float=0.02 + Tfv:float=0.05 + emax:float=2 + emin:float=-2 + dbd1:float=-0.02 + dbd2:float=0.02 + qmax:float=2 + qmin:float=-2 + Kpg:float=1 + Kig:float=1 + fdbd1:float=-0.02 + fdbd2:float=0.02 + femax:float=1.2 + femin:float=0 + pmax:float=2 + pmin:float=0 + Tg:float=0.02 + + + +class FastDER_data_model(BaseModel): + Trv:float=0.02 + dbd1:float=-0.025 + dbd2:float=0.025 + Kqv:float=0.02 + Tiq:float=0.02 + Kpg:float=1.0 + Kig:float=1.0 + fdbd1:float=-0.03 + fdbd2:float=0.03 + Trf:float=0.02 + Tpord:float=0.02 + Tg:float=0.02 + iqmax:float=2.0 + iqmin:float=-2.0 + ipmax:float=2.0 + ipmin:float=-2.0 + femax:float=0.2 + femin:float=-0.2 + imax:float=2.0 + ppriorityflag:bool=True + xf: float=0.25 \ No newline at end of file diff --git a/pypsse/modes/snap.py b/pypsse/modes/snap.py index 81bbc16..a55a30f 100644 --- a/pypsse/modes/snap.py +++ b/pypsse/modes/snap.py @@ -52,17 +52,19 @@ def init(self, bus_subsystems): outx_file[-1] = self.export_settings.filename_prefix + "_" + outx_file[-1] outx_file = "\\".join(outx_file) + self.load_user_defined_models() ierr = self.psse.strt_2([0, 1], outx_file) if ierr == 1: self.psse.cong(0) ierr = self.psse.strt_2([0, 1], outx_file) + assert ierr==0, f"error of strt_2 wwith code {ierr}" elif ierr > 1: msg = "Error starting simulation" raise Exception(msg) - self.load_user_defined_models() + # self.load_user_defined_models() if self.settings.helics and self.settings.helics.cosimulation_mode: if self.settings.helics.iterative_mode: @@ -91,7 +93,9 @@ def step(self, t): logger.debug(f"snap.py step : {t}") self.time = self.time + self.incTime self.xTime = 0 - self.der.update_ibr() + if self.settings.simulation.disable_generation_on_coupled_buses and self.settings.simulation.generation_model_level.lower() == "transmission" and len(self.settings.simulation.transmission_ibrs) > 0: + # iff transmission DER model is disable and ibr is defined + self.der.update_ibr() return self.psse.run(0, t + self.incTime.total_seconds(), 1, 1, 1) def resolve_step(self, t): diff --git a/pypsse/simulator.py b/pypsse/simulator.py index 19aa530..9195c61 100644 --- a/pypsse/simulator.py +++ b/pypsse/simulator.py @@ -343,7 +343,8 @@ def run(self): if t >= total_simulation_time: break - self.psse.pssehalt_2() + ierr = self.psse.pssehalt_2() + assert ierr == 0, f"pssehalt_2 error code: {ierr}" if not self.export_settings.export_results_using_channels: self.results.export_results() else: diff --git a/pypsse/utils/dynamic_utils.py b/pypsse/utils/dynamic_utils.py index 490ff15..96267b1 100644 --- a/pypsse/utils/dynamic_utils.py +++ b/pypsse/utils/dynamic_utils.py @@ -7,6 +7,8 @@ from pypsse.common import MACHINE_CHANNELS from pypsse.modes.constants import dyn_only_options import re +import toml +from pypsse.models import REGCA1_data_model, REECA1_data_model, REPCA1_data_model class DynamicUtils: "Utility functions for dynamic simulations" @@ -16,7 +18,7 @@ class DynamicUtils: def disable_generation_for_coupled_buses(self): """Disables generation of coupled buses (co-simulation mode only)""" if ((self.settings.helics and self.settings.helics.cosimulation_mode and self.settings.simulation.disable_generation_on_coupled_buses) # cosim mode, load in distribution level - or (self.settings.simulation.disable_generation_on_coupled_buses and self.settings.simulation.generation_model_level.lower() == "transmission" and self.settings.simulation.generation_std.lower() == "ieee2800")): + or (self.settings.simulation.disable_generation_on_coupled_buses and self.settings.simulation.generation_model_level.lower() == "transmission")): sub_data = pd.read_csv(self.settings.simulation.subscriptions_file) sub_data = sub_data[sub_data["element_type"] == "Load"] generators = {} @@ -54,40 +56,72 @@ def disable_generation_for_coupled_buses(self): self._f, self._f, ] - ierr, machine_pg = self.psse.macdat(bus_id,machine,'P') - ierr, machine_qg = self.psse.macdat(bus_id,machine,'Q') - ierr, machine_base = self.psse.macdat(bus_id,machine,'MBASE') - if ierr == 0: - generator_data[f"{bus_id}_{machine}"] = { - "pg": machine_pg, - "qg": machine_qg, - "base": machine_base - } self.psse.machine_chng_2(bus_id, machine, intgar, realar) - logger.info(f"Machine disabled: {bus_id}_{machine} (pg={machine_pg},qg={machine_qg},base={machine_base})") + logger.info(f"Machine disabled: {bus_id}_{machine}") - if self.settings.simulation.generation_model_level.lower() == "transmission": - # generation is still in transmission level but use different modeling method - if self.settings.simulation.generation_std.lower() == "ieee2800": - logger.debug(f"Added IBR with IEEE STD 2800") - for bus_id, machines in generators.items(): - for machine in machines: - machine_id = f"{bus_id}_{machine}" - if machine_id in generator_data.keys(): - ibr_data = generator_data[f"{bus_id}_{machine}"] - pg = ibr_data["pg"] - qg = ibr_data["qg"] - base = ibr_data["base"] - ibr_id = f"ir" - logger.info(f"IBR added: {bus_id}_{machine} (pg={machine_pg},qg={machine_qg},base={machine_base})") - ibr_dt = self.settings.simulation.simulation_step_resolution.total_seconds() - self.der.add_ibr([bus_id],[pg],[qg],[ibr_id],ibr_dt) - # self.update_loadchannel_asset_list("loads", [ibr_id, f"{bus_id}"]) - else: - # TODO - raise Exception("STD has not been implemented") - - # os.system("PAUSE") + logger.info(f"generator_data: {generator_data})") + logger.info(f"generators: {generators})") + if self.settings.simulation.generation_model_level.lower() == "transmission" and len(self.settings.simulation.transmission_ibrs) > 0: + intgar = [0, self._i, self._i, self._i, self._i, self._i] + realar = [ + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + self._f, + ] + transmission_ibrs_id = self.settings.simulation.transmission_ibrs + transmission_ibrs_std = self.settings.simulation.transmission_ibrs_std + assert len(transmission_ibrs_id)==len(transmission_ibrs_std), f"Input dimension mismatch" + # added by FX for IBR temporarily + ibr_setting_path = r"C:\Users\FXIE\Documents\GitHub\NEARM_TnD\dynamic_cosim_test_cases-model\WECC\Transmission\pypsse_model\Scenarios\IEEE2800\IBR.toml" + ibr_settings = toml.load(ibr_setting_path) + for ibr, std in zip(transmission_ibrs_id, transmission_ibrs_std): + # generation is still in transmission level but use different modeling method + if std.lower() == "ieee2800": + logger.debug(f"Added IBR with IEEE STD 2800 for generator {ibr}") + bus_id, machine = ibr.split("_") + ierr, machine_pg = self.psse.macdat(int(bus_id),machine,'P') + ierr, machine_qg = self.psse.macdat(int(bus_id),machine,'Q') + ierr, machine_base = self.psse.macdat(int(bus_id),machine,'MBASE') + ierr, machine_status = self.psse.macint(int(bus_id),machine,'STATUS') + if ibr in ibr_settings.keys(): + ibr_setting = ibr_settings[ibr] + logger.debug(f"REPCA1: {ibr_setting['REPCA1']}") + param_repca1 = REPCA1_data_model(**ibr_setting["REPCA1"]) + param_reeca1 = REECA1_data_model(**ibr_setting["REECA1"]) + param_regca1 = REGCA1_data_model(**ibr_setting["REGCA1"]) + else: + param_repca1 = None + param_reeca1 = None + param_regca1 = None + if ierr == 0 and machine_status == 1: + self.psse.machine_chng_2(int(bus_id), machine, intgar, realar) + ibr_id = f"ir" + logger.info(f"IBR added: {bus_id}_{machine} (pg={machine_pg},qg={machine_qg},base={machine_base})") + ibr_dt = self.settings.simulation.simulation_step_resolution.total_seconds() + self.der.add_ibr([int(bus_id)],[machine_pg],[machine_qg],[ibr_id],ibr_dt,param_repca1,param_reeca1,param_regca1) + else: + logger.warning(f"Can't add IBR: {bus_id}_{machine})") + else: + # todo + raise Exception("Standard has not been implemented") + logger.debug(f"self.der.ibr['model']: {self.der.ibr['model']}") + + # + # os.system("PAUSE") def update_loadchannel_asset_list(self,assset_type, asset): for n in range(len(self.export_settings.channel_setup)): From 12fe92976f6d0c6559e6de74dfea1949675ea4aa Mon Sep 17 00:00:00 2001 From: Fuhong Xie Date: Tue, 7 Oct 2025 23:44:44 -0600 Subject: [PATCH 06/14] add profile of status --- pypsse/enumerations.py | 3 +++ pypsse/modes/abstract_mode.py | 25 +++++++++++++++++++------ pypsse/profile_manager/common.py | 6 ++++++ pypsse/profile_manager/profile.py | 11 +++++++++-- pypsse/profile_manager/profile_store.py | 1 + 5 files changed, 38 insertions(+), 8 deletions(-) diff --git a/pypsse/enumerations.py b/pypsse/enumerations.py index 21377c8..7b07ccc 100644 --- a/pypsse/enumerations.py +++ b/pypsse/enumerations.py @@ -44,6 +44,9 @@ class WritableModelTypes(str, Enum): PLANT = "Plant" MACHINE = "Machine" GENERATOR = "Induction_machine" + LOAD_STATUS = "Load_status" + LINE_STATUS = "Line_status" + MACHINE_STATUS = "Machine_status" class ModelTypes(str, Enum): diff --git a/pypsse/modes/abstract_mode.py b/pypsse/modes/abstract_mode.py index a6c34ef..1cc1936 100644 --- a/pypsse/modes/abstract_mode.py +++ b/pypsse/modes/abstract_mode.py @@ -1258,16 +1258,29 @@ def convert_load(self, bus_subsystem=None): def update_object(self, dtype, bus, element_id, values: dict): val = sum(list(values.values())) if val > -VALUE_UPDATE_BOUND and val < VALUE_UPDATE_BOUND: - if dtype == WritableModelTypes.LOAD.value: + if dtype == WritableModelTypes.LOAD.value or dtype == WritableModelTypes.LOAD_STATUS.value: + # ierr = self.psse.load_chng_5(ibus=int(bus), id=element_id, **values) + # elif dtype == WritableModelTypes.LOAD_STATUS.value: + old_load = self.psse.lodint(ibus=int(bus), id=element_id, string='STATUS') + logger.info(f"old load {bus}-{element_id}: {old_load}") + old_load = self.psse.loddt2(ibus=int(bus), id=element_id, string1='MVA', string2='ACT') + logger.info(f"old load MVA {bus}-{element_id}: {old_load}") ierr = self.psse.load_chng_5(ibus=int(bus), id=element_id, **values) - # ierr, cmpval = self.psse.loddt2(int(bus),element_id,'MVA','ACT') - # if ierr == 0: - # logger.info(f"recheck load value: {cmpval}") - # os.system("PAUSE") + old_load = self.psse.lodint(ibus=int(bus), id=element_id, string='STATUS') + logger.info(f"new load {bus}-{element_id}: {old_load}") + old_load = self.psse.loddt2(ibus=int(bus), id=element_id, string1='MVA', string2='ACT') + logger.info(f"old load MVA {bus}-{element_id}: {old_load}") elif dtype == WritableModelTypes.GENERATOR.value: ierr = self.psse.induction_machine_data(ibus=int(bus), id=element_id, **values) - elif dtype == WritableModelTypes.MACHINE.value: + elif dtype == WritableModelTypes.MACHINE.value or dtype == WritableModelTypes.MACHINE_STATUS.value: ierr = self.psse.machine_data_2(i=int(bus), id=element_id, **values) + elif dtype == WritableModelTypes.LINE_STATUS.value: + frombus, tobus = bus.split("_") + old_load = self.psse.brnint(ibus=int(frombus), jbus=int(tobus), ickt=element_id, string='STATUS') + logger.info(f"old line status {bus}-{element_id}: {old_load}") + ierr = self.psse.branch_data_3(ibus=int(frombus), jbus=int(tobus), ckt=element_id, **values) + old_load = self.psse.brnint(ibus=int(frombus), jbus=int(tobus), ickt=element_id, string='STATUS') + logger.info(f"new line status {bus}-{element_id}: {old_load}") elif dtype == WritableModelTypes.PLANT.value: ierr = self.psse.plant_data_4(ibus=int(bus), inode=0, intgar=[self._i, self._i], **values) else: diff --git a/pypsse/profile_manager/common.py b/pypsse/profile_manager/common.py index 638bc94..2f6433d 100644 --- a/pypsse/profile_manager/common.py +++ b/pypsse/profile_manager/common.py @@ -5,6 +5,9 @@ class ProfileTypes(str, Enum): INDUCTION_MACHINE = 'Induction_machine' MACHINE = 'Machine' PLANT = "Plant" + LOAD_STATUS = "Load_status" + LINE_STATUS = "Line_status" + MACHINE_STATUS = "Machine_status" PROFILE_VALIDATION = { ProfileTypes.LOAD: ["PL", "QL", "IP", "IQ", "YP", "YQ", "PG", "QG"], @@ -52,6 +55,9 @@ class ProfileTypes(str, Enum): "WPF", ], ProfileTypes.PLANT: ["VS", "RMPCT"], + ProfileTypes.LOAD_STATUS: ["STATUS"], + ProfileTypes.LINE_STATUS: ["STATUS"], + ProfileTypes.MACHINE_STATUS: ["STATUS"], } DEFAULT_PROFILE_NAME = "Default" diff --git a/pypsse/profile_manager/profile.py b/pypsse/profile_manager/profile.py index fc2dd79..f36dd50 100644 --- a/pypsse/profile_manager/profile.py +++ b/pypsse/profile_manager/profile.py @@ -50,10 +50,12 @@ def update(self, update_object_properties=True): dt2 = ( self.time - (self.stime + datetime.timedelta(seconds=int(n * self.attrs["resTime"]))) ).total_seconds() - value1 = value + (valuen1 - value) * dt2 / self.attrs["resTime"] if update_object_properties: for obj_name in self.value_settings: + if self.value_settings[obj_name]["interpolate"]: + value1 = value + (valuen1 - value) * dt2 / self.attrs["resTime"] + # logger.info(f"obj_name: {obj_name}") bus, object_id = obj_name.split("__") if self.value_settings[obj_name]["interpolate"]: value = value1 @@ -63,7 +65,9 @@ def update(self, update_object_properties=True): if self.value_settings[obj_name]["normalize"]: value_f = value / self.attrs["max"] * mult else: + logger.info(f"value: {value}") value_f = value * mult + logger.info(f"value_f: {value_f}") value_f = self.fill_missing_values(value_f) # if self.dtype == "Machine": # value_f["realar1"] = value_f["realar1"] @@ -73,6 +77,9 @@ def update(self, update_object_properties=True): def fill_missing_values(self, value): "Fixes issues in profile data" - idx = [f"realar{PROFILE_VALIDATION[self.dtype].index(c) + 1}" for c in self.columns] + if self.dtype in ["Load","Induction_machine","Machine","Plant"]: + idx = [f"realar{PROFILE_VALIDATION[self.dtype].index(c) + 1}" for c in self.columns] + else: + idx = [f"intgar{PROFILE_VALIDATION[self.dtype].index(c) + 1}" for c in self.columns] x = dict(zip(idx, list(value))) return x diff --git a/pypsse/profile_manager/profile_store.py b/pypsse/profile_manager/profile_store.py index f132c5d..1be34b3 100644 --- a/pypsse/profile_manager/profile_store.py +++ b/pypsse/profile_manager/profile_store.py @@ -80,6 +80,7 @@ def setup_profiles(self): self.profiles[f"{group}/{profile_name}"] = Profile( grp[profile_name], self.solver, mapping_dict ) + logger.info(rf"Group {group} \ data set {profile_name} is added") else: logger.warning(rf"Group {group} \ data set {profile_name} not found in the h5 store") else: From a42e969593c98c3db347dccee484d42e433a1ef6 Mon Sep 17 00:00:00 2001 From: Fuhong Xie Date: Tue, 2 Dec 2025 22:58:37 -0700 Subject: [PATCH 07/14] Update models.py --- pypsse/models.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pypsse/models.py b/pypsse/models.py index 8633776..d66e857 100644 --- a/pypsse/models.py +++ b/pypsse/models.py @@ -490,11 +490,16 @@ class Contingencies(BaseModel): class REGCA1_data_model(BaseModel): - iqmax: float=1 - iqmin: float=-1 + iqmax: float=1.5 + iqmin: float=-1.5 Tg: float=0.02 xf: float=0.25 - + volim0:float=1.0 + volim1:float=1.2 + lvpnt0:float=0.2 + lvpnt1:float=0.8 + khv:float=1.0 + ccflag:bool=False class REECA1_data_model(BaseModel): ppriorityflag:bool=False From e9d264c2a3cc297a5f62f77e447cb7d7384b1479 Mon Sep 17 00:00:00 2001 From: Fuhong Xie Date: Wed, 3 Dec 2025 11:08:17 -0700 Subject: [PATCH 08/14] add support functions --- pypsse/cli/explore.py | 8 +- pypsse/cli/profiles.py | 2 +- pypsse/common.py | 1 - pypsse/contingencies.py | 36 +-- pypsse/enumerations.py | 1 - pypsse/helics_interface.py | 127 ++++++---- pypsse/models.py | 17 +- pypsse/modes/abstract_mode.py | 319 +----------------------- pypsse/modes/snap.py | 18 +- pypsse/profile_manager/profile.py | 11 +- pypsse/profile_manager/profile_store.py | 6 +- pypsse/profile_manager_interface.py | 72 ++++-- pypsse/result_container.py | 2 - pypsse/simulator.py | 15 +- pypsse/utils/dynamic_utils.py | 160 ++++++++---- 15 files changed, 312 insertions(+), 483 deletions(-) diff --git a/pypsse/cli/explore.py b/pypsse/cli/explore.py index 5b975cb..75ff1a6 100644 --- a/pypsse/cli/explore.py +++ b/pypsse/cli/explore.py @@ -94,24 +94,18 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load, msg = "Simulation file not found. Use -s to choose a valid settings file" "if its name differs from the default file name." assert file_path.exists(), msg - # print(1111111111) simulation_settings = toml.load(file_path) simulation_settings = SimulationSettings(**simulation_settings) simulation_settings.helics.cosimulation_mode = False - # print(2222222222) x = Simulator(simulation_settings) - # print(3333333333) buses = set(x.raw_data.buses) - # print(4444444444) quantities = { 'Loads': ['MVA', "IL", "YL", 'FmA', 'FmB', 'FmC', 'FmD', 'Fel', 'PFel'], 'Induction_generators': ['MVA'], 'Machines': ['MVA', 'PERCENT'], } results = x.sim.read_subsystems(quantities, buses) - # print(55555555555555) - # os.system("PAUSE") # print(results.keys()) # print(results["LOAD_P"]) @@ -211,7 +205,7 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load, results=results[(results["total generation [MVA]"] >= gen_lower) & (results["total generation [MVA]"] <= gen_upper)] # print(results) - results.to_csv(export_file_path,index=False) + results.to_csv(export_file_path) logger.info(f"Results exported to {export_file_path.absolute()}") diff --git a/pypsse/cli/profiles.py b/pypsse/cli/profiles.py index c1ff35e..dfe6c92 100644 --- a/pypsse/cli/profiles.py +++ b/pypsse/cli/profiles.py @@ -38,6 +38,6 @@ def get_profiles(project_path, simulations_file=None): if simulation_settings.log.log_to_external_file: log_path = Path(project_path) / "Logs" / "pypsse.log" logger.add(log_path) - logger.info(f"file_path: {file_path}") + profile_interface = ProfileManagerInterface.from_setting_files(file_path) profile_interface.get_profiles() \ No newline at end of file diff --git a/pypsse/common.py b/pypsse/common.py index bf66b2d..8cbea74 100644 --- a/pypsse/common.py +++ b/pypsse/common.py @@ -18,7 +18,6 @@ DEFAULT_PROFILE_STORE_FILENAME = "profiles.hdf5" DEFAULT_RESULTS_FILENAME = "simulation_results.hdf5" - DEFAULT_OUT_FILE = "results.out" DEFAULT_OUTX_FILE = "results.outx" DEFAULT_EXCEL_FILE = "results.xls" diff --git a/pypsse/contingencies.py b/pypsse/contingencies.py index f0faf8f..7b711a0 100644 --- a/pypsse/contingencies.py +++ b/pypsse/contingencies.py @@ -46,7 +46,14 @@ def __init__(self, psse, settings, contingency_type): self.psse = psse self.enabled = False self.tripped = False - + logger.debug( + f"contingency_type : {contingency_type}" + ) + logger.debug( + f"settings : {settings}" + ) + # os.system("PAUSE") + def update(self, t: float): """updates a fault event @@ -55,30 +62,27 @@ def update(self, t: float): """ self.t = t if hasattr(self.settings, "duration"): - if ( - self.settings.time + self.settings.duration - > t - >= self.settings.time - and not self.enabled - ): + if (self.settings.time + self.settings.duration > t >= self.settings.time and not self.enabled): self.enabled = True self.enable_fault() - if ( - t >= self.settings.time + self.settings.duration - and self.enabled - ): + if (t >= self.settings.time + self.settings.duration and self.enabled): self.enabled = False self.disable_fault() - elif ( - not hasattr(self.settings, "duration") - and t >= self.settings.time - and not self.tripped - ): + elif (not hasattr(self.settings, "duration") and t >= self.settings.time and not self.tripped): self.enable_fault() self.tripped = True def enable_fault(self): """enables a fault event""" + data_check = getattr(self.psse, self.fault_method) + logger.debug( + f"data_check : {data_check}" + ) + logger.debug( + f"data_check : {self.fault_method}" + ) + # os.system("PAUSE") + err = getattr(self.psse, self.fault_method)(**self.fault_settings) if err: logger.warning( diff --git a/pypsse/enumerations.py b/pypsse/enumerations.py index 7b07ccc..11c33a9 100644 --- a/pypsse/enumerations.py +++ b/pypsse/enumerations.py @@ -48,7 +48,6 @@ class WritableModelTypes(str, Enum): LINE_STATUS = "Line_status" MACHINE_STATUS = "Machine_status" - class ModelTypes(str, Enum): "Supported asset tpyes in PyPSSE" BUSES = "Buses" diff --git a/pypsse/helics_interface.py b/pypsse/helics_interface.py index 0abcb3d..29bd9e8 100644 --- a/pypsse/helics_interface.py +++ b/pypsse/helics_interface.py @@ -53,6 +53,21 @@ def __init__( self.subsystem_info = [] self.publications = {} self.subscriptions = {} + self.load_fault = False + ################################################################# + # FX: add these hardcored load value to better matching + self.load_power = { + tid: { + "transmission_loads_at_fault": at_fault, + "transmission_loads_clear_fault": clear_fault + } + for tid, at_fault, clear_fault in zip( + settings.simulation.transmission_ids, + settings.simulation.transmission_loads_at_fault, + settings.simulation.transmission_loads_clear_fault + ) + } + ################################################################# def enter_execution_mode(self): """Enables federate to enter execution mode""" @@ -67,11 +82,16 @@ def enter_execution_mode(self): def create_federate(self): """Creates a HELICS co-simulation federate""" self.fedinfo = h.helicsCreateFederateInfo() + # logger.debug(f"self.fedinfo: {self.fedinfo}") h.helicsFederateInfoSetCoreName(self.fedinfo, self.settings.helics.federate_name) h.helicsFederateInfoSetCoreTypeFromString(self.fedinfo, self.settings.helics.core_type.value) h.helicsFederateInfoSetCoreInitString(self.fedinfo, "--federates=1") h.helicsFederateInfoSetBroker(self.fedinfo, str(self.settings.helics.broker_ip)) h.helicsFederateInfoSetBrokerPort(self.fedinfo, self.settings.helics.broker_port) + IP = self.settings.helics.broker_ip + Port = self.settings.helics.broker_port + logger.info("Connecting to broker @ {}".format(f"{IP}:{Port}" if Port else IP)) + logger.info(f"Connecting to broker @ {self.settings.helics}") if self.settings.helics.iterative_mode: h.helicsFederateInfoSetTimeProperty( @@ -101,6 +121,7 @@ def register_publications(self, bus_subsystems: dict): self.publications = {} self.pub_struc = [] for publication_dict in self.settings.helics.publications: + # logger.debug(f"publication_dict: {publication_dict}") bus_subsystem_ids = publication_dict.bus_subsystems if not set(bus_subsystem_ids).issubset(self.bus_subsystems): msg = f"One or more invalid bus subsystem ID pass in {bus_subsystem_ids}." @@ -116,6 +137,8 @@ def register_publications(self, bus_subsystems: dict): managed_properties = [ptpy.value for ptpy in getattr(self.export_settings, elm_class.lower())] properties = [p.value for p in publication_dict.asset_properties] + # logger.debug(f"managed_properties: {managed_properties}") + # logger.debug(f"properties: {properties}") if not set(properties).issubset(managed_properties): msg = f"One or more publication property defined for class '{elm_class}' is invalid." @@ -134,6 +157,7 @@ def register_publications(self, bus_subsystems: dict): self.pub_struc.append([{elm_class: properties}, bus_cluster]) temp_res = self.sim.read_subsystems({elm_class: properties}, bus_cluster) temp_res = self.get_restructured_results(temp_res) + for c_name, elm_info in temp_res.items(): for name, v_info in elm_info.items(): for p_name, val in v_info.items(): @@ -236,11 +260,12 @@ def register_subscriptions(self): self.psse_dict[row["bus"]][row["element_type"]][element_id] = {} if isinstance(row["element_property"], str): if row["element_property"] not in self.psse_dict[row["bus"]][row["element_type"]][element_id]: - self.psse_dict[row["bus"]][row["element_type"]][element_id][row["element_property"]] = 0 + # FX: modify this so that multiple feeders can be connected to a Transmission Bus + self.psse_dict[row["bus"]][row["element_type"]][element_id][row["element_property"]] = [] elif isinstance(row["element_property"], list): for r in row["element_property"]: if r not in self.psse_dict[row["bus"]][row["element_type"]][element_id]: - self.psse_dict[row["bus"]][row["element_type"]][element_id][r] = 0 + self.psse_dict[row["bus"]][row["element_type"]][element_id][r] = [] def request_time(self, _) -> (bool, float): """Enables time increment of the federate ina co-simulation." @@ -257,23 +282,17 @@ def request_time(self, _) -> (bool, float): r_seconds = self.sim.get_total_seconds() # - self._dss_solver.GetStepResolutionSeconds() logger.info(f"Time requested: {r_seconds}") - # logger.info(f"self.settings.simulation.simulation_step_resolution.total_seconds(): {self.settings.simulation.simulation_step_resolution.total_seconds()}") - # os.system('PAUSE') + if self.sim.get_time() not in self.all_sub_results: self.all_sub_results[self.sim.get_time()] = {} self.all_pub_results[self.sim.get_time()] = {} if not self.settings.helics.iterative_mode: - # logger.info(f"not self.settings.helics.iterative_mode") - # os.system("PAUSE") while self.c_seconds < r_seconds: self.c_seconds = h.helicsFederateRequestTime(self.psse_federate, r_seconds) logger.info(f"Time requested: {r_seconds} - time granted: {self.c_seconds} ") - # os.system('PAUSE') return True, self.c_seconds else: - # logger.info(f"self.settings.helics.iterative_mode") - # os.system("PAUSE") itr = 0 while True: @@ -381,27 +400,24 @@ def subscribe(self) -> dict: """ "Subscribes results each iteration and updates PSSE objects accordingly" + + # logger.debug(f"self.psse_dict is {self.psse_dict}") for sub_tag, sub_data in self.subscriptions.items(): if isinstance(sub_data["property"], str): sub_data["value"] = h.helicsInputGetDouble(sub_data["subscription"]) - # print(f"##################################################") - # print(f"##################################################") - # print(f"sub_data is {sub_data}") + # logger.debug(f"sub_data is {sub_data}") self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][ sub_data["property"] - ] = (sub_data["value"], sub_data["scaler"]) + ].append((sub_data["value"], sub_data["scaler"])) elif isinstance(sub_data["property"], list): sub_data["value"] = h.helicsInputGetVector(sub_data["subscription"]) - # print(f"##################################################") - # print(f"##################################################") - # print(f"##################################################") - # print(f"sub_data is {sub_data}") + # logger.debug(f"sub_data is {sub_data}") if isinstance(sub_data["value"], list) and len(sub_data["value"]) == len(sub_data["property"]): for i, p in enumerate(sub_data["property"]): - self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][p] = ( + self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][p].append(( sub_data["value"][i], sub_data["scaler"][i], - ) + )) logger.debug("Data received {} for tag {}".format(sub_data["value"], sub_tag)) if self.settings.helics.iterative_mode: @@ -411,41 +427,55 @@ def subscribe(self) -> dict: sub_data["dStates"].insert(0, sub_data["dStates"].pop()) all_values = {} for b, b_info in self.psse_dict.items(): - # print(f"##################################################") - # print(f"b is {b}") - # print(f"b_info is {b_info}") for t, t_info in b_info.items(): for i, v_dict in t_info.items(): values = {} j = 0 - # print(f"##################################################") - # print(f"i is {i}") - # print(f"v_dict is {v_dict}") - for p, v_raw in v_dict.items(): - if isinstance(v_raw, tuple): - # print(f"##################################################") - # print(f"v_raw is {v_raw}") - v, scale = v_raw - all_values[f"{t}.{b}.{i}.{p}"] = v + for p, v_raws in v_dict.items(): + if isinstance(v_raws, list): if isinstance(p, str): ppty = f"realar{PROFILE_VALIDATION[t].index(p) + 1}" - values[ppty] = v * scale - # if isinstance(scale, (float, int)): - # values[ppty] = v * scale - # else: - # values[ppty] = v - + values[ppty] = 0 + for v_raw in v_raws: + v, scale = v_raw + if isinstance(scale, (float, int)): + values[ppty] += v * scale + else: + values[ppty] += v + all_values[f"{t}.{b}.{i}.{p}"] = values[ppty] elif isinstance(p, list): for _, ppt in enumerate(p): ppty = f"realar{PROFILE_VALIDATION[t].index(ppt) + 1}" - values[ppty] = v * scale - # if isinstance(scale, (float, int)): - # values[ppty] = v * scale - # else: - # values[ppty] = v + values[ppty] = 0 + for v_raw in v_raws: + v, scale = v_raw + if isinstance(scale, (float, int)): + values[ppty] += v * scale + else: + values[ppty] += v + all_values[f"{t}.{b}.{i}.{p}"] = values[ppty] j += 1 is_empty = [0 if not vx else 1 for vx in values.values()] + logger.debug(f"{t}.{b}.{i} = {values}") logger.debug(f"current HELICE time: {self.c_seconds}") + ###################################################### + ## FX: add this for better transit matching + if round(self.c_seconds, 3) == 0.1 and self.settings.simulation.transmission_loads_markup: + logger.debug("the moment of fault") + logger.debug(f"old {t}.{b}.{i} = {values}") + logger.debug(self.load_power) + load_data = self.load_power[b]['transmission_loads_at_fault'] + values['realar1'] = load_data[0] + values['realar2'] = load_data[1] + # os.system("PAUSE") + if round(self.c_seconds, 3) == 0.175 and self.settings.simulation.transmission_loads_markup: + logger.debug("the moment of clearing fault") + logger.debug(f"old {t}.{b}.{i} = {values}") + load_data = self.load_power[b]['transmission_loads_clear_fault'] + values['realar1'] = load_data[0] + values['realar2'] = load_data[1] + # os.system("PAUSE") + ###################################################### if ( sum(is_empty) != 0 and sum(values.values()) < VALUE_UPDATE_BOUND @@ -457,7 +487,20 @@ def subscribe(self) -> dict: else: logger.debug("write failed") + + ###################################################### + ## FX: clear the result list + for sub_tag, sub_data in self.subscriptions.items(): + if isinstance(sub_data["property"], str): + self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][sub_data["property"]] = [] + elif isinstance(sub_data["property"], list): + if isinstance(sub_data["value"], list) and len(sub_data["value"]) == len(sub_data["property"]): + for i, p in enumerate(sub_data["property"]): + self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][p] = [] + logger.debug(f"clear self.psse_dict is {self.psse_dict}") + ###################################################### + # os.system("PAUSE") self.c_seconds_old = self.c_seconds return all_values diff --git a/pypsse/models.py b/pypsse/models.py index d66e857..d38c554 100644 --- a/pypsse/models.py +++ b/pypsse/models.py @@ -58,8 +58,11 @@ class SimSettings(BaseModel): simulation_mode: SimulationModes disable_generation_on_coupled_buses: bool = True generation_model_level: GenerationLevel - transmission_ibrs: List[str] = [] - transmission_ibrs_std: List[str] = [] + generation_std: str = "ieee2800" + transmission_ids: List[int] + transmission_loads_at_fault: List + transmission_loads_clear_fault: List + transmission_loads_markup: bool = False @model_validator(mode="after") def sim_res_smaller_than_sim_time(self): @@ -178,6 +181,7 @@ class HelicsSettings(BaseModel): max_coiterations: int = Field(15, ge=1) broker_ip: IPvAnyAddress = "127.0.0.1" broker_port: int = 23404 + generation_model_level: str = "distribution" publications: List[PublicationDefination] @@ -569,4 +573,11 @@ class FastDER_data_model(BaseModel): femin:float=-0.2 imax:float=2.0 ppriorityflag:bool=True - xf: float=0.25 \ No newline at end of file + xf: float=0.25 + +class ProfileMap(BaseModel): + id: str + bus: str + multiplier : float = 1 + normalize : bool = False + interpolate : bool = False \ No newline at end of file diff --git a/pypsse/modes/abstract_mode.py b/pypsse/modes/abstract_mode.py index 1cc1936..60fdb0e 100644 --- a/pypsse/modes/abstract_mode.py +++ b/pypsse/modes/abstract_mode.py @@ -267,8 +267,9 @@ def save_model(self): i = 0 file_path = export_path / f"modified_steady_state_{i}.sav" while file_path.exists(): - file_path = export_path / f"modified_steady_state_{i}.sav" i += 1 + file_path = export_path / f"modified_steady_state_{i}.sav" + savfile = export_path / f"modified_steady_state_{i}.sav" rawfile = export_path / f"modified_steady_state_{i}.raw" @@ -511,7 +512,7 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma ext_string2_info = {} if mapping_dict is None: mapping_dict = {} - # logger.debug(f" subsystem_buses : {subsystem_buses}") + results = {} area_numbers = self.get_area_numbers(subsystem_buses) zone_numbers = self.get_zone_numbers(subsystem_buses) @@ -519,18 +520,12 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma substation_numbers = self.get_substation_numbers(subsystem_buses) for class_name, var_list in quantities.items(): - # logger.debug(f"class_name : {class_name}, var_list : {var_list}") - # logger.debug(f"self.func_options : {self.func_options}") if class_name in self.func_options: funcs = self.func_options[class_name] - # logger.debug(f"funcs : {funcs}") for id_, v in enumerate(var_list): for func_name, settinsgs in funcs.items(): - # logger.debug(f"func_name : {func_name}, settinsgs : {settinsgs}") if v in settinsgs: q = f"{class_name}_{v}" - # logger.debug(f"q : {q}") - # os.system("PAUSE") if len(mapping_dict) != 0: if class_name in mapping_dict: @@ -599,7 +594,6 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma else: for b in subsystem_buses: - # logger.debug(f"bus : {b}") if func_name in [ "busdat", "busdt2", @@ -672,7 +666,6 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma elif func_name in ["inddt1", "inddt2", "indnofunc"]: ierr = self.psse.iniind(int(b)) if ierr: - # os.system("PAUSE") logger.info("No induction machine in the model") else: ierr, ind_id = self.psse.nxtind(int(b)) @@ -691,11 +684,7 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma elif func_name in ["loddt2", "lodnofunc", "lodint"]: ierr = self.psse.inilod(int(b)) - # logger.debug(f"func_name : {func_name}") - # logger.debug(f"bus : {b}") ierr, load_id = self.psse.nxtlod(int(b)) - # logger.debug(f"first load_id : {load_id}") - while load_id is not None: if func_name == "lodnofunc": if v in ["BUSNUM", "LOADID"]: @@ -703,200 +692,14 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma results = self.add_result(results, q, val, f"{b}_{load_id}") elif v == "BUSNAME": ierr, val = self.psse.notona(int(b)) - results = self.add_result(results, q, val, f"{b}_{load_id}") elif func_name == "loddt2": ierr, val = getattr(self.psse, func_name)(int(b), load_id, v, "ACT") results = self.add_result(results, q, val, f"{b}_{load_id}") - # if int(b) == 10446 or int(b) == 10466 or int(b) == 10493: - # logger.debug(f"loddt2 -> int(b): {int(b)}, load_id: {load_id}, v: {v}, ierr : {ierr}, val : {val}") - # ierr, state_index = self.psse.lmodind(int(b), load_id, "CHARAC", "STATE") - # assert ierr == 0, f"error={ierr}" - # if state_index is not None: - # act_state_index = state_index + 20 - # ierr, value = self.psse.dsrval("STATE", act_state_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: non-restartable compressor motor A temperature - Load: {int(b)}-{load_id} -> index: {act_state_index}, value:{value}") - # act_state_index = state_index + 21 - # ierr, value = self.psse.dsrval("STATE", act_state_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: restartable compressor motor B temperature - Load: {int(b)}-{load_id} -> index: {act_state_index}, value:{value}") - # ierr, var_index = self.psse.lmodind(int(b), load_id, "CHARAC", "VAR") - # assert ierr == 0, f"error={ierr}" - # if var_index is not None: - # act_var_index = var_index + 0 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Load MVA base - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 12 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Transformer tap - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 13 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Transformer low side voltage real part - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 14 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Transformer low side voltage imaginary part - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 15 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Load bus voltage real part - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 16 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Load bus voltage imaginary part - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 20 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Sum of substation shunt admittance and feeder compensation admittance at substation end - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 21 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Substation shunt admittance - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 24 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Transformer reactance (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 25 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Static load real part (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 26 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Static load reactive part (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 29 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Load bus voltage magnitude - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 30 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Low side bus voltage magnitude - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 37 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D P (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 38 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D Q (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 53 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Initial value of Motor D P (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 54 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Initial value of Motor D Q (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 55 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Feeder compensation admittance at substation end - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 56 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Feeder compensation admittance at far end - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 60 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Calculated Vstallbrk - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 66 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Initial value of Motor D MVA base - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 117 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Bus voltage (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 118 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Bus frequency (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 119 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Aggregated AC unit real power (MW) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 120 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Aggregated AC unit reactive power (MVAr) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 121 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Aggregated AC unit current (pu on system MVA base) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 124 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Terminal current component on network real axis on system MVA base (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 125 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Terminal current comp on network imaginary axis on system MVA base (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 126 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: non-restartable motor A and restartable motor B Initial Temperature - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 131 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: KthA non-restartable compressor motor A fraction not tripped by thermal protection - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 133 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: non-restartable Motor A run / stall state (run=1/stall=0) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 134 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: restartable Motor B run / stall state (run=1/stall=0) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 135 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: KthB restartable compressor motor B fraction not tripped by thermal protection - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 136 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Internal variable used for determining non-restartable motor A temperature - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 137 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Internal variable used for determining restartable motor B temperature - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 138 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Real component of voltage at pervious time step (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 139 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Reactive component of voltage at previous time step (pu) - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 141 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Internal variable, P0 for active power at 1.0 pu voltage - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 142 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Internal variable, Q0 for reactive power at 1.0 pu voltage - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 143 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Motor D: Computed motor MVA base - Load: {int(b)}-{load_id} -> index: {act_var_index}, value:{value}") - - # os.system("PAUSE") elif func_name == "lodint": ierr, val = getattr(self.psse, func_name)(int(b), load_id, v) - results = self.add_result(results, q, val, f"{b}_{load_id}") - # logger.debug(f"val : {val}") - # logger.debug(f"v : {v}") ierr, load_id = self.psse.nxtlod(int(b)) - # logger.debug(f"next load_id : {load_id}") - # logger.debug(f"results : {results}") - # os.system("PAUSE") - elif func_name in ["macdat", "macdt2", "macnofunc", "macint"]: ierr = self.psse.inimac(int(b)) ierr, mach_id = self.psse.nxtmac(int(b)) @@ -910,106 +713,25 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma results = self.add_result(results, q, val, f"{b}_{mach_id}") elif v == "SUBNUMBER": ierr, val = self.psse.busint(int(b), "STATION") - results = self.add_result(results, q, val, f"{b}_{mach_id}") - elif v == "AREANUMBER": ierr, val = self.psse.busint(int(b), "AREA") - results = self.add_result(results, q, val, f"{b}_{mach_id}") - elif v in ["SUBLATITUDE", "SUBLONGITUDE"]: sub_dict = {"SUBLATITUDE": "LATI", "SUBLONGITUDE": "LONG"} ierr, val = self.psse.busint(int(b), "STATION") if val: ierr, val = self.psse.stadat(val, sub_dict[v]) - results = self.add_result(results, q, val, b) else: - logger.debug(f"machine -> int(b): {int(b)}, mach_id: {mach_id}, func_name: {func_name}, v: {v}") - # if mach_id == "PV": - # ierr, state_index = self.psse.windmind(int(b), mach_id, 'WGEN', 'STATE') - # assert ierr == 0, f"error={ierr}" - # if state_index is not None: - # act_state_index = state_index + 0 - # ierr, value = self.psse.dsrval("STATE", act_state_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"voltage measurement lag: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") - # act_state_index = state_index + 1 - # ierr, value = self.psse.dsrval("STATE", act_state_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"generator power measurement lag: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") - # act_state_index = state_index + 2 - # ierr, value = self.psse.dsrval("STATE", act_state_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"first order lag for reactive current: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") - # act_state_index = state_index + 3 - # ierr, value = self.psse.dsrval("STATE", act_state_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"lag block representing inner current loop for iq: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") - # act_state_index = state_index + 4 - # ierr, value = self.psse.dsrval("STATE", act_state_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"lag block for output of multiplier block: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") - # act_state_index = state_index + 8 - # ierr, value = self.psse.dsrval("STATE", act_state_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"first order lag for Pord: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") - # act_state_index = state_index + 9 - # ierr, value = self.psse.dsrval("STATE", act_state_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"lag block representing inner current loop for id: {int(b)}-{mach_id} -> index: {act_state_index}, value:{value}") - # ierr, var_index = self.psse.windmind(int(b), mach_id, 'WGEN', 'VAR') - # assert ierr == 0, f"error={ierr}" - # if var_index is not None: - # act_var_index = var_index + 0 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"bus voltage reference (Vref0): {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 1 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"power factor angle reference (pfaref), radians: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 3 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"active power reference (Pref): {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 4 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"reactive power reference (Qref): {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 7 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Ipcmd: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 8 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"Iqcmd: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 11 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"output of undervoltage voltage multiplier block: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 12 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"output of overvoltage voltage multiplier block: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") - # act_var_index = var_index + 22 - # ierr, value = self.psse.dsrval("VAR", act_var_index) - # assert ierr == 0, f"error={ierr}" - # logger.debug(f"reactive current injection, Iqinj: {int(b)}-{mach_id} -> index: {act_var_index}, value:{value}") - # # os.system("PAUSE") ierr, val = getattr(self.psse, func_name)(int(b), mach_id, v) - results = self.add_result(results, q, val, f"{b}_{mach_id}") ierr, mach_id = self.psse.nxtmac(int(b)) elif func_name in ["fxsdt2", "fxsnofunc"]: ierr = self.psse.inifxs(int(b)) - ierr, fx_id = self.psse.nxtfxs(int(b)) - while fx_id is not None: if func_name == "fxsnofunc": if v in ["BUSNUM", "FXSHID"]: @@ -1017,35 +739,28 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma results = self.add_result(results, q, val, f"{b}_{fx_id}") elif v == "BUSNAME": ierr, val = self.psse.notona(int(b)) - results = self.add_result(results, q, val, f"{b}_{fx_id}") else: ierr, val = getattr(self.psse, func_name)(int(b), fx_id, v) - results = self.add_result(results, q, val, f"{b}_{fx_id}") ierr, fx_id = self.psse.nxtfxs(int(b)) elif func_name in ["swsdt1", "swsnofunc"]: if func_name == "swsnofunc": ierr, val = self.psse.swsint(int(b), "STATUS") - else: ierr, val = getattr(self.psse, func_name)(int(b), v) - if ierr == 0: if func_name == "nofunc": if v == "BUSNUM": val = int(b) elif v == "BUSNAME": ierr, val = self.psse.notona(int(b)) - results = self.add_result(results, q, val, b) elif func_name in ["brndat", "brndt2", "brnmsc", "brnint", "brnnofunc"]: ierr = self.psse.inibrx(int(b), 1) - ierr, b1, ickt = self.psse.nxtbrn(int(b)) - ickt_string = str(ickt) while ierr == 0: if func_name == "brnnofunc": @@ -1104,7 +819,6 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma else: ierr, val = getattr(self.psse, func_name)(int(b), int(b1), str(ickt), v) - if ierr == 0: results = self.add_result( results, q, val, f"{b!s}_{b1!s}_{ickt_string}" @@ -1259,38 +973,27 @@ def update_object(self, dtype, bus, element_id, values: dict): val = sum(list(values.values())) if val > -VALUE_UPDATE_BOUND and val < VALUE_UPDATE_BOUND: if dtype == WritableModelTypes.LOAD.value or dtype == WritableModelTypes.LOAD_STATUS.value: - # ierr = self.psse.load_chng_5(ibus=int(bus), id=element_id, **values) - # elif dtype == WritableModelTypes.LOAD_STATUS.value: - old_load = self.psse.lodint(ibus=int(bus), id=element_id, string='STATUS') - logger.info(f"old load {bus}-{element_id}: {old_load}") - old_load = self.psse.loddt2(ibus=int(bus), id=element_id, string1='MVA', string2='ACT') - logger.info(f"old load MVA {bus}-{element_id}: {old_load}") ierr = self.psse.load_chng_5(ibus=int(bus), id=element_id, **values) - old_load = self.psse.lodint(ibus=int(bus), id=element_id, string='STATUS') - logger.info(f"new load {bus}-{element_id}: {old_load}") - old_load = self.psse.loddt2(ibus=int(bus), id=element_id, string1='MVA', string2='ACT') - logger.info(f"old load MVA {bus}-{element_id}: {old_load}") elif dtype == WritableModelTypes.GENERATOR.value: ierr = self.psse.induction_machine_data(ibus=int(bus), id=element_id, **values) elif dtype == WritableModelTypes.MACHINE.value or dtype == WritableModelTypes.MACHINE_STATUS.value: ierr = self.psse.machine_data_2(i=int(bus), id=element_id, **values) + elif dtype == WritableModelTypes.PLANT.value: + ierr = self.psse.plant_data_4(ibus=int(bus), inode=0, intgar=[self._i, self._i], **values) elif dtype == WritableModelTypes.LINE_STATUS.value: frombus, tobus = bus.split("_") - old_load = self.psse.brnint(ibus=int(frombus), jbus=int(tobus), ickt=element_id, string='STATUS') - logger.info(f"old line status {bus}-{element_id}: {old_load}") + # old_load = self.psse.brnint(ibus=int(frombus), jbus=int(tobus), ickt=element_id, string='STATUS') + # logger.info(f"old line status {bus}-{element_id}: {old_load}") ierr = self.psse.branch_data_3(ibus=int(frombus), jbus=int(tobus), ckt=element_id, **values) - old_load = self.psse.brnint(ibus=int(frombus), jbus=int(tobus), ickt=element_id, string='STATUS') - logger.info(f"new line status {bus}-{element_id}: {old_load}") - elif dtype == WritableModelTypes.PLANT.value: - ierr = self.psse.plant_data_4(ibus=int(bus), inode=0, intgar=[self._i, self._i], **values) + # old_load = self.psse.brnint(ibus=int(frombus), jbus=int(tobus), ickt=element_id, string='STATUS') + # logger.info(f"new line status {bus}-{element_id}: {old_load}") else: ierr = 1 if ierr == 0: - # logger.info(f"Profile Manager: {dtype} '{element_id}' on bus '{bus}' has been updated. {values}") - pass + logger.info(f"Profile Manager: {dtype} '{element_id}' on bus '{bus}' has been updated. {values}") else: logger.error(f"Profile Manager: Error updating {dtype} '{element_id}' on bus '{bus}'. Error code is {ierr}") - + def has_converged(self): return self.psse.solved() diff --git a/pypsse/modes/snap.py b/pypsse/modes/snap.py index a55a30f..6d9b797 100644 --- a/pypsse/modes/snap.py +++ b/pypsse/modes/snap.py @@ -46,25 +46,22 @@ def init(self, bus_subsystems): # # The following logic only runs when the helics interface is enabled self.disable_load_models_for_coupled_buses() self.disable_generation_for_coupled_buses() - # self.save_model() ############# ------------------------------------- ############### outx_file = str(self.settings.export.outx_file).split("\\") outx_file[-1] = self.export_settings.filename_prefix + "_" + outx_file[-1] outx_file = "\\".join(outx_file) - self.load_user_defined_models() ierr = self.psse.strt_2([0, 1], outx_file) if ierr == 1: self.psse.cong(0) ierr = self.psse.strt_2([0, 1], outx_file) - assert ierr==0, f"error of strt_2 wwith code {ierr}" elif ierr > 1: msg = "Error starting simulation" raise Exception(msg) - # self.load_user_defined_models() + self.load_user_defined_models() if self.settings.helics and self.settings.helics.cosimulation_mode: if self.settings.helics.iterative_mode: @@ -90,11 +87,10 @@ def init(self, bus_subsystems): def step(self, t): "Increments the simulation" - logger.debug(f"snap.py step : {t}") self.time = self.time + self.incTime self.xTime = 0 if self.settings.simulation.disable_generation_on_coupled_buses and self.settings.simulation.generation_model_level.lower() == "transmission" and len(self.settings.simulation.transmission_ibrs) > 0: - # iff transmission DER model is disable and ibr is defined + # FX: if transmission DER model is disable and ibr is defined self.der.update_ibr() return self.psse.run(0, t + self.incTime.total_seconds(), 1, 1, 1) @@ -142,20 +138,12 @@ def read_subsystems(self, quantities, subsystem_buses, ext_string2_info=None, ma ext_string2_info = {} if mapping_dict is None: mapping_dict = {} - results = super().read_subsystems( quantities, subsystem_buses, mapping_dict=mapping_dict, ext_string2_info=ext_string2_info ) - # logger.debug(f"snap.py results : {results}") - # logger.debug(f"quantities : {quantities}") - # logger.debug(f"subsystem_buses : {subsystem_buses}") - # logger.debug(f"mapping_dict : {mapping_dict}") - # logger.debug(f"ext_string2_info : {ext_string2_info}") - # raise Exception(1) + poll_results = self.poll_channels() results.update(poll_results) - # logger.debug(f"snap.py updated results : {results}") - # os.system("PAUSE") """ Add """ for class_name, var_list in quantities.items(): if class_name in dyn_only_options: diff --git a/pypsse/profile_manager/profile.py b/pypsse/profile_manager/profile.py index f36dd50..8d3c13f 100644 --- a/pypsse/profile_manager/profile.py +++ b/pypsse/profile_manager/profile.py @@ -35,6 +35,7 @@ def __init__(self, profile_obj, solver, mapping_dict, buffer_size=10, neglect_ye def update(self, update_object_properties=True): "Returns value at the current timestep in the given profile" self.time = copy.deepcopy(self.solver.get_time()).astimezone(None) + logger.info(f"self.profile: {self.profile}") if self.time < self.stime or self.time > self.etime: value = np.array([0] * len(self.profile[0])) value1 = np.array([0] * len(self.profile[0])) @@ -46,7 +47,6 @@ def update(self, update_object_properties=True): valuen1 = np.array(list(self.profile[n + 1])) except Exception as _: valuen1 = value - dt2 = ( self.time - (self.stime + datetime.timedelta(seconds=int(n * self.attrs["resTime"]))) ).total_seconds() @@ -55,7 +55,6 @@ def update(self, update_object_properties=True): for obj_name in self.value_settings: if self.value_settings[obj_name]["interpolate"]: value1 = value + (valuen1 - value) * dt2 / self.attrs["resTime"] - # logger.info(f"obj_name: {obj_name}") bus, object_id = obj_name.split("__") if self.value_settings[obj_name]["interpolate"]: value = value1 @@ -65,18 +64,16 @@ def update(self, update_object_properties=True): if self.value_settings[obj_name]["normalize"]: value_f = value / self.attrs["max"] * mult else: - logger.info(f"value: {value}") value_f = value * mult - logger.info(f"value_f: {value_f}") value_f = self.fill_missing_values(value_f) - # if self.dtype == "Machine": - # value_f["realar1"] = value_f["realar1"] self.solver.update_object(self.dtype, bus, object_id, value_f) - logger.info(f"Object updated: {object_id}.{bus}.{self.dtype}={value_f}") + logger.debug(f"Object updated: {object_id}.{bus}.{self.dtype}={value_f}") return value def fill_missing_values(self, value): "Fixes issues in profile data" + # idx = [f"realar{PROFILE_VALIDATION[self.dtype].index(c) + 1}" for c in self.columns] + ## FX: added to handle more data if self.dtype in ["Load","Induction_machine","Machine","Plant"]: idx = [f"realar{PROFILE_VALIDATION[self.dtype].index(c) + 1}" for c in self.columns] else: diff --git a/pypsse/profile_manager/profile_store.py b/pypsse/profile_manager/profile_store.py index 1be34b3..2540a54 100644 --- a/pypsse/profile_manager/profile_store.py +++ b/pypsse/profile_manager/profile_store.py @@ -40,7 +40,7 @@ def __init__( file_path = settings.simulation.project_path / PROFILES_FOLDER / DEFAULT_PROFILE_STORE_FILENAME if file_path.exists(): - logger.info("Loading existing h5 store") + logger.info(f"Loading existing h5 store: {file_path}") self.store = h5py.File(file_path, mode) else: logger.info("Creating new h5 store") @@ -267,6 +267,7 @@ def create_metadata( info (str): profile info p_type (ProfileTypes): profile type """ + # logger.debug(start_time.strftime()) metadata = { "sTime": str(start_time), @@ -295,10 +296,11 @@ def update(self) -> dict: """ results = {} + # logger.info(self.profiles) for profile_name, profile_obj in self.profiles.items(): result = profile_obj.update() results[profile_name] = result - + # logger.info(results) return results # def __del__(self): diff --git a/pypsse/profile_manager_interface.py b/pypsse/profile_manager_interface.py index 57b2322..6543207 100644 --- a/pypsse/profile_manager_interface.py +++ b/pypsse/profile_manager_interface.py @@ -1,5 +1,6 @@ from datetime import timedelta from pathlib import Path +import os from loguru import logger import pandas as pd @@ -36,9 +37,8 @@ def from_setting_files(cls, simulation_settings_file: Path): def get_profiles(self) -> list: all_datasets = [] for model_type, model_info in self._toml_dict.items(): - logger.info(f"model_type {model_type}") - logger.info(f"model_info {model_info}") for profile_id, model_maps in model_info.items(): + logger.info(f"model_type: {model_type}, model_info: {model_info}") for model_map in model_maps: bus_id : str = model_map["bus"] model_id : str = model_map["id"] @@ -46,23 +46,67 @@ def get_profiles(self) -> list: norm: True = model_map.get("normalize") dataset = self._store[f"{model_type}/{profile_id}"] data = np.array(np.array(dataset).tolist()) - - if norm: - data_max = np.array(dataset.attrs["max"]) - data = data / data_max - if mult: - data = data * mult + # logger.info(f"data: {data[:10]}") + # os.system("PAUSE") + if model_type == "Load": + if norm: + data_max = np.array(dataset.attrs["max"]) + data = data / data_max + if mult: + data = data * mult - data = pd.DataFrame(data) - even_sum = data.iloc[:, ::2].sum(axis=1) - odd_sum = data.iloc[:, 1::2].sum(axis=1) - data = [even_sum, odd_sum] + data = pd.DataFrame(data) + P_even_sum = data.iloc[:, ::2].sum(axis=1) + Q_odd_sum = data.iloc[:, 1::2].sum(axis=1) + data = [P_even_sum, Q_odd_sum] + elif model_type == "Machine": + # Added by Fuhong, for a 'good' generation profile. Pgen and Qgen are strictly limited within the geneator maximum and minimum ranges. + data = pd.DataFrame(data) + P_gen = data.iloc[:, 0] + Q_gen = data.iloc[:, 1] + data = [P_gen, Q_gen] + + elif model_type == "Plant": + # Added by Fuhong + data = pd.DataFrame(data) + vs_plant = data.iloc[:, 0] + rmpct_plant = data.iloc[:, 1] + data = [vs_plant, rmpct_plant] + + elif model_type == "Induction_machine": + # TODO + pass + + elif model_type == "Load_status": + data = pd.DataFrame(data) + load_status = data.iloc[:, 0] + data = [load_status] + + elif model_type == "Line_status": + data = pd.DataFrame(data) + line_status = data.iloc[:, 0] + data = [line_status] + + elif model_type == "Machine_status": + data = pd.DataFrame(data) + machine_status = data.iloc[:, 0] + data = [machine_status] + else: + raise TypeError + final_df = pd.concat(data, axis=1) - final_df.columns = [f"{model_type}_{model_id}_{bus_id}_P", f"{model_type}_{model_id}_{bus_id}_Q"] + if model_type == "Load" or model_type == "Machine": + final_df.columns = [f"{model_type}_{model_id}_{bus_id}_P", f"{model_type}_{model_id}_{bus_id}_Q"] + elif model_type == "Plant": + final_df.columns = [f"{model_type}_{model_id}_{bus_id}_V", f"{model_type}_{model_id}_{bus_id}_RMPCT"] + elif model_type == "Load_status" or model_type == "Line_status" or model_type == "Machine_status": + final_df.columns = [f"{model_type}_{model_id}_{bus_id}_Status"] + else: + raise TypeError start_time = str(dataset.attrs["sTime"].decode('utf-8')) end_time = str(dataset.attrs["eTime"].decode('utf-8')) - timestep = timedelta(seconds=dataset.attrs["resTime"]) + timestep = timedelta(seconds=int(dataset.attrs["resTime"])) date_range =pd.date_range( start_time, end_time, diff --git a/pypsse/result_container.py b/pypsse/result_container.py index 7943693..58d02f5 100644 --- a/pypsse/result_container.py +++ b/pypsse/result_container.py @@ -102,10 +102,8 @@ def update(self, bus_data: dict, _, time: datetime.datetime, has_converged: bool """ if self.export_settings.file_format not in self.BULK_WRITE_MODES: - # logger.debug("1111111111111111111") self.dataWriter.write(time, bus_data, has_converged) else: - # logger.debug("2222222222222222222222") for variable_name, _ in bus_data.items(): if not isinstance(self.results[f"{variable_name}"], pd.DataFrame): self.results[f"{variable_name}"] = pd.DataFrame(bus_data[variable_name], index=[0]) diff --git a/pypsse/simulator.py b/pypsse/simulator.py index 9195c61..8cc508a 100644 --- a/pypsse/simulator.py +++ b/pypsse/simulator.py @@ -97,7 +97,7 @@ def __init__( ) assert ( export_settings_path.exists() - ), f"{export_settings_path} does nor exist" + ), f"{export_settings_path} does not exist" export_settings = toml.load(export_settings_path) export_settings = ExportFileOptions(**export_settings) @@ -167,10 +167,10 @@ def start_simulation(self): self.hi = None self.simStartTime = time.time() - - if self.settings.simulation.case_study is not None and self.settings.simulation.case_study.exists(): + + if self.settings.simulation.case_study and self.settings.simulation.case_study.exists(): self.psse.case(str(self.settings.simulation.case_study)) - elif self.settings.simulation.raw_file is not None and self.settings.simulation.raw_file.exists(): + elif self.settings.simulation.raw_file and self.settings.simulation.raw_file.exists(): self.psse.read(0, str(self.settings.simulation.raw_file)) else: msg = "Please pass a RAW or SAV file in the settings dictionary" @@ -343,8 +343,7 @@ def run(self): if t >= total_simulation_time: break - ierr = self.psse.pssehalt_2() - assert ierr == 0, f"pssehalt_2 error code: {ierr}" + self.psse.pssehalt_2() if not self.export_settings.export_results_using_channels: self.results.export_results() else: @@ -431,7 +430,6 @@ def update_result_container(self, t: float) -> dict: """ if self.export_settings.defined_subsystems_only: - # logger.debug(f"self.exp_vars : {self.exp_vars}") curr_results = self.sim.read_subsystems( self.exp_vars, self.all_subsysten_buses ) @@ -442,7 +440,6 @@ def update_result_container(self, t: float) -> dict: if not USING_NAERM: if not self.export_settings.export_results_using_channels: - # logger.debug(f"curr_results : {curr_results}") self.results.update( curr_results, t, @@ -484,7 +481,7 @@ def get_results(self, params: Union[ExportAssetTypes, dict]) -> dict: Returns: dict: simulation results """ - logger.debug(f"self.exp_vars : {self.exp_vars}") + self._status = SimulationStatus.STARTING_RESULT_EXPORT self.exp_vars = self.results.update_export_variables(params) curr_results = ( diff --git a/pypsse/utils/dynamic_utils.py b/pypsse/utils/dynamic_utils.py index 96267b1..88fe9c1 100644 --- a/pypsse/utils/dynamic_utils.py +++ b/pypsse/utils/dynamic_utils.py @@ -23,7 +23,6 @@ def disable_generation_for_coupled_buses(self): sub_data = sub_data[sub_data["element_type"] == "Load"] generators = {} generator_list = {} - generator_data = {} for gen_bus, gen_id in self.raw_data.generators: if gen_bus not in generator_list: @@ -59,8 +58,6 @@ def disable_generation_for_coupled_buses(self): self.psse.machine_chng_2(bus_id, machine, intgar, realar) logger.info(f"Machine disabled: {bus_id}_{machine}") - logger.info(f"generator_data: {generator_data})") - logger.info(f"generators: {generators})") if self.settings.simulation.generation_model_level.lower() == "transmission" and len(self.settings.simulation.transmission_ibrs) > 0: intgar = [0, self._i, self._i, self._i, self._i, self._i] realar = [ @@ -140,22 +137,23 @@ def disable_load_models_for_coupled_buses(self): if self.settings.helics and self.settings.helics.cosimulation_mode: sub_data = pd.read_csv(self.settings.simulation.subscriptions_file) sub_data = sub_data[sub_data["element_type"] == "Load"] - logger.info(f"break the load") - self.break_loads(None, ['FmD']) - # self.psse_dict = {} - # for _, row in sub_data.iterrows(): - # bus = row["bus"] - # load = row["element_id"] - # ierr = self.psse.ldmod_status(0, int(bus), str(load), 1, 0) - # if ierr == 0: - # logger.info(f"Dynamic model for load {load} connected to bus {bus} has been disabled") - # elif ierr == 5: - # logger.error(f"No dynamic model found for load {load} connected to bus {bus}") - # else: - # raise Exception(f"error={ierr}") - # os.system("PAUSE") - - def break_loads(self, loads: list = None, components_to_replace: List[str] = []): + ### add by Aadil + logger.debug("Implementing load brek logic for dynamic cosimulations") + self.break_loads_for_dynamic_cosimulations(loads=None, components_to_replace=["FmD"]) + + self.psse_dict = {} + for _, row in sub_data.iterrows(): + bus = row["bus"] + load = row["element_id"] + ierr = self.psse.ldmod_status(0, int(bus), str(load), 1, 0) + if ierr == 0: + logger.info(f"Dynamic model for load {load} connected to bus {bus} has been disabled") + elif ierr == 5: + logger.error(f"No dynamic model found for load {load} connected to bus {bus}") + else: + raise Exception(f"error={ierr}") + + def break_loads_for_dynamic_cosimulations(self, loads: list = None, components_to_replace: List[str] = []): """Implements the load split logic Args: @@ -163,22 +161,23 @@ def break_loads(self, loads: list = None, components_to_replace: List[str] = []) components_to_replace (List[str], optional): components to be simulated on distribution side. Defaults to []. """ logger.info("##################### break_loads #########################") - os.system("PAUSE") components_to_stay = [x for x in self.dynamic_params if x not in components_to_replace] logger.info(f"components_to_stay: {components_to_stay}") logger.info(f"components_to_replace: {components_to_replace}") - os.system("PAUSE") + # os.system("PAUSE") if loads is None: loads = self._get_coupled_loads() - os.system("PAUSE") + logger.debug("Fetching static data for loads") loads = self._get_load_static_data(loads) - os.system("PAUSE") + # os.system("PAUSE") + logger.debug("Fetching dynamic data for loads") loads = self._get_load_dynamic_data(loads) - os.system("PAUSE") + logger.debug("Creating dummy loads for coupled buses") loads = self._replicate_coupled_load(loads, components_to_replace) - os.system("PAUSE") + # os.system("PAUSE") + logger.debug("Updating dynamic parameters for load models") self._update_dynamic_parameters(loads, components_to_stay, components_to_replace) - os.system("PAUSE") + # os.system("PAUSE") def _update_dynamic_parameters(self, loads: dict, components_to_stay: list, components_to_replace: list): """Updates dynamic parameters of composite old / replicated load models @@ -195,20 +194,31 @@ def _update_dynamic_parameters(self, loads: dict, components_to_stay: list, comp for comp in components_to_stay: count += load[comp] for comp in components_to_stay: - new_percentages[comp] = load[comp] / count + if count == 0: + new_percentages[comp] = 0 + else: + new_percentages[comp] = load[comp] / count for comp in components_to_replace: new_percentages[comp] = 0.0 settings = self._get_load_dynamic_properties(load) - # + for k, v in new_percentages.items(): idx = dyn_only_options["Loads"]["lmodind"][k] settings[idx] = v + logger.debug(f"Dynamic model parameters for load {load['bus']}/{load['id']} at bus XX --> index{idx}, value{v}.") # self.psse.change_ldmod_con(load['bus'], 'XX' ,r"""CMLDBLU2""" ,idx ,v) values = list(settings.values()) self.psse.add_load_model(load["bus"], "XX", 0, 1, r"""CMLDBLU2""", 2, [0, 0], ["", ""], 133, values) + # self.psse.add_load_model(load["bus"], "A", 0, 1, r"""CMLDBLU2""", 2, [0, 0], ["", ""], 133, values) + # self.psse.add_load_model(load["bus"], "B", 0, 1, r"""CMLDBLU2""", 2, [0, 0], ["", ""], 133, values) + # self.psse.add_load_model(load["bus"], "C", 0, 1, r"""CMLDBLU2""", 2, [0, 0], ["", ""], 133, values) + # self.psse.add_load_model(load["bus"], "D", 0, 1, r"""CMLDBLU2""", 2, [0, 0], ["", ""], 133, values) + # self.psse.add_load_model(load["bus"], "F", 0, 1, r"""CMLDBLU2""", 2, [0, 0], ["", ""], 133, values) + # self.psse.add_load_model(load["bus"], "S", 0, 1, r"""CMLDBLU2""", 2, [0, 0], ["", ""], 133, values) logger.info(f"Dynamic model parameters for load {load['id']} at bus 'XX' changed. New settings are {values}.") + def _get_load_dynamic_properties(self, load): "Returns dynamic parameters of composite load models" settings = {} @@ -220,7 +230,36 @@ def _get_load_dynamic_properties(self, load): assert ierr == 0, f"error={ierr}" settings[i] = value return settings - + + def _get_bus_generation(self, bus): + "Returns the total generation on an certain bus" + generator_list = {} + generators = {} + for gen_bus, gen_id in self.raw_data.generators: + if gen_bus not in generator_list: + generator_list[gen_bus] = [] + generator_list[gen_bus].append(gen_id) + try: + generators[bus] = generator_list[bus] + except: + raise Exception(f"Can't get generator on bus {bus}") + # logger.warning(f"Can't get generator on bus {bus}") + bus_total_p = 0 + bus_total_q = 0 + for bus_id, machines in generators.items(): + for machine in machines: + ierr, ival = self.psse.macint(bus_id, machine, 'STATUS') + if ival == 1: + ierr, cmpval = self.psse.macdt2(bus_id, machine, 'PQ') + assert ierr == 0, f"error={ierr}" + logger.debug(f"{bus_id}.{machine} generation: {cmpval}") + else: + cmpval = complex(0,0) + logger.debug(f"{bus_id}.{machine} offline generation: {cmpval}") + bus_total_p += cmpval.real + bus_total_q += cmpval.imag + return bus_total_p, bus_total_q + def _replicate_coupled_load(self, loads: dict, components_to_replace: list): """create a replica of composite load model @@ -231,10 +270,12 @@ def _replicate_coupled_load(self, loads: dict, components_to_replace: list): Returns: dict: updated load dictionary """ - logger.info(f"2222222222222222222222222") + for load in loads: + logger.info(f"load : {load}") dynamic_percentage = load["FmA"] + load["FmB"] + load["FmC"] + load["FmD"] + load["Fel"] static_percentage = 1.0 - dynamic_percentage + logger.debug(f"Static properties - Load: Static -> value:{static_percentage}") for comp in components_to_replace: static_percentage += load[comp] remaining_load = 1 - static_percentage @@ -248,20 +289,33 @@ def _replicate_coupled_load(self, loads: dict, components_to_replace: list): realar=[total_transmission_load.real, total_transmission_load.imag, 0.0, 0.0, 0.0, 0.0], # lodtyp="replica", ) - # ierr, cmpval = self.psse.loddt2(load["bus"], "XX" ,"MVA" , "ACT") - # modify old load - self.psse.load_data_5( - load["bus"], - str(load["id"]), - realar=[total_distribution_load.real, total_distribution_load.imag, 0.0, 0.0, 0.0, 0.0], - # lodtyp="original", - ) - # ierr, cmpval = self.psse.loddt2(load["bus"], load["id"] ,"MVA" , "ACT") - logger.info(f"Original load {load['id']} @ bus {load['bus']}: {total_load}") - logger.info(f"New load 'XX' @ bus {load['bus']} created successfully: {total_transmission_load}") - logger.info(f"Load {load['id']} @ bus {load['bus']} updated : {total_distribution_load}") - load["distribution"] = total_distribution_load - load["transmission"] = total_transmission_load + if (self.settings.helics and self.settings.helics.cosimulation_mode and self.settings.helics.disable_generation_on_coupled_buses + and self.settings.helics.generation_model_level == 'distribution'): + total_bus_generation_p, total_bus_generation_q = self._get_bus_generation(load['bus']) + logger.info(f"Generation is modeled in distribution level so transmission load is substituted the generation") + self.psse.load_data_5( + load["bus"], + str(load["id"]), + realar=[total_distribution_load.real-total_bus_generation_p, total_distribution_load.imag-total_bus_generation_q, 0.0, 0.0, 0.0, 0.0], + # lodtyp="original", + ) + logger.info(f"Original load {load['id']} @ bus {load['bus']}: {total_load}") + logger.info(f"New load 'XX' @ bus {load['bus']} created successfully: {total_transmission_load}") + logger.info(f"Load {load['id']} @ bus {load['bus']} updated : ({total_distribution_load.real-total_bus_generation_p},{total_distribution_load.imag-total_bus_generation_q})") + load["distribution"] = complex(total_distribution_load.real-total_bus_generation_p, total_distribution_load.imag-total_bus_generation_q) + load["transmission"] = total_transmission_load + else: + self.psse.load_data_5( + load["bus"], + str(load["id"]), + realar=[total_distribution_load.real, total_distribution_load.imag, 0.0, 0.0, 0.0, 0.0], + # lodtyp="original", + ) + logger.info(f"Original load {load['id']} @ bus {load['bus']}: {total_load}") + logger.info(f"New load 'XX' @ bus {load['bus']} created successfully: {total_transmission_load}") + logger.info(f"Load {load['id']} @ bus {load['bus']} updated : {total_distribution_load}") + load["distribution"] = total_distribution_load + load["transmission"] = total_transmission_load logger.info(f"{loads}") return loads @@ -307,7 +361,7 @@ def _get_load_static_data(self, loads: list) -> dict: for v in values: ierr, cmpval = self.psse.loddt2(load["bus"], str(load["id"]), v, "ACT") load[v] = cmpval - logger.info(f"{loads}") + logger.debug(f"Static properties - Load: {v} -> {cmpval}") return loads def _get_load_dynamic_data(self, loads: list) -> dict: @@ -335,7 +389,7 @@ def _get_load_dynamic_data(self, loads: list) -> dict: ierr, value = self.psse.dsrval("CON", act_con_index) assert ierr == 0, f"error={ierr}" load[v] = value - logger.info(f"{loads}") + logger.debug(f"Dynamic properties - Load: {v} -> index: {act_con_index}, value:{value}") return loads def setup_machine_channels(self, machines: dict, properties: list): @@ -368,13 +422,12 @@ def setup_load_channels(self, loads: list): if "LOAD_P" not in self.channel_map: self.channel_map["LOAD_P"] = {} self.channel_map["LOAD_Q"] = {} - # logger.info(f"loads {loads}") + for ld, b in loads: - logger.info(f"P and Q for load {b}_{ld}") self.channel_map["LOAD_P"][f"{b}_{ld}"] = [self.chnl_idx] self.channel_map["LOAD_Q"][f"{b}_{ld}"] = [self.chnl_idx + 1] - ierr = self.psse.load_array_channel([self.chnl_idx, 1, int(b)], ld, ""); assert ierr == 0, f"error={ierr}" - ierr = self.psse.load_array_channel([self.chnl_idx + 1, 2, int(b)], ld, ""); assert ierr == 0, f"error={ierr}" + self.psse.load_array_channel([self.chnl_idx, 1, int(b)], ld, "") + self.psse.load_array_channel([self.chnl_idx + 1, 2, int(b)], ld, "") logger.info(f"P and Q for load {b}_{ld} added to channel {self.chnl_idx} and {self.chnl_idx + 1}") self.chnl_idx += 2 @@ -409,7 +462,6 @@ def poll_channels(self) -> dict: """ results = {} - # logger.info(f"self.channel_map: {self.channel_map}") for ppty, b_dict in self.channel_map.items(): ppty_new = ppty.split("_and_") for b, indices in b_dict.items(): @@ -434,8 +486,6 @@ def setup_all_channels(self): self.chnl_idx = 1 if not self.export_settings.channel_setup: return - ierr = self.psse.delete_all_plot_channels(); assert ierr == 0, f"error={ierr}" - # logger.info(f"self.export_settings.channel_setup {self.export_settings.channel_setup}") for channel in self.export_settings.channel_setup: method_type = channel.asset_type @@ -444,8 +494,8 @@ def setup_all_channels(self): elif method_type == "loads": load_list = [[x, int(y)] for x, y in channel.asset_list] self.setup_load_channels(load_list) + # logger.debug(f"load_list: {load_list}") + # os.system("PAUSE") elif method_type == "machines": machine_list = [[x, int(y)] for x, y in channel.asset_list] self.setup_machine_channels(machine_list, channel.asset_properties) - - From 3af7c647427853db46c94d684d815125f5dd3f4b Mon Sep 17 00:00:00 2001 From: HaleyRoss Date: Wed, 25 Feb 2026 13:46:02 -0700 Subject: [PATCH 09/14] updating minor syntax changes --- pypsse/data_writers/hdf5.py | 2 +- pypsse/utils/dynamic_utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pypsse/data_writers/hdf5.py b/pypsse/data_writers/hdf5.py index d6e3b3e..5811781 100644 --- a/pypsse/data_writers/hdf5.py +++ b/pypsse/data_writers/hdf5.py @@ -119,7 +119,7 @@ def write( if self.step >= len(self.Timestamp): self.Timestamp.resize((len(self.Timestamp) + 1,)) self.convergence.resize((len(self.convergence) + 1,)) - self.Timestamp[self.step - 1] = np.string_(currenttime.strftime("%Y-%m-%d %H:%M:%S.%f")) + self.Timestamp[self.step - 1] = np.bytes_(currenttime.strftime("%Y-%m-%d %H:%M:%S.%f")) self.convergence[self.step - 1] = convergence # Add object status data to a DataFrame self.store.flush() diff --git a/pypsse/utils/dynamic_utils.py b/pypsse/utils/dynamic_utils.py index 88fe9c1..9cf64bd 100644 --- a/pypsse/utils/dynamic_utils.py +++ b/pypsse/utils/dynamic_utils.py @@ -289,7 +289,7 @@ def _replicate_coupled_load(self, loads: dict, components_to_replace: list): realar=[total_transmission_load.real, total_transmission_load.imag, 0.0, 0.0, 0.0, 0.0], # lodtyp="replica", ) - if (self.settings.helics and self.settings.helics.cosimulation_mode and self.settings.helics.disable_generation_on_coupled_buses + if (self.settings.helics and self.settings.helics.cosimulation_mode and self.settings.simulation.disable_generation_on_coupled_buses and self.settings.helics.generation_model_level == 'distribution'): total_bus_generation_p, total_bus_generation_q = self._get_bus_generation(load['bus']) logger.info(f"Generation is modeled in distribution level so transmission load is substituted the generation") From aeb6eb66abfe2baa4aab3399fa3c3a0b39975753 Mon Sep 17 00:00:00 2001 From: HaleyRoss Date: Mon, 2 Mar 2026 15:17:51 -0700 Subject: [PATCH 10/14] corrrect key error bug in psse explore function --- pypsse/cli/explore.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pypsse/cli/explore.py b/pypsse/cli/explore.py index 75ff1a6..b539a7c 100644 --- a/pypsse/cli/explore.py +++ b/pypsse/cli/explore.py @@ -133,7 +133,7 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load, is_comp_load[bus] = is_comp load_dict[bus].append(ld_id) - key = f"{ld_id} _{bus}" if len(ld_id) == 1 else f"{ld_id}_{bus}" + key = f"{bus}_{ld_id}" key2 = f"{bus}_{ld_id}".replace(" ", "") load_p = max( results["Loads_MVA"][key].real + results["Loads_IL"][key].real + results["Loads_YL"][key].real, @@ -153,7 +153,7 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load, if bus not in generator_dict: generator_dict[bus] = [] bus_gen[bus] = 0 - key = f"{gen_id} _{bus}" if len(gen_id) == 1 else f"{gen_id}_{bus}" + key = f"{bus}_{gen_id}" generator_dict[bus].append(gen_id) bus_gen[bus] += results["Machines_MVA"][key] From 94238b0193b4a97e82291dd220b2eb2043261412 Mon Sep 17 00:00:00 2001 From: Panossian Date: Thu, 12 Mar 2026 10:07:22 -0600 Subject: [PATCH 11/14] allow kbroker ip address --- pypsse/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pypsse/models.py b/pypsse/models.py index d38c554..ceebb58 100644 --- a/pypsse/models.py +++ b/pypsse/models.py @@ -179,7 +179,7 @@ class HelicsSettings(BaseModel): iterative_mode: bool = False error_tolerance: float = Field(1e-5, g=0) max_coiterations: int = Field(15, ge=1) - broker_ip: IPvAnyAddress = "127.0.0.1" + broker_ip: str="127.0.0.1" #IPvAnyAddress = "127.0.0.1" broker_port: int = 23404 generation_model_level: str = "distribution" publications: List[PublicationDefination] From f56f11975ff96c818687bfb494bf2f2a18d19297 Mon Sep 17 00:00:00 2001 From: Panossian Date: Mon, 16 Mar 2026 17:44:40 -0600 Subject: [PATCH 12/14] enable tcp helics cores --- pypsse/enumerations.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pypsse/enumerations.py b/pypsse/enumerations.py index 11c33a9..6b42626 100644 --- a/pypsse/enumerations.py +++ b/pypsse/enumerations.py @@ -36,6 +36,8 @@ class GenerationLevel(str, Enum): class HelicsCoreTypes(str, Enum): "HELICS core types" ZMQ = "zmq" + TCP_SS="tcp_ss" + TCP="tcp" class WritableModelTypes(str, Enum): From 802cafbf4cd7a44aaec1afc8a4583d13ea8a5ef0 Mon Sep 17 00:00:00 2001 From: HaleyRoss Date: Tue, 24 Mar 2026 11:42:22 -0600 Subject: [PATCH 13/14] Make changes to allow for non-composite load busses. --- pypsse/modes/snap.py | 4 ++-- pypsse/utils/dynamic_utils.py | 24 ++++++++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/pypsse/modes/snap.py b/pypsse/modes/snap.py index 6d9b797..15875c9 100644 --- a/pypsse/modes/snap.py +++ b/pypsse/modes/snap.py @@ -9,7 +9,7 @@ class Snap(AbstractMode, DynamicUtils): - "Class defination for snapshat simulation mode (uses snp and sav files)" + "Class defination for snapshot simulation mode (uses snp and sav files)" def __init__( self, @@ -43,7 +43,7 @@ def init(self, bus_subsystems): ierr = self.psse.rstr(str(self.settings.simulation.snp_file)) # - # # The following logic only runs when the helics interface is enabled + # The following logic only runs when the helics interface is enabled self.disable_load_models_for_coupled_buses() self.disable_generation_for_coupled_buses() ############# ------------------------------------- ############### diff --git a/pypsse/utils/dynamic_utils.py b/pypsse/utils/dynamic_utils.py index 9cf64bd..1f35ef0 100644 --- a/pypsse/utils/dynamic_utils.py +++ b/pypsse/utils/dynamic_utils.py @@ -31,7 +31,10 @@ def disable_generation_for_coupled_buses(self): for _, row in sub_data.iterrows(): bus = row["bus"] - generators[bus] = generator_list[bus] + if bus in generator_list: + generators[bus] = generator_list[bus] + else: + logger.warning(f"No generators at coupled bus {bus}; skipping generation disable for this bus.") for bus_id, machines in generators.items(): for machine in machines: @@ -279,7 +282,10 @@ def _replicate_coupled_load(self, loads: dict, components_to_replace: list): for comp in components_to_replace: static_percentage += load[comp] remaining_load = 1 - static_percentage - total_load = load["MVA"] + # Use TOTAL (MVA + IL + YL) so that constant-current and + # constant-admittance portions are included in the split. + # Using only MVA would drop the I and Y components. + total_load = load["TOTAL"] total_distribution_load = total_load * static_percentage total_transmission_load = total_load * remaining_load # ceate new load @@ -375,7 +381,9 @@ def _get_load_dynamic_data(self, loads: list) -> dict: """ logger.info(f"_get_load_dynamic_data") values = dyn_only_options["Loads"]["lmodind"] + loads_with_dynamic_data = [] for load in loads: + has_dynamic = True for v, con_ind in values.items(): ierr = self.psse.inilod(load["bus"]) assert ierr == 0, f"error={ierr}" @@ -383,14 +391,22 @@ def _get_load_dynamic_data(self, loads: list) -> dict: assert ierr == 0, f"error={ierr}" if ld_id is not None: ierr, con_index = self.psse.lmodind(load["bus"], ld_id, "CHARAC", "CON") - assert ierr == 0, f"error={ierr}" + if ierr != 0: + logger.warning( + f"No CHARAC load model at bus {load['bus']} load '{ld_id}' " + f"(lmodind ierr={ierr}). Skipping dynamic data for this load." + ) + has_dynamic = False + break if con_index is not None: act_con_index = con_index + con_ind ierr, value = self.psse.dsrval("CON", act_con_index) assert ierr == 0, f"error={ierr}" load[v] = value logger.debug(f"Dynamic properties - Load: {v} -> index: {act_con_index}, value:{value}") - return loads + if has_dynamic: + loads_with_dynamic_data.append(load) + return loads_with_dynamic_data def setup_machine_channels(self, machines: dict, properties: list): """sets up machine channels From d73c43eae08421d43527dd5d5e6a8dc6c8d463bc Mon Sep 17 00:00:00 2001 From: Panossian Date: Tue, 31 Mar 2026 13:30:49 -0600 Subject: [PATCH 14/14] tests passing --- pypsse/models.py | 8 ++++---- pypsse/profile_manager/profile.py | 2 ++ pypsse/profile_manager/profile_store.py | 2 +- tests/examples/static_example/simulation_settings.toml | 2 +- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/pypsse/models.py b/pypsse/models.py index ad0ed19..c125395 100644 --- a/pypsse/models.py +++ b/pypsse/models.py @@ -57,11 +57,11 @@ class SimSettings(BaseModel): setup_files: List[str] = [] simulation_mode: SimulationModes disable_generation_on_coupled_buses: bool = True - generation_model_level: GenerationLevel + generation_model_level: GenerationLevel = GenerationLevel.TRANSMISSION generation_std: str = "ieee2800" - transmission_ids: List[int] - transmission_loads_at_fault: List - transmission_loads_clear_fault: List + transmission_ids: List[int] = [] + transmission_loads_at_fault: List = [] + transmission_loads_clear_fault: List = [] transmission_loads_markup: bool = False @model_validator(mode="after") diff --git a/pypsse/profile_manager/profile.py b/pypsse/profile_manager/profile.py index 8d3c13f..a742d15 100644 --- a/pypsse/profile_manager/profile.py +++ b/pypsse/profile_manager/profile.py @@ -39,6 +39,8 @@ def update(self, update_object_properties=True): if self.time < self.stime or self.time > self.etime: value = np.array([0] * len(self.profile[0])) value1 = np.array([0] * len(self.profile[0])) + valuen1 = np.array([0] * len(self.profile[0])) + dt2 = self.attrs["resTime"] else: dt = (self.time - self.stime).total_seconds() n = int(dt / self.attrs["resTime"]) diff --git a/pypsse/profile_manager/profile_store.py b/pypsse/profile_manager/profile_store.py index 2540a54..b59c221 100644 --- a/pypsse/profile_manager/profile_store.py +++ b/pypsse/profile_manager/profile_store.py @@ -283,7 +283,7 @@ def create_metadata( } for key, value in metadata.items(): if isinstance(value, str): - value_mod = np.string_(value) + value_mod = np.bytes_(value) else: value_mod = value d_set.attrs[key] = value_mod diff --git a/tests/examples/static_example/simulation_settings.toml b/tests/examples/static_example/simulation_settings.toml index 4631004..5ac7f02 100644 --- a/tests/examples/static_example/simulation_settings.toml +++ b/tests/examples/static_example/simulation_settings.toml @@ -5,7 +5,7 @@ psse_solver_timestep = 0.004166667 start_time = "2020-01-01 00:00:00.0" simulation_mode = "Steady-state" use_profile_manager = false -psse_path = "C:/Program Files/PTI/PSSE35/35.4/PSSPY39" +psse_path = "C:/Program Files/PTI/PSSE35/35.6/PSSPY39" project_path = "." case_study = "savnw.sav" raw_file = "savnw.raw"