Skip to content

Commit

Permalink
Merge pull request #48 from NREL/feature/move_import
Browse files Browse the repository at this point in the history
extending cli to enable model exploration
  • Loading branch information
AadilLatif authored Feb 7, 2024
2 parents 529ee7c + c45c1e0 commit 711a153
Show file tree
Hide file tree
Showing 14 changed files with 228 additions and 12 deletions.
26 changes: 26 additions & 0 deletions .github/workflows/pull_request_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Pytests

on: pull_request

jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
#os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.7", "3.10", "3.11"]

steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install '.[dev]'
- name: Run pytests
run: |
python -m pytest -v --disable-warnings
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ path = "pypsse/__init__.py"
[tool.hatch.envs.test]
dependencies = [
"coverage[toml]",
"pytest-xdist"
"pytest-xdist",
"pytest-mock",
"pytest-cov",
"pytest",
Expand Down
2 changes: 1 addition & 1 deletion pypsse/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "1.0.9"
__version__ = "1.1.0"
1 change: 0 additions & 1 deletion pypsse/api/web/handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@

shutdown_event = Event()


class Handler:

"""Handlers for web server."""
Expand Down
186 changes: 186 additions & 0 deletions pypsse/cli/explore.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
from pathlib import Path

from loguru import logger
import pandas as pd
import click

from pypsse.common import EXPORTS_SETTINGS_FILENAME, SIMULATION_SETTINGS_FILENAME
from pypsse.models import ExportFileOptions, SimulationSettings
from pypsse.simulator import Simulator

@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.option(
"-e",
"--export-file-path",
required=False,
default="./filtered_results.csv",
show_default=True,
help="path for exporting filtered results",
)
@click.option(
"-l",
"--load-filter",
help="applies load filters if set to true",
is_flag=True,
default=False,
show_default=True,
)
@click.option(
'--load/--no-load',
required=False,
show_default=True,
help="filter by load [bool]",
)
@click.option(
"-g",
"--gen-filter",
help="applies generator filters if set to true",
is_flag=True,
default=False,
show_default=True,
)
@click.option(
'--generation/--no-generation',
required=False,
show_default=True,
help="filter by generation [bool]",
)
@click.option(
'--comp-load/--no-comp-load',
required=False,
show_default=True,
help="filter by composite load models [bool]",
)
@click.option(
"-b",
"--apply-bounds",
help="applies load and generation limit bounds if set to true",
is_flag=True,
default=False,
show_default=True,
)
@click.option(
'--load-bounds',
required=False,
show_default=True,
default= "0/10000",
help="bounds for load [example 10/100]",
)
@click.option(
'--gen-bounds',
required=False,
show_default=True,
default= "0/10000",
help="bounds for generation ",
)
@click.command()
def explore(project_path, simulations_file, export_file_path, load_filter, load, gen_filter, generation, apply_bounds, comp_load, load_bounds, gen_bounds):
"""Runs a valid PyPSSE simulation."""

export_file_path = Path(export_file_path)

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
x = Simulator.from_setting_files(file_path)
buses = set(x.raw_data.buses)
quantities = {
'Loads': ['MVA', 'FmA', 'FmB', 'FmC', 'FmD', 'Fel', 'PFel'],
'Induction_generators': ['MVA'],
'Machines': ['MVA', 'PERCENT'],
}
results = x.sim.read_subsystems(quantities, buses)
had_comp_models = False
if "Loads_FmA" in results:
had_comp_models = True

load_dict = {}
bus_load_real = {}
bus_load_imag = {}
is_comp_load = {}
for bus, ld_id in x.raw_data.loads:
if bus not in load_dict:
load_dict[bus] = []
bus_load_real[bus] = 0
bus_load_imag[bus] = 0
is_comp_load = []

if had_comp_models:
is_comp = True if f'{bus}_{ld_id}' in results["Loads_FmA"] else False
else:
is_comp = False
is_comp_load.append(is_comp)
load_dict[bus].append(ld_id)
key = f"{ld_id} _{bus}" if len(ld_id) == 1 else f"{ld_id}_{bus}"
bus_load_real[bus] += results["Loads_MVA"][key].real
bus_load_imag[bus] += results["Loads_MVA"][key].imag

generator_dict = {}
bus_gen = {}
for bus, gen_id in x.raw_data.generators:
if bus not in load_dict:
generator_dict[bus] = []
bus_gen[bus] = 0
key = f"{gen_id} _{bus}" if len(gen_id) == 1 else f"{gen_id}_{bus}"
generator_dict[bus].append(gen_id)
bus_gen[bus] += results["Machines_MVA"][key]

results = {
"bus" : [],
"has load" : [],
"is load comp" : [],
"total P load [MW]" : [],
"total Q load [MVar]" : [],
"has generation" : [],
"total generation [MVA]" : [],
}
for bus in x.raw_data.buses:
results["bus"].append(bus)
results["has load"].append(True if bus in load_dict else False)
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["total generation [MVA]"].append(bus_gen[bus] if bus in bus_gen else 0)


results = pd.DataFrame(results)
if filter:
if load_filter and load:
results=results[results["has load"] == True]
elif load_filter and not load:
results=results[results["has load"] == False]

if gen_filter and generation:
results=results[results["has generation"] == True]
elif gen_filter and not generation:
results=results[results["has generation"] == False]

if load_filter and comp_load:
results=results[results["is load comp"] == True]
elif load_filter and not comp_load:
results=results[results["is load comp"] == False]

load_lower, load_upper = [float(x) for x in load_bounds.split("/")]
gen_lower, gen_upper = [float(x) for x in gen_bounds.split("/")]
if apply_bounds:
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)]

results.to_csv(export_file_path)





2 changes: 2 additions & 0 deletions pypsse/cli/pypsse.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from pypsse.cli.create_profiles import create_profiles
from pypsse.cli.create_project import create_project
from pypsse.cli.explore import explore
from pypsse.cli.run import run

server_dependencies_installed = True
Expand All @@ -26,5 +27,6 @@ def cli():
cli.add_command(create_project)
cli.add_command(run)
cli.add_command(create_profiles)
cli.add_command(explore)
if server_dependencies_installed:
cli.add_command(serve)
4 changes: 1 addition & 3 deletions pypsse/cli/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
from pathlib import Path

import click
import toml

from pypsse.common import EXPORTS_SETTINGS_FILENAME, SIMULATION_SETTINGS_FILENAME
from pypsse.models import ExportFileOptions, SimulationSettings
from pypsse.common import SIMULATION_SETTINGS_FILENAME
from pypsse.simulator import Simulator


Expand Down
3 changes: 2 additions & 1 deletion pypsse/mdao_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@ def _build_outputs(self, output: dict = None) -> dict:
outputs = json.loads(self.probelm.outputs.model_dump_json())
buses = outputs["buses"]
quantities = outputs["quantities"]

print(buses)
print(quantities)
results = self.psse_obj.sim.read_subsystems(subsystem_buses=buses, quantities=quantities)
if output is None:
output = {}
Expand Down
10 changes: 7 additions & 3 deletions pypsse/utils/dynamic_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,13 @@ def disable_load_models_for_coupled_buses(self):
bus = row["bus"]
load = row["element_id"]
ierr = self.psse.ldmod_status(0, int(bus), str(load), 1, 0)
assert ierr == 0, f"error={ierr}"
logger.error(f"Dynamic model for load {load} connected to bus {bus} has been disabled")

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(self, loads: list = None, components_to_replace: List[str] = []):
"""Implements the load split logic
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
4 changes: 2 additions & 2 deletions tests/test_web_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
from pypsse.models import ApiAssetQuery, ApiWebSocketRequest


server = Server()
# server = Server()

client = TestClient(server.app)
# client = TestClient(server.app)


# def test_web_sockt_interface():
Expand Down

0 comments on commit 711a153

Please sign in to comment.