Skip to content

Commit 711a153

Browse files
authored
Merge pull request #48 from NREL/feature/move_import
extending cli to enable model exploration
2 parents 529ee7c + c45c1e0 commit 711a153

14 files changed

Lines changed: 228 additions & 12 deletions
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
name: Pytests
2+
3+
on: pull_request
4+
5+
jobs:
6+
build:
7+
runs-on: ${{ matrix.os }}
8+
strategy:
9+
matrix:
10+
os: [ubuntu-latest, macos-latest]
11+
#os: [ubuntu-latest, macos-latest, windows-latest]
12+
python-version: ["3.7", "3.10", "3.11"]
13+
14+
steps:
15+
- uses: actions/checkout@v2
16+
- name: Set up Python ${{ matrix.python-version }}
17+
uses: actions/setup-python@v2
18+
with:
19+
python-version: ${{ matrix.python-version }}
20+
- name: Install dependencies
21+
run: |
22+
python -m pip install --upgrade pip
23+
python -m pip install '.[dev]'
24+
- name: Run pytests
25+
run: |
26+
python -m pytest -v --disable-warnings

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ path = "pypsse/__init__.py"
5252
[tool.hatch.envs.test]
5353
dependencies = [
5454
"coverage[toml]",
55-
"pytest-xdist"
55+
"pytest-xdist",
5656
"pytest-mock",
5757
"pytest-cov",
5858
"pytest",

pypsse/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.9"
1+
__version__ = "1.1.0"

pypsse/api/web/handler.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@
1919

2020
shutdown_event = Event()
2121

22-
2322
class Handler:
2423

2524
"""Handlers for web server."""

pypsse/cli/explore.py

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
from pathlib import Path
2+
3+
from loguru import logger
4+
import pandas as pd
5+
import click
6+
7+
from pypsse.common import EXPORTS_SETTINGS_FILENAME, SIMULATION_SETTINGS_FILENAME
8+
from pypsse.models import ExportFileOptions, SimulationSettings
9+
from pypsse.simulator import Simulator
10+
11+
@click.argument(
12+
"project-path",
13+
)
14+
@click.option(
15+
"-s",
16+
"--simulations-file",
17+
required=False,
18+
default=SIMULATION_SETTINGS_FILENAME,
19+
show_default=True,
20+
help="scenario toml file to run (over rides default)",
21+
)
22+
@click.option(
23+
"-e",
24+
"--export-file-path",
25+
required=False,
26+
default="./filtered_results.csv",
27+
show_default=True,
28+
help="path for exporting filtered results",
29+
)
30+
@click.option(
31+
"-l",
32+
"--load-filter",
33+
help="applies load filters if set to true",
34+
is_flag=True,
35+
default=False,
36+
show_default=True,
37+
)
38+
@click.option(
39+
'--load/--no-load',
40+
required=False,
41+
show_default=True,
42+
help="filter by load [bool]",
43+
)
44+
@click.option(
45+
"-g",
46+
"--gen-filter",
47+
help="applies generator filters if set to true",
48+
is_flag=True,
49+
default=False,
50+
show_default=True,
51+
)
52+
@click.option(
53+
'--generation/--no-generation',
54+
required=False,
55+
show_default=True,
56+
help="filter by generation [bool]",
57+
)
58+
@click.option(
59+
'--comp-load/--no-comp-load',
60+
required=False,
61+
show_default=True,
62+
help="filter by composite load models [bool]",
63+
)
64+
@click.option(
65+
"-b",
66+
"--apply-bounds",
67+
help="applies load and generation limit bounds if set to true",
68+
is_flag=True,
69+
default=False,
70+
show_default=True,
71+
)
72+
@click.option(
73+
'--load-bounds',
74+
required=False,
75+
show_default=True,
76+
default= "0/10000",
77+
help="bounds for load [example 10/100]",
78+
)
79+
@click.option(
80+
'--gen-bounds',
81+
required=False,
82+
show_default=True,
83+
default= "0/10000",
84+
help="bounds for generation ",
85+
)
86+
@click.command()
87+
def explore(project_path, simulations_file, export_file_path, load_filter, load, gen_filter, generation, apply_bounds, comp_load, load_bounds, gen_bounds):
88+
"""Runs a valid PyPSSE simulation."""
89+
90+
export_file_path = Path(export_file_path)
91+
92+
file_path = Path(project_path) / simulations_file
93+
msg = "Simulation file not found. Use -s to choose a valid settings file"
94+
"if its name differs from the default file name."
95+
assert file_path.exists(), msg
96+
x = Simulator.from_setting_files(file_path)
97+
buses = set(x.raw_data.buses)
98+
quantities = {
99+
'Loads': ['MVA', 'FmA', 'FmB', 'FmC', 'FmD', 'Fel', 'PFel'],
100+
'Induction_generators': ['MVA'],
101+
'Machines': ['MVA', 'PERCENT'],
102+
}
103+
results = x.sim.read_subsystems(quantities, buses)
104+
had_comp_models = False
105+
if "Loads_FmA" in results:
106+
had_comp_models = True
107+
108+
load_dict = {}
109+
bus_load_real = {}
110+
bus_load_imag = {}
111+
is_comp_load = {}
112+
for bus, ld_id in x.raw_data.loads:
113+
if bus not in load_dict:
114+
load_dict[bus] = []
115+
bus_load_real[bus] = 0
116+
bus_load_imag[bus] = 0
117+
is_comp_load = []
118+
119+
if had_comp_models:
120+
is_comp = True if f'{bus}_{ld_id}' in results["Loads_FmA"] else False
121+
else:
122+
is_comp = False
123+
is_comp_load.append(is_comp)
124+
load_dict[bus].append(ld_id)
125+
key = f"{ld_id} _{bus}" if len(ld_id) == 1 else f"{ld_id}_{bus}"
126+
bus_load_real[bus] += results["Loads_MVA"][key].real
127+
bus_load_imag[bus] += results["Loads_MVA"][key].imag
128+
129+
generator_dict = {}
130+
bus_gen = {}
131+
for bus, gen_id in x.raw_data.generators:
132+
if bus not in load_dict:
133+
generator_dict[bus] = []
134+
bus_gen[bus] = 0
135+
key = f"{gen_id} _{bus}" if len(gen_id) == 1 else f"{gen_id}_{bus}"
136+
generator_dict[bus].append(gen_id)
137+
bus_gen[bus] += results["Machines_MVA"][key]
138+
139+
results = {
140+
"bus" : [],
141+
"has load" : [],
142+
"is load comp" : [],
143+
"total P load [MW]" : [],
144+
"total Q load [MVar]" : [],
145+
"has generation" : [],
146+
"total generation [MVA]" : [],
147+
}
148+
for bus in x.raw_data.buses:
149+
results["bus"].append(bus)
150+
results["has load"].append(True if bus in load_dict else False)
151+
results["is load comp"].append(is_comp_load[bus] if bus in is_comp_load else False)
152+
results["total P load [MW]"].append(bus_load_real[bus] if bus in bus_load_real else 0)
153+
results["total Q load [MVar]"].append(bus_load_imag[bus] if bus in bus_load_imag else 0)
154+
results["has generation"].append(True if bus in generator_dict else False)
155+
results["total generation [MVA]"].append(bus_gen[bus] if bus in bus_gen else 0)
156+
157+
158+
results = pd.DataFrame(results)
159+
if filter:
160+
if load_filter and load:
161+
results=results[results["has load"] == True]
162+
elif load_filter and not load:
163+
results=results[results["has load"] == False]
164+
165+
if gen_filter and generation:
166+
results=results[results["has generation"] == True]
167+
elif gen_filter and not generation:
168+
results=results[results["has generation"] == False]
169+
170+
if load_filter and comp_load:
171+
results=results[results["is load comp"] == True]
172+
elif load_filter and not comp_load:
173+
results=results[results["is load comp"] == False]
174+
175+
load_lower, load_upper = [float(x) for x in load_bounds.split("/")]
176+
gen_lower, gen_upper = [float(x) for x in gen_bounds.split("/")]
177+
if apply_bounds:
178+
results=results[(results["total P load [MW]"] >= load_lower) & (results["total P load [MW]"] <= load_upper)]
179+
results=results[(results["total generation [MVA]"] >= gen_lower) & (results["total generation [MVA]"] <= gen_upper)]
180+
181+
results.to_csv(export_file_path)
182+
183+
184+
185+
186+

pypsse/cli/pypsse.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
from pypsse.cli.create_profiles import create_profiles
77
from pypsse.cli.create_project import create_project
8+
from pypsse.cli.explore import explore
89
from pypsse.cli.run import run
910

1011
server_dependencies_installed = True
@@ -26,5 +27,6 @@ def cli():
2627
cli.add_command(create_project)
2728
cli.add_command(run)
2829
cli.add_command(create_profiles)
30+
cli.add_command(explore)
2931
if server_dependencies_installed:
3032
cli.add_command(serve)

pypsse/cli/run.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
from pathlib import Path
66

77
import click
8-
import toml
98

10-
from pypsse.common import EXPORTS_SETTINGS_FILENAME, SIMULATION_SETTINGS_FILENAME
11-
from pypsse.models import ExportFileOptions, SimulationSettings
9+
from pypsse.common import SIMULATION_SETTINGS_FILENAME
1210
from pypsse.simulator import Simulator
1311

1412

pypsse/mdao_interface.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,8 @@ def _build_outputs(self, output: dict = None) -> dict:
7171
outputs = json.loads(self.probelm.outputs.model_dump_json())
7272
buses = outputs["buses"]
7373
quantities = outputs["quantities"]
74-
74+
print(buses)
75+
print(quantities)
7576
results = self.psse_obj.sim.read_subsystems(subsystem_buses=buses, quantities=quantities)
7677
if output is None:
7778
output = {}

pypsse/utils/dynamic_utils.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,13 @@ def disable_load_models_for_coupled_buses(self):
7070
bus = row["bus"]
7171
load = row["element_id"]
7272
ierr = self.psse.ldmod_status(0, int(bus), str(load), 1, 0)
73-
assert ierr == 0, f"error={ierr}"
74-
logger.error(f"Dynamic model for load {load} connected to bus {bus} has been disabled")
75-
73+
if ierr == 0:
74+
logger.info(f"Dynamic model for load {load} connected to bus {bus} has been disabled")
75+
elif ierr == 5:
76+
logger.error(f"No dynamic model found for load {load} connected to bus {bus}")
77+
else:
78+
raise Exception(f"error={ierr}")
79+
7680
def break_loads(self, loads: list = None, components_to_replace: List[str] = []):
7781
"""Implements the load split logic
7882
Binary file not shown.

0 commit comments

Comments
 (0)