{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Introduction to pandapower's control module" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The control module in pandapower allows you to **adjust, regulate and manipulate individual components** within a power grid. The possibilities here are many and varied and are purely up to your imagination. They range from simple controllers that make it possible, for example, to adjust and manipulate the entries of loads, feeders or storage units in the course of a time series simulation, to equipment-specific controllers such as tap changers, through to complex controllers that control the various flexibilities within a power grid. \n", "\n", "This tutorial is intended to help you gain a basic understanding of the control module and thus enable you to use the control module for your purposes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Structure of the control module" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Compared to the other components that make up a power grid, the **controller module is rather exotic**. This is because, unlike with all other components, we had to realize that it makes less sense here to start from the classic table structure and try to embed the controllers in a fixed structure. In other words, while **buses, lines, and so on always depend on fixed variables**, this does not apply to controllers: **controllers are extremely flexible**. On the one hand, this is their strength (the possibilities here are limitless), but on the other hand it means that you lose the clear table structure that you are familiar with from the other components you have already seen.\n", "\n", "However, this should not prevent you from using our control module as you wish. We have tried to make it as structured as possible so that you can quickly find your way around and easily create your own controllers.\n", "\n", "### Network embedding of the controllers\n", "\n", "**Every controller that is integrated into a pandapower network is an object**. Each controller object is based on the abstract *Controller* class in basic_controller.py. A **controller always requires a network to which the controller is assigned**. " ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [], "source": [ "import pandapower as pp\n", "\n", "net = pp.create_empty_network()" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The basic controller only needs the network as an external variable:" ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [], "source": [ "from pandapower.control.basic_controller import Controller" ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [], "source": [ "basic_control = Controller(net)" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "This pandapower network includes the following parameter tables:\n", " - controller (1 element)" ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A look at the individual attributes reveals the elementary parts of each controller:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "{'index': 0, 'matching_params': {}}" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "basic_control.__dict__" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The *index* is the indexing within the network, so to speak, the place where you can find your controller under net.controller. The storage location is a pandas DataFrame.\n", "\n", "*matching_params* checks if the defined controller already exists and enables the user to remove already defined controllers with the same parameters." ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "scrolled": true }, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
objectin_serviceorderlevelinitial_runrecycle
0ControllerTrue0.00TrueFalse
\n", "
" ], "text/plain": [ " object in_service order level initial_run recycle\n", "0 Controller True 0.0 0 True False" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.controller" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You will find the index at the beginning of each line. This is assigned automatically, but can also be defined individually by you. You will also find further important information here:\n", "\n", "The information *in_service* gives you the option of **ignoring a controller** at any time without having to delete it. \n", "\n", "*order* and *level*, on the other hand, are best explained graphically. \n", "Assuming you have 5 controllers:\n", "\n", "\n", "\n", "\n", "These are now placed in a sequence according to their *level* and *order*. It is important to note that **the higher** the *level* and *order*, **the later** the controllers become active, i.e. controllers with a higher *level* and *order* are **more important as they can react** to the control of a previously executed controller. Using the example above, this would look like this:\n", "\n", "\n", "\n", "\n", "Controllers **within a *level* must always all converge**. Controllers in **different *levels* are independent of each other**. An example would look like this. Suppose there are two level controllers that control the voltage of a certain node. One controller wants the voltage at this node to always be below 1 p.u., while the other wants exactly the opposite. Even though this example is unlikely to be found in reality, it illustrates the difference between *level* and *order* quite well. If both controllers were at the same *level*, the controller loop would never converge, as the conditions of both controllers could never be fulfilled. However, if one of the controllers were at a higher *level*, each individual controller loop would again find a solution without any problems, as these two controllers are independent of each other. One controller would, for example, push the voltage below 1 p.u. in its *level*, while the second would then reverse this in its *level*. \n", "\n", "### Basic structure of each controller\n", "\n", "The most important functions of each controller are as follows:\n", "- intialize_control\n", "- control_step\n", "- repair_control\n", "- finalize_control\n", "\n", "Each of these four functions is ultimately relevant for the control process and describes the behaviour of the controller in the network. *initialize_control* is called at the **beginning, before the controller loops are run through**. For example, the intial values of P and Q of a specific load/feed could be called up here. The *control_step* describes the **actual control behavior of the controller**. *repair_control* enables you to make a **one-off correction for each control loop if the grid does not converge** due to the controller. For example, the handling of occurring NaN values could be described here. *finalize_control* is **executed at the end**. A typical example here is to set the converged flag back to False so that the controller is not inadvertently ingorized in the subsequent control loop.\n", "\n", "Other relevant functions are:\n", "- is_converged\n", "- set_recycle\n", "\n", "*is_converged* describes the conditions under which the **controller has stabilized**, i.e. has converged. *set_recycle* defines the extent to which the **Jacobian matrix may be reused** in successive controller loops.\n", "\n", "Furhter important functions are *time_step*, *restore_init_state*, *finalize_step*. However, these are only relevant for the **time series simulation** and for this reason we refer to the time series simulation tutorial at this point.\n", "\n", "These are the primarily relevant functions of any controller. If you want to build a controller yourself, you must always consider these functions." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Using the control module" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This example, which is intended to familiarize you with the control module, uses the tap controller (TapControl). To do this, you must first load the MV-Oberrhein grid, which contains two 110/220 kV transformers:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "c:\\users\\srdm\\git\\pandapower\\pandapower\\networks\\mv_oberrhein.py:75: FutureWarning: ChainedAssignmentError: behaviour will change in pandas 3.0!\n", "You are setting values through chained assignment. Currently this works in certain cases, but when using Copy-on-Write (which will become the default behaviour in pandas 3.0) this will never work to update the original DataFrame or Series, because the intermediate object on which we are setting values will behave as a copy.\n", "A typical example is when you are setting values in a column of a DataFrame, like:\n", "\n", "df[\"col\"][row_indexer] = value\n", "\n", "Use `df.loc[row_indexer, \"col\"] = values` instead, to perform the assignment in a single step and ensure this keeps updating the original `df`.\n", "\n", "See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy\n", "\n", " net.trafo.tap_pos.loc[hv_trafos] = [-2, -3]\n" ] }, { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
namestd_typehv_buslv_bussn_mvavn_hv_kvvn_lv_kvvk_percentvkr_percentpfe_kwi0_percentshift_degreetap_sidetap_neutraltap_mintap_maxtap_step_percenttap_step_degreetap_postap_phase_shifterparalleldfin_service
114HV/MV Transformer 025 MVA 110/20 kV583925.0110.020.011.20.28229.00.071150.0hv0-991.5NaN-2False11.0True
142HV/MV Transformer 125 MVA 110/20 kV31831925.0110.020.011.20.28229.00.071150.0hv0-991.5NaN-3False11.0True
\n", "
" ], "text/plain": [ " name std_type hv_bus lv_bus sn_mva vn_hv_kv \\\n", "114 HV/MV Transformer 0 25 MVA 110/20 kV 58 39 25.0 110.0 \n", "142 HV/MV Transformer 1 25 MVA 110/20 kV 318 319 25.0 110.0 \n", "\n", " vn_lv_kv vk_percent vkr_percent pfe_kw i0_percent shift_degree \\\n", "114 20.0 11.2 0.282 29.0 0.071 150.0 \n", "142 20.0 11.2 0.282 29.0 0.071 150.0 \n", "\n", " tap_side tap_neutral tap_min tap_max tap_step_percent \\\n", "114 hv 0 -9 9 1.5 \n", "142 hv 0 -9 9 1.5 \n", "\n", " tap_step_degree tap_pos tap_phase_shifter parallel df in_service \n", "114 NaN -2 False 1 1.0 True \n", "142 NaN -3 False 1 1.0 True " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import pandapower as pp\n", "from pandapower.networks import mv_oberrhein\n", "\n", "net = mv_oberrhein()\n", "net.trafo" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here you can now see both transformers with all their properties.\n", "\n", "Next, you should calculate a power flow to see which voltage prevails on the upper and lower sides of the transformer. You can do this using the pp.runpp(net) command. Immediately afterwards, display the results: Once on the upper voltage side (not very exciting, as the slack hangs here) ..." ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "114 1.0\n", "142 1.0\n", "Name: vm_hv_pu, dtype: float64" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pp.runpp(net)\n", "net.res_trafo.vm_hv_pu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "... and once on the undervoltage side:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "114 1.014598\n", "142 1.028804\n", "Name: vm_lv_pu, dtype: float64" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.res_trafo.vm_lv_pu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What you can also see on the net.trafo output: Both transformers contain a tap changer that is in the neutral position at level 0 and can be changed by 9 levels down and up.\n", "In our case, both tap changers are in the following positions:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "114 -2\n", "142 -3\n", "Name: tap_pos, dtype: int32" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.trafo['tap_pos']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The tap changer position does not change within a power flow. However, it is possible to use the tap controllers to influence the result of the powerflow on the upstream or downstream side depending on the node voltage." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Discrete Tap Control\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The discrete tap controller (DiscreteTapControl) in pandapower is first given the id of the transformer that is to be controlled. The default setting is that the voltage is checked on the undervoltage side. In addition, a deadband is passed in which the voltage at a particular node may move. In our case, we define a deadband of 0.99 and 1.01 p.u. for the first transformer in the Oberrhein grid:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [], "source": [ "import pandapower.control as control\n", "trafo_controller = control.DiscreteTapControl(net=net, tid=114, vm_lower_pu=0.99, vm_upper_pu=1.00)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As mentioned above, this controller is also automatically registered in the network:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ "
\n", "\n", "\n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", " \n", "
objectin_serviceorderlevelinitial_runrecycle
0DiscreteTapControl of trafo 114True0.00True{'trafo': True, 'gen': False, 'bus_pq': False}
\n", "
" ], "text/plain": [ " object in_service order level initial_run \\\n", "0 DiscreteTapControl of trafo 114 True 0.0 0 True \n", "\n", " recycle \n", "0 {'trafo': True, 'gen': False, 'bus_pq': False} " ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.controller" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To activate the controllers, *run_control = True* must now be set when calling pp.runpp. A look at the results on the transformer on the low-voltage side reveals the extent to which the controllers have become active:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "c:\\users\\srdm\\git\\pandapower\\pandapower\\control\\run_control.py:50: FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`\n", " level = controller.level.fillna(0).apply(asarray).values\n" ] }, { "data": { "text/plain": [ "114 0.998267\n", "142 1.028804\n", "Name: vm_lv_pu, dtype: float64" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pp.runpp(net, run_control=True)\n", "net.res_trafo.vm_lv_pu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you can see, the voltage has decreased and is now within the specified range. If we also check the position of the switch position, we can see that the position has changed from -2 to -1" ] }, { "cell_type": "code", "execution_count": 14, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "114 -1\n", "142 -3\n", "Name: tap_pos, dtype: int32" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.trafo['tap_pos']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Continuous Tap Control" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition to the discrete tap controller, there is also the option of using a continuous tap controller. The special feature here is that you do not have to specify a voltage range, but that an exact voltage to be achieved is specified (a tolerance range 'tol' only indicates when the result is accurate enough and no further control loop is required). In concrete terms, this means that it is assumed that the stages of a transformer do not have to be integer. In our example, you can use this controller for the second transformer, for example." ] }, { "cell_type": "code", "execution_count": 15, "metadata": {}, "outputs": [], "source": [ "trafo_controller = control.ContinuousTapControl(net=net, tid=142, vm_set_pu=0.98, tol=1e-6)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you now carry out a power flow with activated controllers, the voltage on the undervoltage side is exactly 0.98 p.u.:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "c:\\users\\srdm\\git\\pandapower\\pandapower\\control\\run_control.py:50: FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`\n", " level = controller.level.fillna(0).apply(asarray).values\n" ] }, { "data": { "text/plain": [ "114 0.998267\n", "142 0.980000\n", "Name: vm_lv_pu, dtype: float64" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "pp.runpp(net, run_control=True)\n", "net.res_trafo.vm_lv_pu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Furthermore, as was to be expected, the tap changer position is no longer an integer, but is around -0.07:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": {}, "outputs": [ { "data": { "text/plain": [ "114 -1.000000\n", "142 -0.067373\n", "Name: tap_pos, dtype: float64" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.trafo['tap_pos']" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Even if this result cannot occur in reality, it can still be useful, for example to avoid large jumps in results in large-scale studies.\n", "\n", "In a short digression, the effect of level and order shall be displayed. If a another continuous trafo contoller in the same level is defined setting vm_set_pu = 0.99 as a result the controllers would not converge:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "c:\\users\\srdm\\git\\pandapower\\pandapower\\control\\run_control.py:50: FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`\n", " level = controller.level.fillna(0).apply(asarray).values\n" ] }, { "ename": "ControllerNotConverged", "evalue": "Maximum number of iterations per controller is reached. Some controller did not converge after 31 calculations!", "output_type": "error", "traceback": [ "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m", "\u001b[1;31mControllerNotConverged\u001b[0m Traceback (most recent call last)", "Cell \u001b[1;32mIn[18], line 2\u001b[0m\n\u001b[0;32m 1\u001b[0m trafo_controller \u001b[38;5;241m=\u001b[39m control\u001b[38;5;241m.\u001b[39mContinuousTapControl(net\u001b[38;5;241m=\u001b[39mnet, tid\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m142\u001b[39m, vm_set_pu\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m0.99\u001b[39m, tol\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1e-6\u001b[39m)\n\u001b[1;32m----> 2\u001b[0m \u001b[43mpp\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mrunpp\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnet\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mrun_control\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mTrue\u001b[39;49;00m\u001b[43m)\u001b[49m\n", "File \u001b[1;32mc:\\users\\srdm\\git\\pandapower\\pandapower\\run.py:235\u001b[0m, in \u001b[0;36mrunpp\u001b[1;34m(net, algorithm, calculate_voltage_angles, init, max_iteration, tolerance_mva, trafo_model, trafo_loading, enforce_q_lims, check_connectivity, voltage_depend_loads, consider_line_temperature, run_control, distributed_slack, tdpf, tdpf_delay_s, **kwargs)\u001b[0m\n\u001b[0;32m 233\u001b[0m \u001b[38;5;66;03m# disable run control for inner loop to avoid infinite loop\u001b[39;00m\n\u001b[0;32m 234\u001b[0m parameters[\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mrun_control\u001b[39m\u001b[38;5;124m\"\u001b[39m] \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mFalse\u001b[39;00m\n\u001b[1;32m--> 235\u001b[0m run_control(\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mparameters)\n\u001b[0;32m 236\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m 237\u001b[0m passed_parameters \u001b[38;5;241m=\u001b[39m _passed_runpp_parameters(\u001b[38;5;28mlocals\u001b[39m())\n", "File \u001b[1;32mc:\\users\\srdm\\git\\pandapower\\pandapower\\control\\run_control.py:303\u001b[0m, in \u001b[0;36mrun_control\u001b[1;34m(net, ctrl_variables, max_iter, **kwargs)\u001b[0m\n\u001b[0;32m 300\u001b[0m ctrl_variables \u001b[38;5;241m=\u001b[39m net_initialization(net, ctrl_variables, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m 302\u001b[0m \u001b[38;5;66;03m# run each controller step in given controller order\u001b[39;00m\n\u001b[1;32m--> 303\u001b[0m control_implementation(net, controller_order, ctrl_variables, max_iter, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m 305\u001b[0m \u001b[38;5;66;03m# call finalize function of each controller\u001b[39;00m\n\u001b[0;32m 306\u001b[0m control_finalization(controller_order)\n", "File \u001b[1;32mc:\\users\\srdm\\git\\pandapower\\pandapower\\control\\run_control.py:222\u001b[0m, in \u001b[0;36mcontrol_implementation\u001b[1;34m(net, controller_order, ctrl_variables, max_iter, evaluate_net_fct, **kwargs)\u001b[0m\n\u001b[0;32m 220\u001b[0m \u001b[38;5;66;03m# raises controller not converged\u001b[39;00m\n\u001b[0;32m 221\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m ctrl_variables[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mcheck_each_level\u001b[39m\u001b[38;5;124m'\u001b[39m]:\n\u001b[1;32m--> 222\u001b[0m \u001b[43mcheck_final_convergence\u001b[49m\u001b[43m(\u001b[49m\u001b[43mrun_count\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmax_iter\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mctrl_variables\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[38;5;124;43mconverged\u001b[39;49m\u001b[38;5;124;43m'\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 223\u001b[0m \u001b[38;5;66;03m# is required if you only want to check if in the last level everything is converged\u001b[39;00m\n\u001b[0;32m 224\u001b[0m check_final_convergence(run_count, max_iter, ctrl_variables[\u001b[38;5;124m'\u001b[39m\u001b[38;5;124mconverged\u001b[39m\u001b[38;5;124m'\u001b[39m])\n", "File \u001b[1;32mc:\\users\\srdm\\git\\pandapower\\pandapower\\control\\run_control.py:138\u001b[0m, in \u001b[0;36mcheck_final_convergence\u001b[1;34m(run_count, max_iter, net_converged)\u001b[0m\n\u001b[0;32m 136\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m \u001b[38;5;21mcheck_final_convergence\u001b[39m(run_count, max_iter, net_converged):\n\u001b[0;32m 137\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m run_count \u001b[38;5;241m>\u001b[39m max_iter:\n\u001b[1;32m--> 138\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m ControllerNotConverged(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mMaximum number of iterations per controller is reached. \u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 139\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mSome controller did not converge after \u001b[39m\u001b[38;5;132;01m%i\u001b[39;00m\u001b[38;5;124m calculations!\u001b[39m\u001b[38;5;124m\"\u001b[39m\n\u001b[0;32m 140\u001b[0m \u001b[38;5;241m%\u001b[39m run_count)\n\u001b[0;32m 141\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m net_converged:\n\u001b[0;32m 142\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m NetCalculationNotConverged(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mController did not converge because the calculation did not converge!\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n", "\u001b[1;31mControllerNotConverged\u001b[0m: Maximum number of iterations per controller is reached. Some controller did not converge after 31 calculations!" ] } ], "source": [ "trafo_controller = control.ContinuousTapControl(net=net, tid=142, vm_set_pu=0.99, tol=1e-6)\n", "pp.runpp(net, run_control=True)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, if the level is increased in case of one of the two tap controller, the system would converge again, as the two controllers would not affect each other. The controller with the higher level would then be the superior controller as its set value would be the leading one." ] }, { "cell_type": "code", "execution_count": 19, "metadata": {}, "outputs": [ { "name": "stderr", "output_type": "stream", "text": [ "c:\\users\\srdm\\git\\pandapower\\pandapower\\control\\run_control.py:50: FutureWarning: Downcasting object dtype arrays on .fillna, .ffill, .bfill is deprecated and will change in a future version. Call result.infer_objects(copy=False) instead. To opt-in to the future behavior, set `pd.set_option('future.no_silent_downcasting', True)`\n", " level = controller.level.fillna(0).apply(asarray).values\n" ] }, { "data": { "text/plain": [ "114 0.998267\n", "142 0.990000\n", "Name: vm_lv_pu, dtype: float64" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "net.controller.at[2, 'level'] = 1\n", "pp.runpp(net, run_control=True)\n", "net.res_trafo.vm_lv_pu" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Lessons learned\n", "\n", "After completing this tutorial:\n", "- you will have understood the structure of the control module.\n", "- know what the central components of each controller are.\n", "- know the difference between level and order.\n", "- be able to embed a simple level controller in a grid. " ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "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.10.13" } }, "nbformat": 4, "nbformat_minor": 4 }