From 77bfedc87674a74f36b65a1e53fbdc286c057b2d Mon Sep 17 00:00:00 2001 From: Christian Biasuzzi Date: Thu, 16 May 2024 12:40:10 +0200 Subject: [PATCH] adds a VL explorer widget with NAD and SLD in two different tabs (#6) * adds a VL explorer widget with NAD and SLD in two different tabs * renames vl_explorer to network_explorer * removes redundant sld explorer and sld navigator (their features are now included in the network explorer) Signed-off-by: Christian Biasuzzi --- ...orer.ipynb => demo_network_explorer.ipynb} | 22 ++- examples/demo_sld_navigator.ipynb | 58 ------- src/pypowsybl_jupyter/__init__.py | 3 +- src/pypowsybl_jupyter/networkexplorer.py | 152 ++++++++++++++++++ src/pypowsybl_jupyter/sldexplorer.py | 81 ---------- src/pypowsybl_jupyter/sldnavigator.py | 52 ------ 6 files changed, 162 insertions(+), 206 deletions(-) rename examples/{demo_sld_explorer.ipynb => demo_network_explorer.ipynb} (56%) delete mode 100644 examples/demo_sld_navigator.ipynb create mode 100644 src/pypowsybl_jupyter/networkexplorer.py delete mode 100644 src/pypowsybl_jupyter/sldexplorer.py delete mode 100644 src/pypowsybl_jupyter/sldnavigator.py diff --git a/examples/demo_sld_explorer.ipynb b/examples/demo_network_explorer.ipynb similarity index 56% rename from examples/demo_sld_explorer.ipynb rename to examples/demo_network_explorer.ipynb index 38e33f7..d660085 100644 --- a/examples/demo_sld_explorer.ipynb +++ b/examples/demo_network_explorer.ipynb @@ -3,37 +3,33 @@ { "cell_type": "code", "execution_count": null, - "id": "676b6701-3638-47f5-aa59-3d7b109ce622", + "id": "f42abf42-4718-4f92-a542-68ae289906a6", "metadata": {}, "outputs": [], "source": [ - "from pypowsybl_jupyter import sld_explorer\n", - "import pypowsybl.network as pn" + "import pypowsybl.network as pn\n", + "import pypowsybl.loadflow as lf\n", + "from pypowsybl_jupyter import network_explorer" ] }, { "cell_type": "code", "execution_count": null, - "id": "f775ac95-c697-49a6-a163-78a63b73cc42", + "id": "ce646543-5d2d-4933-8106-dfb7a281124e", "metadata": {}, "outputs": [], "source": [ - "network = pn.create_ieee14()" + "network=pn.create_four_substations_node_breaker_network()" ] }, { "cell_type": "code", "execution_count": null, - "id": "02dc0a4c-35ca-4496-9e44-ff31c197422c", + "id": "74dd5af0-167d-41a2-a9a3-3ca3c701fe5d", "metadata": {}, "outputs": [], "source": [ - "#Display a simple explorer widget for the network\n", - "#built by assembling the SLD widget and other basic ipywidgets.\n", - "#Select a VL from the VL list to show its SLD diagram.\n", - "#The Filter text box can be used to search for specific VLs.\n", - "\n", - "sld_explorer(network)" + "network_explorer(network, depth=4)" ] } ], @@ -53,7 +49,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.12.2" + "version": "3.12.3" } }, "nbformat": 4, diff --git a/examples/demo_sld_navigator.ipynb b/examples/demo_sld_navigator.ipynb deleted file mode 100644 index a3d520a..0000000 --- a/examples/demo_sld_navigator.ipynb +++ /dev/null @@ -1,58 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": null, - "id": "3a3c8cd6-f197-4eb8-bf73-cbd931b244bb", - "metadata": {}, - "outputs": [], - "source": [ - "import pypowsybl as pp\n", - "from pypowsybl_jupyter import sld_navigator" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "3e65bbd6-b3c8-4294-87e1-3699b47a6952", - "metadata": {}, - "outputs": [], - "source": [ - "network = pp.network.create_four_substations_node_breaker_network()" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1f638951-dc80-404f-a45f-abffa0b638be", - "metadata": {}, - "outputs": [], - "source": [ - "#display a simple in-place SLD navigator widget for the network\n", - "#(starting from the first network's VL, click on a VL arrow to update the widget, click on a switch to change its status)\n", - "sld_navigator(network, network.get_voltage_levels().index[0])" - ] - } - ], - "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.2" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/src/pypowsybl_jupyter/__init__.py b/src/pypowsybl_jupyter/__init__.py index 4a4781c..b2b6569 100644 --- a/src/pypowsybl_jupyter/__init__.py +++ b/src/pypowsybl_jupyter/__init__.py @@ -10,12 +10,11 @@ from .sldwidget import ( SldWidget, display_sld, update_sld ) -from .sldexplorer import sld_explorer -from .sldnavigator import sld_navigator from .nadwidget import ( NadWidget, display_nad, update_nad ) from .nadexplorer import nad_explorer +from .networkexplorer import network_explorer try: __version__ = importlib.metadata.version("pypowsybl_jupyter") diff --git a/src/pypowsybl_jupyter/networkexplorer.py b/src/pypowsybl_jupyter/networkexplorer.py new file mode 100644 index 0000000..ccb417a --- /dev/null +++ b/src/pypowsybl_jupyter/networkexplorer.py @@ -0,0 +1,152 @@ +# Copyright (c) 2024, RTE (http://www.rte-france.com) +# 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 pypowsybl.network import Network, NadParameters, LayoutParameters +from .nadwidget import display_nad, update_nad +from .sldwidget import display_sld, update_sld + +import ipywidgets as widgets + +def network_explorer(network: Network, vl_id : str = None, depth: int = 0, high_nominal_voltage_bound: float = -1, low_nominal_voltage_bound: float = -1, nad_parameters: NadParameters = None, sld_parameters: LayoutParameters = None): + """ + Creates a combined NAD and SLD explorer widget for the network. Diagrams are displayed on two different tabs. + + Args: + network: the input network + vl_id: the starting VL to display. If None, display the first VL from network.get_voltage_levels() + depth: the diagram depth around the voltage level, controls the size of the sub network. In the SLD tab will be always displayed one diagram, from the VL list currently selected item. + low_nominal_voltage_bound: low bound to filter voltage level according to nominal voltage + 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 + + Examples: + + .. code-block:: python + + network_explorer(pp.network.create_eurostag_tutorial_example1_network()) + """ + + vls = network.get_voltage_levels(attributes=[]) + nad_widget=None + sld_widget=None + + selected_vl = vls.index[0] if vl_id is None else vl_id + if selected_vl not in vls.index: + raise ValueError(f'a voltage level {vl_id} does not exist in the network.') + + selected_depth=depth + + npars = nad_parameters if nad_parameters is not None else NadParameters(edge_name_displayed=False, + id_displayed=False, + edge_info_along_edge=False, + power_value_precision=1, + angle_value_precision=0, + current_value_precision=1, + voltage_value_precision=0, + bus_legend=False, + substation_description_displayed=True) + + spars=sld_parameters if sld_parameters is not None else LayoutParameters(use_name=True) + + def go_to_vl(event: any): + nonlocal selected_vl + selected_vl= str(event.clicked_nextvl) + found.value=selected_vl + + def toggle_switch(event: any): + idswitch = event.clicked_switch.get('id') + statusswitch = event.clicked_switch.get('switch_status') + network.update_switches(id=idswitch, open=statusswitch) + update_sld_diagram(True) + update_nad_diagram() + + + def update_nad_diagram(): + nonlocal nad_widget + if len(selected_vl)>0: + new_diagram_data=network.get_network_area_diagram(voltage_level_ids=selected_vl, depth=selected_depth, high_nominal_voltage_bound=high_nominal_voltage_bound, low_nominal_voltage_bound=low_nominal_voltage_bound, nad_parameters=npars) + if nad_widget==None: + nad_widget=display_nad(new_diagram_data) + else: + update_nad(nad_widget,new_diagram_data) + + def update_sld_diagram(kv: bool = False): + nonlocal sld_widget + if selected_vl is not None: + sld_diagram_data=network.get_single_line_diagram(selected_vl, spars) + if sld_widget==None: + sld_widget=display_sld(sld_diagram_data, enable_callbacks=True) + sld_widget.on_nextvl(lambda event: go_to_vl(event)) + sld_widget.on_switch(lambda event: toggle_switch(event)) + + else: + update_sld(sld_widget, sld_diagram_data, keep_viewbox=kv, enable_callbacks=True) + + + nadslider = widgets.IntSlider(value=selected_depth, min=0, max=20, step=1, description='depth:', disabled=False, continuous_update=False, orientation='horizontal', readout=True, readout_format='d') + + def on_nadslider_changed(d): + nonlocal selected_depth + selected_depth=d['new'] + update_nad_diagram() + + nadslider.observe(on_nadslider_changed, names='value') + + vl_input = widgets.Text( + value='', + placeholder='Voltage level ID', + description='Filter', + disabled=False, + continuous_update=True + ) + + def on_text_changed(d): + nonlocal selected_vl + found.options = vls[vls.index.str.contains(d['new'], regex=False)].index + if len(found.options) > 0: + selected_vl=d['new'] + else: + selected_vl=None + + vl_input.observe(on_text_changed, names='value') + + found = widgets.Select( + options=list(vls.index), + value=selected_vl, + description='Found', + disabled=False, + layout=widgets.Layout(height='670px') + ) + + def on_selected(d): + nonlocal selected_vl + if d['new'] != None: + selected_vl=d['new'] + update_nad_diagram() + update_sld_diagram() + + found.observe(on_selected, names='value') + + update_nad_diagram() + update_sld_diagram() + + left_panel = widgets.VBox([widgets.Label('Voltage levels'), vl_input, found]) + + right_panel_nad = widgets.VBox([nadslider, nad_widget]) + right_panel_sld = widgets.HBox([sld_widget]) + + tabs_diagrams = widgets.Tab() + tabs_diagrams.children = [right_panel_nad, right_panel_sld] + tabs_diagrams.titles = ['Network Area', 'Single Line'] + tabs_diagrams.layout=widgets.Layout(width='850px', height='700px') + + hbox = widgets.HBox([left_panel, tabs_diagrams]) + hbox.layout.align_items='flex-end' + + return hbox + \ No newline at end of file diff --git a/src/pypowsybl_jupyter/sldexplorer.py b/src/pypowsybl_jupyter/sldexplorer.py deleted file mode 100644 index 384a6d2..0000000 --- a/src/pypowsybl_jupyter/sldexplorer.py +++ /dev/null @@ -1,81 +0,0 @@ -# Copyright (c) 2020-2024, RTE (http://www.rte-france.com) -# 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 IPython.display import display -from pypowsybl.network import Network, LayoutParameters -from .sldwidget import display_sld, update_sld - -import ipywidgets as widgets - - -def sld_explorer(network: Network, vl_id: str = None, parameters: LayoutParameters = None): - """ - Creates a basic SLD explorer widget for a network, built with the sld widget. - - Args: - network: the input network - vl_id: the starting VL to display. If None, display the first VL from network.get_voltage_levels() - parameters: layout properties to adjust the svg rendering for the sld - - Examples: - - .. code-block:: python - - sld_explorer(pp.network.create_four_substations_node_breaker_network()) - """ - - _params=parameters if parameters is not None else LayoutParameters(use_name=True) - vls = network.get_voltage_levels(attributes=[]) - selected_vl= vls.index[0] if vl_id is None else vl_id - sld_widget=None - - vl_input = widgets.Text( - value='', - placeholder='Voltage level ID', - description='Filter', - disabled=False, - continuous_update=True - ) - - def on_text_changed(d): - found.options = vls[vls.index.str.contains(d['new'], regex=False)].index - - vl_input.observe(on_text_changed, names='value') - - found = widgets.Select( - options=vls.index, - value=selected_vl, - description='Found', - disabled=False, - layout=widgets.Layout(height='570px') - ) - - def update_diagram(): - nonlocal sld_widget - if selected_vl is not None: - new_diagram_data=network.get_single_line_diagram(selected_vl, _params) - if sld_widget==None: - sld_widget=display_sld(new_diagram_data) - else: - update_sld(sld_widget, new_diagram_data) - - def on_selected(d): - nonlocal selected_vl - if d['new'] != None: - selected_vl=d['new'] - update_diagram() - - found.observe(on_selected, names='value') - - update_diagram() - - left_panel = widgets.VBox([widgets.Label('Voltage levels'), vl_input, found]) - right_panel = widgets.VBox([sld_widget]) - hbox = widgets.HBox([left_panel, right_panel]) - hbox.layout.align_items='flex-end' - - return hbox \ No newline at end of file diff --git a/src/pypowsybl_jupyter/sldnavigator.py b/src/pypowsybl_jupyter/sldnavigator.py deleted file mode 100644 index a0e12b0..0000000 --- a/src/pypowsybl_jupyter/sldnavigator.py +++ /dev/null @@ -1,52 +0,0 @@ -# Copyright (c) 2020-2024, RTE (http://www.rte-france.com) -# 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 IPython.display import display -from ipywidgets import widgets -from pypowsybl.network import Network, LayoutParameters -from .sldwidget import display_sld, update_sld - -def sld_navigator(network: Network, vl_id: str = None, parameters: LayoutParameters = None): - """ - created a basic, in-place, SLD network explorer. Click on a VL arrows to move to another VL. - Click on a switch to change its status. - - Args: - network: the input network - vl_id: the starting VL to display. If None, display the first VL from network.get_voltage_levels() - parameters: layout properties to adjust the svg rendering for the sld - - Examples: - - .. code-block:: python - - sld_navigator(pp.network.create_four_substations_node_breaker_network()) - """ - - _params=parameters if parameters is not None else LayoutParameters(use_name=True) - _current_vl_id = vl_id if vl_id is not None else network.get_voltage_levels().index[0] - _sldwidget = None - - def _toggle_switch(event: any): - idswitch = event.clicked_switch.get('id') - statusswitch = event.clicked_switch.get('switch_status') - network.update_switches(id=idswitch, open=statusswitch) - update_sld(_sldwidget, network.get_single_line_diagram(_current_vl_id, _params), True, enable_callbacks= True) - - def _go_to_vl(event: any): - nonlocal _current_vl_id - _current_vl_id= str(event.clicked_nextvl) - update_sld(_sldwidget, network.get_single_line_diagram(_current_vl_id, _params), enable_callbacks=True) - - diagram_panel = widgets.Output() - with diagram_panel: - _sldwidget = display_sld(network.get_single_line_diagram(_current_vl_id, _params), enable_callbacks=True) - _sldwidget.on_nextvl(lambda event: _go_to_vl(event)) - _sldwidget.on_switch(lambda event: _toggle_switch(event)) - display(_sldwidget) - - return widgets.HBox([diagram_panel]) \ No newline at end of file