Skip to content

Commit a249df6

Browse files
authored
Merge pull request #57 from NatLabRockies/develop_fx_bugfix
enables cosimulation with hpc settings allows non-composite loads starts on steady state enabling
2 parents 790609d + 83e2e96 commit a249df6

22 files changed

Lines changed: 567 additions & 167 deletions

pypsse/cli/explore.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pathlib import Path
2-
2+
import os
33
from loguru import logger
44
import pandas as pd
55
import click
@@ -129,7 +129,7 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load,
129129

130130
is_comp_load[bus] = is_comp
131131
load_dict[bus].append(ld_id)
132-
key = f"{ld_id} _{bus}" if len(ld_id) == 1 else f"{ld_id}_{bus}"
132+
key = f"{bus}_{ld_id}"
133133
key2 = f"{bus}_{ld_id}".replace(" ", "")
134134
load_p = max(
135135
results["Loads_MVA"][key].real + results["Loads_IL"][key].real + results["Loads_YL"][key].real,
@@ -149,7 +149,7 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load,
149149
if bus not in generator_dict:
150150
generator_dict[bus] = []
151151
bus_gen[bus] = 0
152-
key = f"{gen_id} _{bus}" if len(gen_id) == 1 else f"{gen_id}_{bus}"
152+
key = f"{bus}_{gen_id}"
153153
generator_dict[bus].append(gen_id)
154154
bus_gen[bus] += results["Machines_MVA"][key]
155155

@@ -168,7 +168,7 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load,
168168
results["is load comp"].append(is_comp_load[bus] if bus in is_comp_load else False)
169169
results["total P load [MW]"].append(bus_load_real[bus] if bus in bus_load_real else 0)
170170
results["total Q load [MVar]"].append(bus_load_imag[bus] if bus in bus_load_imag else 0)
171-
results["has generation"].append(True if bus in generator_dict else False)
171+
results["has generation"].append(True if (bus in generator_dict and bus_gen[bus] > 0)else False)
172172
results["total generation [MVA]"].append(bus_gen[bus] if bus in bus_gen else 0)
173173

174174

@@ -200,7 +200,7 @@ def explore(project_path, simulations_file, export_file_path, load_filter, load,
200200
results=results[(results["total P load [MW]"] >= load_lower) & (results["total P load [MW]"] <= load_upper)]
201201
results=results[(results["total generation [MVA]"] >= gen_lower) & (results["total generation [MVA]"] <= gen_upper)]
202202

203-
print(results)
203+
# print(results)
204204
results.to_csv(export_file_path)
205205
logger.info(f"Results exported to {export_file_path.absolute()}")
206206

pypsse/cli/profiles.py

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
"""
2-
CLI to run a PyDSS project
3-
"""
2+
CLI to run a PyDSS project
3+
"""
44

55
from pathlib import Path
6-
6+
77
from loguru import logger
88
import click
99
import toml
10-
10+
1111
from pypsse.models import SimulationSettings
1212
from pypsse.common import SIMULATION_SETTINGS_FILENAME
1313
from pypsse.profile_manager_interface import ProfileManagerInterface
14-
14+
1515
@click.argument(
1616
"project-path",
1717
)
@@ -41,4 +41,3 @@ def get_profiles(project_path, simulations_file=None):
4141

4242
profile_interface = ProfileManagerInterface.from_setting_files(file_path)
4343
profile_interface.get_profiles()
44-

pypsse/cli/run.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from loguru import logger
88
import click
99
import toml
10+
import os
1011

1112
from pypsse.models import SimulationSettings
1213
from pypsse.common import SIMULATION_SETTINGS_FILENAME
@@ -34,11 +35,13 @@ def run(project_path, simulations_file=None):
3435

3536
simulation_settiings = toml.load(file_path)
3637
simulation_settiings = SimulationSettings(**simulation_settiings)
37-
3838
logger.level(simulation_settiings.log.logging_level.value)
3939
if simulation_settiings.log.log_to_external_file:
4040
log_path = Path(project_path) / "Logs" / "pypsse.log"
41-
logger.add(log_path)
41+
if simulation_settiings.log.clear_old_log_file:
42+
logger.add(log_path, mode="w")
43+
else:
44+
logger.add(log_path)
4245

4346
x = Simulator.from_setting_files(file_path)
4447

pypsse/contingencies.py

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,14 @@ def __init__(self, psse, settings, contingency_type):
4646
self.psse = psse
4747
self.enabled = False
4848
self.tripped = False
49-
49+
logger.debug(
50+
f"contingency_type : {contingency_type}"
51+
)
52+
logger.debug(
53+
f"settings : {settings}"
54+
)
55+
# os.system("PAUSE")
56+
5057
def update(self, t: float):
5158
"""updates a fault event
5259
@@ -55,30 +62,27 @@ def update(self, t: float):
5562
"""
5663
self.t = t
5764
if hasattr(self.settings, "duration"):
58-
if (
59-
self.settings.time + self.settings.duration
60-
> t
61-
>= self.settings.time
62-
and not self.enabled
63-
):
65+
if (self.settings.time + self.settings.duration > t >= self.settings.time and not self.enabled):
6466
self.enabled = True
6567
self.enable_fault()
66-
if (
67-
t >= self.settings.time + self.settings.duration
68-
and self.enabled
69-
):
68+
if (t >= self.settings.time + self.settings.duration and self.enabled):
7069
self.enabled = False
7170
self.disable_fault()
72-
elif (
73-
not hasattr(self.settings, "duration")
74-
and t >= self.settings.time
75-
and not self.tripped
76-
):
71+
elif (not hasattr(self.settings, "duration") and t >= self.settings.time and not self.tripped):
7772
self.enable_fault()
7873
self.tripped = True
7974

8075
def enable_fault(self):
8176
"""enables a fault event"""
77+
data_check = getattr(self.psse, self.fault_method)
78+
logger.debug(
79+
f"data_check : {data_check}"
80+
)
81+
logger.debug(
82+
f"data_check : {self.fault_method}"
83+
)
84+
# os.system("PAUSE")
85+
8286
err = getattr(self.psse, self.fault_method)(**self.fault_settings)
8387
if err:
8488
logger.warning(

pypsse/data_writers/hdf5.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ def write(
119119
if self.step >= len(self.Timestamp):
120120
self.Timestamp.resize((len(self.Timestamp) + 1,))
121121
self.convergence.resize((len(self.convergence) + 1,))
122-
self.Timestamp[self.step - 1] = np.string_(currenttime.strftime("%Y-%m-%d %H:%M:%S.%f"))
122+
self.Timestamp[self.step - 1] = np.bytes_(currenttime.strftime("%Y-%m-%d %H:%M:%S.%f"))
123123
self.convergence[self.step - 1] = convergence
124124
# Add object status data to a DataFrame
125125
self.store.flush()

pypsse/enumerations.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,16 @@ class SimulationModes(str, Enum):
2828
STATIC = "Steady-state"
2929
DYNAMIC = "Dynamic"
3030

31+
class GenerationLevel(str, Enum):
32+
"Valid generation level setting modes"
33+
TRANSMISSION = "transmission"
34+
DISTRIBUTION = "distribution"
3135

3236
class HelicsCoreTypes(str, Enum):
3337
"HELICS core types"
3438
ZMQ = "zmq"
39+
TCP_SS="tcp_ss"
40+
TCP="tcp"
3541

3642

3743
class WritableModelTypes(str, Enum):
@@ -40,7 +46,9 @@ class WritableModelTypes(str, Enum):
4046
PLANT = "Plant"
4147
MACHINE = "Machine"
4248
GENERATOR = "Induction_machine"
43-
49+
LOAD_STATUS = "Load_status"
50+
LINE_STATUS = "Line_status"
51+
MACHINE_STATUS = "Machine_status"
4452

4553
class ModelTypes(str, Enum):
4654
"Supported asset tpyes in PyPSSE"

pypsse/helics_interface.py

Lines changed: 88 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import ast
2-
2+
import os
3+
import time
34
import helics as h
45
import pandas as pd
56
from loguru import logger
@@ -52,6 +53,21 @@ def __init__(
5253
self.subsystem_info = []
5354
self.publications = {}
5455
self.subscriptions = {}
56+
self.load_fault = False
57+
#################################################################
58+
# add these hardcored load values for better matching
59+
self.load_power = {
60+
tid: {
61+
"transmission_loads_at_fault": at_fault,
62+
"transmission_loads_clear_fault": clear_fault
63+
}
64+
for tid, at_fault, clear_fault in zip(
65+
settings.simulation.transmission_ids,
66+
settings.simulation.transmission_loads_at_fault,
67+
settings.simulation.transmission_loads_clear_fault
68+
)
69+
}
70+
#################################################################
5571

5672
def enter_execution_mode(self):
5773
"""Enables federate to enter execution mode"""
@@ -66,11 +82,16 @@ def enter_execution_mode(self):
6682
def create_federate(self):
6783
"""Creates a HELICS co-simulation federate"""
6884
self.fedinfo = h.helicsCreateFederateInfo()
85+
logger.debug(f"self.fedinfo: {self.fedinfo}")
6986
h.helicsFederateInfoSetCoreName(self.fedinfo, self.settings.helics.federate_name)
7087
h.helicsFederateInfoSetCoreTypeFromString(self.fedinfo, self.settings.helics.core_type.value)
7188
h.helicsFederateInfoSetCoreInitString(self.fedinfo, "--federates=1")
7289
h.helicsFederateInfoSetBroker(self.fedinfo, str(self.settings.helics.broker_ip))
7390
h.helicsFederateInfoSetBrokerPort(self.fedinfo, self.settings.helics.broker_port)
91+
IP = self.settings.helics.broker_ip
92+
Port = self.settings.helics.broker_port
93+
logger.info("Connecting to broker @ {}".format(f"{IP}:{Port}" if Port else IP))
94+
logger.info(f"Connecting to broker @ {self.settings.helics}")
7495

7596
if self.settings.helics.iterative_mode:
7697
h.helicsFederateInfoSetTimeProperty(
@@ -100,6 +121,7 @@ def register_publications(self, bus_subsystems: dict):
100121
self.publications = {}
101122
self.pub_struc = []
102123
for publication_dict in self.settings.helics.publications:
124+
logger.debug(f"publication_dict: {publication_dict}")
103125
bus_subsystem_ids = publication_dict.bus_subsystems
104126
if not set(bus_subsystem_ids).issubset(self.bus_subsystems):
105127
msg = f"One or more invalid bus subsystem ID pass in {bus_subsystem_ids}."
@@ -133,6 +155,7 @@ def register_publications(self, bus_subsystems: dict):
133155
self.pub_struc.append([{elm_class: properties}, bus_cluster])
134156
temp_res = self.sim.read_subsystems({elm_class: properties}, bus_cluster)
135157
temp_res = self.get_restructured_results(temp_res)
158+
136159
for c_name, elm_info in temp_res.items():
137160
for name, v_info in elm_info.items():
138161
for p_name, val in v_info.items():
@@ -235,11 +258,12 @@ def register_subscriptions(self):
235258
self.psse_dict[row["bus"]][row["element_type"]][element_id] = {}
236259
if isinstance(row["element_property"], str):
237260
if row["element_property"] not in self.psse_dict[row["bus"]][row["element_type"]][element_id]:
238-
self.psse_dict[row["bus"]][row["element_type"]][element_id][row["element_property"]] = 0
261+
# FX: modify this so that multiple feeders can be connected to a Transmission Bus
262+
self.psse_dict[row["bus"]][row["element_type"]][element_id][row["element_property"]] = []
239263
elif isinstance(row["element_property"], list):
240264
for r in row["element_property"]:
241265
if r not in self.psse_dict[row["bus"]][row["element_type"]][element_id]:
242-
self.psse_dict[row["bus"]][row["element_type"]][element_id][r] = 0
266+
self.psse_dict[row["bus"]][row["element_type"]][element_id][r] = []
243267

244268
def request_time(self, _) -> (bool, float):
245269
"""Enables time increment of the federate ina co-simulation."
@@ -253,8 +277,10 @@ def request_time(self, _) -> (bool, float):
253277
bool: flag for iteration requrirement (rerun same time step)
254278
float: current helics time in seconds
255279
"""
256-
280+
257281
r_seconds = self.sim.get_total_seconds() # - self._dss_solver.GetStepResolutionSeconds()
282+
logger.info(f"Time requested: {r_seconds}")
283+
258284
if self.sim.get_time() not in self.all_sub_results:
259285
self.all_sub_results[self.sim.get_time()] = {}
260286
self.all_pub_results[self.sim.get_time()] = {}
@@ -372,20 +398,23 @@ def subscribe(self) -> dict:
372398
"""
373399

374400
"Subscribes results each iteration and updates PSSE objects accordingly"
401+
375402
for sub_tag, sub_data in self.subscriptions.items():
376403
if isinstance(sub_data["property"], str):
377404
sub_data["value"] = h.helicsInputGetDouble(sub_data["subscription"])
405+
logger.debug(f"sub_data is {sub_data}")
378406
self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][
379407
sub_data["property"]
380-
] = (sub_data["value"], sub_data["scaler"])
408+
].append((sub_data["value"], sub_data["scaler"]))
381409
elif isinstance(sub_data["property"], list):
382410
sub_data["value"] = h.helicsInputGetVector(sub_data["subscription"])
411+
logger.debug(f"sub_data is {sub_data}")
383412
if isinstance(sub_data["value"], list) and len(sub_data["value"]) == len(sub_data["property"]):
384413
for i, p in enumerate(sub_data["property"]):
385-
self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][p] = (
414+
self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][p].append((
386415
sub_data["value"][i],
387416
sub_data["scaler"][i],
388-
)
417+
))
389418

390419
logger.debug("Data received {} for tag {}".format(sub_data["value"], sub_tag))
391420
if self.settings.helics.iterative_mode:
@@ -399,31 +428,76 @@ def subscribe(self) -> dict:
399428
for i, v_dict in t_info.items():
400429
values = {}
401430
j = 0
402-
for p, v_raw in v_dict.items():
403-
if isinstance(v_raw, tuple):
404-
v, scale = v_raw
405-
all_values[f"{t}.{b}.{i}.{p}"] = v
431+
for p, v_raws in v_dict.items():
432+
if isinstance(v_raws, list):
406433
if isinstance(p, str):
407434
ppty = f"realar{PROFILE_VALIDATION[t].index(p) + 1}"
408-
values[ppty] = v * scale
435+
values[ppty] = 0
436+
for v_raw in v_raws:
437+
v, scale = v_raw
438+
if isinstance(scale, (float, int)):
439+
values[ppty] += v * scale
440+
else:
441+
values[ppty] += v
442+
all_values[f"{t}.{b}.{i}.{p}"] = values[ppty]
409443
elif isinstance(p, list):
410444
for _, ppt in enumerate(p):
411445
ppty = f"realar{PROFILE_VALIDATION[t].index(ppt) + 1}"
412-
values[ppty] = v * scale
446+
values[ppty] = 0
447+
for v_raw in v_raws:
448+
v, scale = v_raw
449+
if isinstance(scale, (float, int)):
450+
values[ppty] += v * scale
451+
else:
452+
values[ppty] += v
453+
all_values[f"{t}.{b}.{i}.{p}"] = values[ppty]
413454
j += 1
414-
415455
is_empty = [0 if not vx else 1 for vx in values.values()]
456+
logger.debug(f"{t}.{b}.{i} = {values}")
457+
logger.debug(f"current HELICE time: {self.c_seconds}")
458+
######################################################
459+
## add this for better transit matching
460+
if round(self.c_seconds, 3) == 0.1 and self.settings.simulation.transmission_loads_markup:
461+
logger.debug("the moment of fault")
462+
logger.debug(f"old {t}.{b}.{i} = {values}")
463+
logger.debug(self.load_power)
464+
load_data = self.load_power[b]['transmission_loads_at_fault']
465+
values['realar1'] = load_data[0]
466+
values['realar2'] = load_data[1]
467+
# os.system("PAUSE")
468+
if round(self.c_seconds, 3) == 0.175 and self.settings.simulation.transmission_loads_markup:
469+
logger.debug("the moment of clearing fault")
470+
logger.debug(f"old {t}.{b}.{i} = {values}")
471+
load_data = self.load_power[b]['transmission_loads_clear_fault']
472+
values['realar1'] = load_data[0]
473+
values['realar2'] = load_data[1]
474+
# os.system("PAUSE")
475+
######################################################
416476
if (
417477
sum(is_empty) != 0
418478
and sum(values.values()) < VALUE_UPDATE_BOUND
419479
and sum(values.values()) > -VALUE_UPDATE_BOUND
480+
and self.c_seconds > 0.02
420481
):
421482
self.sim.update_object(t, b, i, values)
422483
logger.debug(f"{t}.{b}.{i} = {values}")
423484

424485
else:
425486
logger.debug("write failed")
487+
488+
######################################################
489+
## clear the result list
490+
for sub_tag, sub_data in self.subscriptions.items():
491+
if isinstance(sub_data["property"], str):
492+
self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][sub_data["property"]] = []
493+
elif isinstance(sub_data["property"], list):
494+
if isinstance(sub_data["value"], list) and len(sub_data["value"]) == len(sub_data["property"]):
495+
for i, p in enumerate(sub_data["property"]):
496+
self.psse_dict[sub_data["bus"]][sub_data["element_type"]][sub_data["element_id"]][p] = []
497+
logger.debug(f"clear self.psse_dict is {self.psse_dict}")
498+
######################################################
426499

500+
# os.system("PAUSE")
427501
self.c_seconds_old = self.c_seconds
428502
return all_values
429503

0 commit comments

Comments
 (0)