From ce273a24ee040ca993a3a69b10032e8e8c3599dc Mon Sep 17 00:00:00 2001 From: Christian Biasuzzi Date: Fri, 30 Aug 2024 19:44:38 +0200 Subject: [PATCH] adds a function parameter to the explorer, to filter the voltage levels list using some custom logic Signed-off-by: Christian Biasuzzi --- ...emo_network_explorer_filter_function.ipynb | 117 ++++++++++++++++++ src/pypowsybl_jupyter/networkexplorer.py | 7 +- src/pypowsybl_jupyter/selectcontext.py | 26 +++- 3 files changed, 144 insertions(+), 6 deletions(-) create mode 100644 examples/demo_network_explorer_filter_function.ipynb diff --git a/examples/demo_network_explorer_filter_function.ipynb b/examples/demo_network_explorer_filter_function.ipynb new file mode 100644 index 0000000..75a3d31 --- /dev/null +++ b/examples/demo_network_explorer_filter_function.ipynb @@ -0,0 +1,117 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "id": "f42abf42-4718-4f92-a542-68ae289906a6", + "metadata": {}, + "outputs": [], + "source": [ + "import pypowsybl.network as pn\n", + "from pypowsybl_jupyter import network_explorer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ece1d9b1-31b4-4b43-8e2c-42773d7ffbeb", + "metadata": {}, + "outputs": [], + "source": [ + "#Define a ComponentType enum for a Network, and a filter function that returns the set of the network VL's ids, \n", + "#for all the VLs that include a component of type c_type" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20558132-2382-49e9-ac8e-c5b7afd0d139", + "metadata": {}, + "outputs": [], + "source": [ + "from enum import Enum\n", + "\n", + "class ComponentType(Enum):\n", + " LINE = 'LINE'\n", + " TWT = 'TWT'\n", + " SVC = 'SVC' \n", + " GENERATOR = 'GENERATOR'\n", + " BATTERY = 'BATTERY'\n", + "\n", + "def filter_vls_by_included_component_type(network: pn.Network, c_type:ComponentType):\n", + " if c_type == ComponentType.LINE:\n", + " df = network.get_lines()[['voltage_level1_id', 'voltage_level2_id']]\n", + " vls = set(df['voltage_level1_id']).union(set(df['voltage_level2_id']))\n", + " elif c_type == ComponentType.TWT:\n", + " df = network.get_2_windings_transformers()[['voltage_level1_id', 'voltage_level2_id']]\n", + " vls = set(df['voltage_level1_id']).union(set(df['voltage_level2_id']))\n", + " elif c_type == ComponentType.GENERATOR:\n", + " df = network.get_generators()[['voltage_level_id']]\n", + " vls = set(df['voltage_level_id']) \n", + " elif c_type == ComponentType.SVC:\n", + " df = network.get_static_var_compensators()[['voltage_level_id']]\n", + " vls = set(df['voltage_level_id']) \n", + " elif c_type == ComponentType.BATTERY:\n", + " df = network.get_batteries()[['voltage_level_id']]\n", + " vls = set(df['voltage_level_id']) \n", + " \n", + " if len(vls) == 0: \n", + " raise ValueError(f'the network does not contain any voltage level that includse a {c_type.name}')\n", + "\n", + " return vls" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ca2e7af-7d2a-4169-b71d-57cf3f8980a4", + "metadata": {}, + "outputs": [], + "source": [ + "# Creates a network, then opens an explorer filtering the VL by component type" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce646543-5d2d-4933-8106-dfb7a281124e", + "metadata": {}, + "outputs": [], + "source": [ + "four_substations_network = pn.create_four_substations_node_breaker_network()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6bb2f0a1-97e4-4cf5-975b-7e1651c830bb", + "metadata": {}, + "outputs": [], + "source": [ + "network_explorer(four_substations_network,\n", + " filter_vls_function = lambda network: filter_vls_by_included_component_type(network, ComponentType.SVC))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/src/pypowsybl_jupyter/networkexplorer.py b/src/pypowsybl_jupyter/networkexplorer.py index 3ab2c98..dfaa1da 100644 --- a/src/pypowsybl_jupyter/networkexplorer.py +++ b/src/pypowsybl_jupyter/networkexplorer.py @@ -6,6 +6,7 @@ # from pypowsybl.network import Network, NadParameters, SldParameters +from typing import Callable, Set from .nadwidget import display_nad, update_nad from .sldwidget import display_sld, update_sld from .selectcontext import SelectContext @@ -14,7 +15,8 @@ def network_explorer(network: Network, vl_id : str = None, use_name:bool = True, depth: int = 0, high_nominal_voltage_bound: float = -1, low_nominal_voltage_bound: float = -1, - nad_parameters: NadParameters = None, sld_parameters: SldParameters = None): + nad_parameters: NadParameters = None, sld_parameters: SldParameters = None, + filter_vls_function: Callable[[Network], Set[str]] = None): """ Creates a combined NAD and SLD explorer widget for the network. Diagrams are displayed on two different tabs. @@ -27,6 +29,7 @@ def network_explorer(network: Network, vl_id : str = None, use_name:bool = True, high_nominal_voltage_bound: high bound to filter voltage level according to nominal voltage nad_parameters: layout properties to adjust the svg rendering for the NAD sld_parameters: layout properties to adjust the svg rendering for the SLD + filter_vls_function: filter the voltage levels list, using some custom logic. The parameter function must return a set of VL ids Examples: @@ -35,7 +38,7 @@ def network_explorer(network: Network, vl_id : str = None, use_name:bool = True, network_explorer(pp.network.create_eurostag_tutorial_example1_network()) """ - sel_ctx=SelectContext(network, vl_id, use_name, history_max_length = 10) + sel_ctx=SelectContext(network, vl_id, use_name, history_max_length = 10, filter_vls_function = filter_vls_function) nad_widget=None sld_widget=None diff --git a/src/pypowsybl_jupyter/selectcontext.py b/src/pypowsybl_jupyter/selectcontext.py index 073a94c..b433474 100644 --- a/src/pypowsybl_jupyter/selectcontext.py +++ b/src/pypowsybl_jupyter/selectcontext.py @@ -8,10 +8,11 @@ import pandas as pd from collections import deque from pypowsybl.network import Network +from typing import Callable, Set class SelectContext: - def __init__(self, network:Network = None, vl_id : str = None, use_name:bool = True, history_max_length:int = -1): + def __init__(self, network:Network = None, vl_id : str = None, use_name:bool = True, history_max_length:int = -1, filter_vls_function: Callable[[Network], Set[str]] = None): self.network = network self.use_name = use_name self.display_attribute = 'name' if use_name else 'id' @@ -20,13 +21,24 @@ def __init__(self, network:Network = None, vl_id : str = None, use_name:bool = T self.vls['name'] = self.vls['name'].replace('', pd.NA).fillna(self.vls.index.to_series().astype(str)) self.vls['id'] = self.vls.index + if self.vls.empty: + raise ValueError(f'the network does not contain any voltage level.') + + self.filter_vls_function = filter_vls_function + self.vls = self.vls.sort_values(by=self.display_attribute) if use_name else self.vls.sort_index() self.apply_filter(None) self.history = deque(maxlen=None if history_max_length == -1 else history_max_length) - self.set_selected(self.vls.index[0] if vl_id is None else vl_id) + self.set_selected(self.vls_filtered.index[0] if vl_id is None else vl_id) + + def filter_vls(self, filter_function: Callable[[Network], Set[str]]) -> pd.DataFrame: + vls_df = self.get_vls() + filtered_vls_ids = filter_function(self.network) + filtered_vls = vls_df[vls_df['id'].isin(filtered_vls_ids)] + return filtered_vls def get_vls(self): return self.vls @@ -42,11 +54,17 @@ def get_selected(self): return self.selected_vl def apply_filter(self, sfilter, search_attribute = None): + if self.filter_vls_function is not None: + funcfiltered=self.filter_vls(self.filter_vls_function) + else: + funcfiltered=self.vls + if sfilter is not None and sfilter != '': search_by = self.display_attribute if search_attribute is None else search_attribute - self.vls_filtered = self.vls[self.vls[search_by].str.contains(sfilter, case=False, na=False, regex=False)] + self.vls_filtered = funcfiltered[funcfiltered[search_by].str.contains(sfilter, case=False, na=False, regex=False)] else: - self.vls_filtered = self.vls + self.vls_filtered = funcfiltered + def is_selected_in_filtered_vls(self): return self.selected_vl in self.vls_filtered.index