From 9fb446bfabea7d82a1c5eea8356b874b60e9afc1 Mon Sep 17 00:00:00 2001 From: Damien Jeandemange Date: Sun, 24 Nov 2024 14:58:35 +0100 Subject: [PATCH] Add components (islands) view with Load Flow results Signed-off-by: Damien Jeandemange --- yagat/frames/impl/base_list_view.py | 3 ++ yagat/frames/impl/components_list_view.py | 39 ++++++++++++++++ yagat/frames/impl/logs_view.py | 1 + yagat/frames/impl/tabs_view.py | 2 + yagat/menus/impl/run.py | 10 ++-- yagat/menus/impl/view.py | 3 ++ .../impl/network_structure.py | 46 +++++++++++++++++++ 7 files changed, 101 insertions(+), 3 deletions(-) create mode 100644 yagat/frames/impl/components_list_view.py diff --git a/yagat/frames/impl/base_list_view.py b/yagat/frames/impl/base_list_view.py index 4830dcf..314b971 100644 --- a/yagat/frames/impl/base_list_view.py +++ b/yagat/frames/impl/base_list_view.py @@ -130,6 +130,9 @@ def __init__(self, column_name: str, editable: bool = False): 'ac_interchange': DoubleColumnFormat('ac_interchange', precision=PRECISION_POWER), 'dc_interchange': DoubleColumnFormat('dc_interchange', precision=PRECISION_POWER), 'ac': BooleanColumnFormat('ac'), + 'iteration_count': IntegerColumnFormat('iteration_count'), + 'active_power_mismatch': DoubleColumnFormat('active_power_mismatch', precision=PRECISION_POWER), + 'distributed_active_power': DoubleColumnFormat('distributed_active_power', precision=PRECISION_POWER), } diff --git a/yagat/frames/impl/components_list_view.py b/yagat/frames/impl/components_list_view.py new file mode 100644 index 0000000..6d6254a --- /dev/null +++ b/yagat/frames/impl/components_list_view.py @@ -0,0 +1,39 @@ +# +# Copyright (c) 2024, Damien Jeandemange (https://github.com/jeandemanged) +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. +# SPDX-License-Identifier: MPL-2.0 +# +from typing import Any + +import pandas as pd + +from yagat.app_context import AppContext +from yagat.frames.impl.base_list_view import BaseListView, BaseColumnFormat + + +class ComponentsListView(BaseListView): + + def __init__(self, parent, context: AppContext, *args, **kwargs): + BaseListView.__init__(self, parent, context, *args, **kwargs) + + @property + def tab_name(self) -> str: + return 'Components (Islands)' + + @property + def tab_group_name(self) -> str: + return 'Components (Islands)' + + def get_data_frame(self) -> pd.DataFrame: + return self.context.network_structure.components + + def get_column_formats(self) -> dict[str, BaseColumnFormat]: + return super().get_column_formats() + + def on_entry(self, ident: str, column_name: str, new_value: Any): + raise RuntimeError('Components do not support update') + + def filter_data_frame(self, df: pd.DataFrame, voltage_levels: list[str]) -> pd.DataFrame: + return df diff --git a/yagat/frames/impl/logs_view.py b/yagat/frames/impl/logs_view.py index 1ed468f..075116d 100644 --- a/yagat/frames/impl/logs_view.py +++ b/yagat/frames/impl/logs_view.py @@ -47,6 +47,7 @@ def __init__(self, parent, *args, **kwargs): logging.info(_pypowsybl.get_version_table()) def emit(self, record): + # FIXME: slowing down everything ! msg = self.format(record) self.sheet.insert_row(row=[record.asctime, record.levelname, msg], idx=0) if record.levelname == 'WARNING': diff --git a/yagat/frames/impl/tabs_view.py b/yagat/frames/impl/tabs_view.py index c626146..64db217 100644 --- a/yagat/frames/impl/tabs_view.py +++ b/yagat/frames/impl/tabs_view.py @@ -13,6 +13,7 @@ from yagat.app_context import AppContext from yagat.frames.impl.area_boundaries_list_view import AreaBoundariesListView from yagat.frames.impl.area_list_view import AreaListView +from yagat.frames.impl.components_list_view import ComponentsListView from yagat.frames.impl.diagram_view_bus import DiagramViewBus from yagat.frames.impl.buses_bus_view_list_view import BusesListView from yagat.frames.impl.buses_bus_breaker_view_list_view import BusesBusBreakerViewListView @@ -60,6 +61,7 @@ def __init__(self, parent, context: AppContext, *args, **kwargs): self._add_tab(HvdcLineView(self.tab_control, self.context)) self._add_tab(AreaListView(self.tab_control, self.context)) self._add_tab(AreaBoundariesListView(self.tab_control, self.context)) + self._add_tab(ComponentsListView(self.tab_control, self.context)) self.tab_control.pack(expand=True, fill=tk.BOTH) diff --git a/yagat/menus/impl/run.py b/yagat/menus/impl/run.py index f3f39c0..49c88f1 100644 --- a/yagat/menus/impl/run.py +++ b/yagat/menus/impl/run.py @@ -34,10 +34,14 @@ def on_done(): def task(): if ac: - results = lf.run_ac(self.context.network, parameters=self.context.lf_parameters, reporter=reporter) + lf_components_results = lf.run_ac(self.context.network, + parameters=self.context.lf_parameters, + reporter=reporter) else: - results = lf.run_dc(self.context.network, parameters=self.context.lf_parameters, reporter=reporter) - print(results) + lf_components_results = lf.run_dc(self.context.network, + parameters=self.context.lf_parameters, + reporter=reporter) + self.context.network_structure.lf_components_results = lf_components_results print(reporter) self.context.start_long_running_task(name='Load Flow', target=task, on_done=on_done) diff --git a/yagat/menus/impl/view.py b/yagat/menus/impl/view.py index b95fa25..2dc859a 100644 --- a/yagat/menus/impl/view.py +++ b/yagat/menus/impl/view.py @@ -41,6 +41,9 @@ def __init__(self, parent, context: AppContext, *args, **kwargs): self.add_command(label='Areas', command=lambda: self.update_view_and_tab_group('TreeAndTabs', 'Areas List')) self.add_separator() + self.add_command(label='Components (Islands)', + command=lambda: self.update_view_and_tab_group('TreeAndTabs', 'Components (Islands)')) + self.add_separator() self.add_command(label='Load Flow Parameters', command=self.view_load_flow_parameters) self.add_separator() self.add_command(label='Logs', command=self.view_logs) diff --git a/yagat/networkstructure/impl/network_structure.py b/yagat/networkstructure/impl/network_structure.py index f677202..acddaf5 100644 --- a/yagat/networkstructure/impl/network_structure.py +++ b/yagat/networkstructure/impl/network_structure.py @@ -8,7 +8,9 @@ import logging from typing import Dict, List, Optional, Tuple, Union +import numpy as np import pandas as pd +import pypowsybl.loadflow as lf import pypowsybl.network as pn import yagat.networkstructure as ns @@ -38,6 +40,9 @@ def __init__(self, network: pn.Network): self._linear_shunt_compensator_sections_df: pd.DataFrame = pd.DataFrame() self._non_linear_shunt_compensator_sections_df: pd.DataFrame = pd.DataFrame() + self._components_df: pd.DataFrame = pd.DataFrame() + self._lf_components_results: list[lf.ComponentResult] = [] + self._bus_breaker_topology_cache: Dict[str, pn.BusBreakerTopology] = {} self.refresh() @@ -106,6 +111,14 @@ def __init__(self, network: pn.Network): def network(self) -> pn.Network: return self._network + @property + def lf_components_results(self) -> list[lf.ComponentResult]: + return self._lf_components_results + + @lf_components_results.setter + def lf_components_results(self, value: list[lf.ComponentResult]) -> None: + self._lf_components_results = value + @property def areas(self) -> pd.DataFrame: return self._areas_df @@ -174,6 +187,10 @@ def tie_lines(self) -> pd.DataFrame: def hvdc_lines(self) -> pd.DataFrame: return self._hvdc_lines_df + @property + def components(self) -> pd.DataFrame: + return self._components_df + def refresh(self): logging.info('refresh start') @@ -208,6 +225,35 @@ def refresh(self): .merge(tmp, left_on='voltage_level_id', right_on='id', how='left') .set_index('id')) + logging.info('building components ...') + components = list(zip(self._buses_df.connected_component, self._buses_df.synchronous_component)) + components.sort() + components = [f'CC{connected_component} SC{synchronous_component}' + for (connected_component, synchronous_component) in components] + df = pd.DataFrame(index=components) + df = df[~df.index.duplicated(keep='first')] + new_columns = {'status': [''] * len(df), + 'status_text': [''] * len(df), + 'iteration_count': [np.nan] * len(df), + 'reference_bus_id': [''] * len(df), + 'slack_buses_ids': [''] * len(df), + 'active_power_mismatch': [np.nan] * len(df), + 'distributed_active_power': [np.nan] * len(df), + } + self._components_df = df.assign(**new_columns) + + for cr in self._lf_components_results: + cid = f'CC{cr.connected_component_num} SC{cr.synchronous_component_num}' + self._components_df.loc[cid, 'status'] = cr.status.name + self._components_df.loc[cid, 'status_text'] = cr.status_text + self._components_df.loc[cid, 'iteration_count'] = cr.iteration_count + self._components_df.loc[cid, 'reference_bus_id'] = cr.reference_bus_id + self._components_df.loc[cid, 'slack_buses_ids'] = ','.join([sbr.id for sbr in cr.slack_bus_results]) + self._components_df.loc[cid, 'active_power_mismatch'] = ( + sum(sbr.active_power_mismatch for sbr in cr.slack_bus_results) + ) + self._components_df.loc[cid, 'distributed_active_power'] = cr.distributed_active_power + logging.info('get_bus_breaker_view_buses') self._buses_bus_breaker_view_df = (self._network.get_bus_breaker_view_buses() .reset_index()