{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Group Module" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The Group module allows you to combine several elements of a pandapower net into a group. Various functions are available, which are then automatically applied to all elements in this group.\n", "This tutorial shows you how to create groups and how to use some helpful simple group functions." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To analyse the group functionality we use the CIGRE MV net." ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import numpy as np\n", "import pandapower as pp\n", "from pandapower.networks import create_cigre_network_mv\n", "from pandapower.create import create_group\n", "net = create_cigre_network_mv(with_der=\"all\")\n", "net.switch[\"closed\"] = True" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "import pandapower.create" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Create Groups\n", "As examples, we define two groups, one to represent a virtual power plant with sgens, loads and a storage and for all elements of the second feeder of the net.\n", "You can create groups using lists of element types and element indices, or you can pass all that information in one dict." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "# define a Group as virtual power plant\n", "gr1_name = \"virtual power plant\"\n", "vpp_element_types = [\"storage\", \"sgen\", \"load\"]\n", "vpp_elements = [[1], [6, 8, 9, 10, 11, 12], [5, 6]]\n", "gr1_idx = create_group(net, vpp_element_types, vpp_elements, name=gr1_name)\n", "#gr1_idx = pp.group(net, vpp_element_types, vpp_elements)\n", "\n", "# define a Group of a Feeder 2\n", "gr2_name = \"Feeder2\"\n", "feeder2buses = [12, 13, 14]\n", "feeder2_elements_dict = pp.get_connected_elements_dict(net, feeder2buses)\n", "feeder2_elements_dict[\"bus\"] = feeder2buses\n", "gr2_idx = pp.create_group_from_dict(net, feeder2_elements_dict, name=gr2_name)\n", "#gr2_idx = pp.branch_element_bus_dict(net, feeder2_elements_dict)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we can see that there are entries in `net.group` for both groups. As usual in pandapower, the information is accessed by means of the index. For groups with multiple element types, e.g. the virtual power plant includes storages, sgens and loads, multiple rows with the same index are created to `net.group`." ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ " name element_type element reference_column\n", "0 virtual power plant storage [1] None\n", "0 virtual power plant sgen [6, 8, 9, 10, 11, 12] None\n", "0 virtual power plant load [5, 6] None\n", "1 Feeder2 line [10, 11, 14] None\n", "1 Feeder2 switch [5] None\n", "1 Feeder2 bus [12, 13, 14] None\n", "1 Feeder2 trafo [1] None\n", "1 Feeder2 load [8, 9, 15, 16, 17] None\n" ] } ], "source": [ "print(net.group)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Set `reference_column`\n", "However, the user can manipulate the indices of the net element dataframes and some helper functions change/may not preserve the element indices, such as `pp.create_continuous_elements_index(net)`. As a result, by means of the indices, a group can no longer find its members.\n", "For that reason, groups can also detect their members by a column of the elements dataframes. That can be applied directly at the group definition or later using `set_group_reference_column()`. Then we can see, that the group does no longer store the indices but the values of the set reference column:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Group 1 data with indices:\n", "\n", " name element_type element reference_column\n", "0 virtual power plant storage [1] None\n", "0 virtual power plant sgen [6, 8, 9, 10, 11, 12] None\n", "0 virtual power plant load [5, 6] None\n", "\n", "After setting 'name' as reference column, the group stores the members by the names:\n", "\n", " name element_type \\\n", "0 virtual power plant storage \n", "0 virtual power plant sgen \n", "0 virtual power plant load \n", "\n", " element reference_column \n", "0 [Battery 2] name \n", "0 [PV 10, WKA 7, Residential fuel cell 1, CHP di... name \n", "0 [Load R8, Load R10] name \n" ] } ], "source": [ "print(\"Group 1 data with indices:\\n\")\n", "print(net.group.loc[gr1_idx])\n", "\n", "pp.set_group_reference_column(net, gr1_idx, \"name\")\n", "\n", "print(\"\\nAfter setting 'name' as reference column, the group stores the members by the names:\\n\")\n", "print(net.group.loc[gr1_idx])" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we can see, the second group now stores the names of the members and not indices. Using `group_element_index()` you can nevertheless get the indices of the group members. You can see that these are still the same as given in the definition of the group above." ] }, { "cell_type": "code", "execution_count": 6, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Int64Index([5, 6], dtype='int64')\n" ] } ], "source": [ "print(pp.group_element_index(net, gr1_idx, \"load\"))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Attention:** Be aware that `reference_column` only works fine if there are no duplicated values in `net[element][reference_column]` for all members of the groups!" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Set Values to all Group Members\n", "The following code block shows you how to set the value to all members of a group. A specific use case of the is to set all members in service or out of service. For that reason, these got explicit function names." ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The load names:\n", "\n", "0 Load R1\n", "1 Load R3\n", "2 Load R4\n", "3 Load R5\n", "4 Load R6\n", "5 Load R8\n", "6 Load R10\n", "7 Load R11\n", "8 member of group 'Feeder2'\n", "9 member of group 'Feeder2'\n", "10 Load CI1\n", "11 Load CI3\n", "12 Load CI7\n", "13 Load CI9\n", "14 Load CI10\n", "15 member of group 'Feeder2'\n", "16 member of group 'Feeder2'\n", "17 member of group 'Feeder2'\n", "Name: name, dtype: object\n", "\n", "The bus results with Feeder 2 out of service:\n", "\n", " vm_pu va_degree p_mw q_mvar\n", "0 1.030000 0.000000 -22.818564 -8.646534\n", "1 0.993935 -6.110560 19.839000 4.637136\n", "2 0.976713 -6.707834 0.000000 0.000000\n", "3 0.949406 -7.665482 0.481700 0.208882\n", "4 0.947294 -7.750513 0.411650 0.108182\n", "5 0.946493 -7.784148 1.264500 0.182329\n", "6 0.947350 -7.674175 0.518050 0.137354\n", "7 0.947704 -7.645199 -1.423500 0.047410\n", "8 0.947124 -7.719307 0.556850 0.147078\n", "9 0.946759 -7.729477 0.021750 0.355578\n", "10 0.946407 -7.773404 0.689300 0.161264\n", "11 0.946652 -7.770208 0.319800 0.082656\n", "12 NaN NaN 0.000000 0.000000\n", "13 NaN NaN 0.000000 0.000000\n", "14 NaN NaN 0.000000 0.000000\n", "\n", "The bus results with Feeder 2 back in service:\n", "\n", " vm_pu va_degree p_mw q_mvar\n", "0 1.030000 0.000000 -43.40813 -15.655818\n", "1 0.996932 -5.726043 19.83900 4.637136\n", "2 0.986772 -6.003966 0.00000 0.000000\n", "3 0.970355 -6.429326 0.48170 0.208882\n", "4 0.968860 -6.482045 0.41165 0.108182\n", "5 0.968247 -6.505724 1.26450 0.182329\n", "6 0.969547 -6.377311 0.51805 0.137354\n", "7 0.969965 -6.346000 -1.42350 0.047410\n", "8 0.969896 -6.391227 0.55685 0.147078\n", "9 0.969338 -6.411158 0.02175 0.355578\n", "10 0.968510 -6.477714 0.68930 0.161264\n", "11 0.968542 -6.485215 0.31980 0.082656\n", "12 0.997599 -5.859926 20.01000 4.693341\n", "13 0.982794 -6.130536 0.03400 0.021071\n", "14 0.973929 -6.298161 0.54005 0.257713\n" ] } ], "source": [ "# setting a name value to group 2 members\n", "pp.set_value_to_group(net, gr2_idx, \"member of group '%s'\" % gr2_name, \"name\")\n", "\n", "# visualize the effect\n", "print(\"The load names:\\n\")\n", "print(net.load.name)\n", "\n", "# set all elements of group 2 out of service\n", "pp.set_group_out_of_service(net, gr2_idx)\n", "pp.runpp(net)\n", "print(\"\\nThe bus results with Feeder 2 out of service:\\n\")\n", "print(net.res_bus) # the Feeder 2 buses should now have nan values\n", "\n", "# and back in service...\n", "pp.set_group_in_service(net, gr2_idx)\n", "pp.runpp(net)\n", "print(\"\\nThe bus results with Feeder 2 back in service:\\n\")\n", "print(net.res_bus)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Sum Group Consumption Power\n", "Groups can sum its complete power consumption including losses. Since the virtual power plant group predominantly consists of generation units, its active power value is negative." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Group 'virtual power plant' consumes -0.85 MW and 0.27 Mvar.\n", "Group 'Feeder2' consumes 20.66 MW and 7.45 Mvar.\n" ] } ], "source": [ "for gr_idx, gr_name in zip([gr1_idx, gr2_idx], [gr1_name, gr2_name]):\n", " print(\"Group '%s' consumes %.2f MW and %.2f Mvar.\" % (\n", " gr_name, pp.group_res_p_mw(net, gr_idx), pp.group_res_q_mvar(net, gr_idx)))\n", "\n", "# a validation of Feeder 2 group values is easy since there is only one trafo and one line\n", "# which supply the feeder:\n", "p_val = net.res_line.p_to_mw.at[14] + net.res_trafo.p_hv_mw.at[1]\n", "assert np.isclose(pp.group_res_p_mw(net, gr2_idx), p_val)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Append and Drop Group Members\n", "Once defined, it still easy to change the members of a group:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [], "source": [ "p_before = pp.group_res_p_mw(net, gr1_idx)\n", "\n", "# append the virtual power plant\n", "pp.attach_to_group(net, gr1_idx, [\"storage\"], [[net.storage.name.at[0]]], \"name\")\n", "# drop an sgen from the virtual power plant\n", "pp.detach_from_group(net, gr1_idx, \"load\", [5])\n", "\n", "# validate via compare the active power consumption\n", "assert np.isclose(p_before + net.res_storage.p_mw.at[0] - net.res_load.p_mw.at[5], pp.group_res_p_mw(net, gr1_idx))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Some more functionality" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "For small groups, `print(net.group.loc[[group_index]]` can be used to see quickly how many members are included. For larger groups, `count_group_elements()` is available to access the number of group members per element type:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Number of member per element type:\n", "line 3\n", "switch 1\n", "bus 3\n", "trafo 1\n", "load 5\n", "dtype: int32\n", "\n", " Overall number of members: 13\n" ] } ], "source": [ "no_member = pp.count_group_elements(net, gr2_idx)\n", "print(\"Number of member per element type:\")\n", "print(no_member)\n", "print(f\"\\n Overall number of members: {no_member.sum()}\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If `net.group` has been corrupted, `ensure_lists_in_group_element_column()` and `remove_not_existing_group_members()` may help:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "hp.pandapower.groups - INFO: net.group row 6 is dropped because no fitting elements exist in net[trafo].\n" ] }, { "name": "stdout", "output_type": "stream", "text": [ " name element_type \\\n", "0 virtual power plant storage \n", "0 virtual power plant sgen \n", "0 virtual power plant load \n", "1 Feeder2 line \n", "1 Feeder2 switch \n", "1 Feeder2 bus \n", "1 Feeder2 load \n", "\n", " element reference_column \n", "0 [Battery 2, Battery 1] name \n", "0 [PV 10, WKA 7, Residential fuel cell 1, CHP di... name \n", "0 [Load R10] name \n", "1 [10, 11, 14] None \n", "1 [5] None \n", "1 [12, 13, 14] None \n", "1 [5] None \n" ] } ], "source": [ "# assume net.group is corrupted for some reason, as...\n", "net.group.element.iat[-1] = 5 # no list\n", "net.trafo.drop(1, inplace=True) # net elements were dropped without using toolbox functions like drop_trafos()\n", "\n", "pp.ensure_lists_in_group_element_column(net) # fixes no list data\n", "pp.remove_not_existing_group_members(net) # detects that transformer 1 does not exist and drops it\n", "print(net.group)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To explore some more functions, such as `groups_equal()`, `compare_group_elements()` and others, please have a look to the Code." ] } ], "metadata": { "anaconda-cloud": {}, "kernelspec": { "display_name": "Python 3.9.16 ('base')", "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.9.16" }, "vscode": { "interpreter": { "hash": "19d1d53a962d236aa061289c2ac16dc8e6d9648c89fe79f459ae9a3493bc67b4" } } }, "nbformat": 4, "nbformat_minor": 2 }