This tutorial introduces the pandapower controle module with the example of tap changer control. For this, we first load the MV oberrhein network that contains two 110/20 kV transformers:
# Importing necessary packages
import pandapower as pp
from pandapower.networks import mv_oberrhein
net = mv_oberrhein()
net.trafo
name | std_type | hv_bus | lv_bus | sn_mva | vn_hv_kv | vn_lv_kv | vk_percent | vkr_percent | pfe_kw | ... | tap_neutral | tap_min | tap_max | tap_step_percent | tap_step_degree | tap_pos | tap_phase_shifter | parallel | df | in_service | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
114 | HV/MV Transformer 0 | 25 MVA 110/20 kV | 58 | 39 | 25.0 | 110.0 | 20.0 | 11.2 | 0.282 | 29.0 | ... | 0 | -9 | 9 | 1.5 | NaN | -2 | False | 1 | 1.0 | True |
142 | HV/MV Transformer 1 | 25 MVA 110/20 kV | 318 | 319 | 25.0 | 110.0 | 20.0 | 11.2 | 0.282 | 29.0 | ... | 0 | -9 | 9 | 1.5 | NaN | -3 | False | 1 | 1.0 | True |
2 rows × 23 columns
If we run a power flow, we can see the voltage at the low voltage side of the transformers:
pp.runpp(net)
net.res_trafo.vm_lv_pu
114 1.014598 142 1.028804 Name: vm_lv_pu, dtype: float64
Both transformers include a tap changer with a range of -9 to +9, which are set to positions -2 and -3 respectively:
net.trafo["tap_pos"]
114 -2 142 -3 Name: tap_pos, dtype: int32
The tap position is constant within a power flow calculation. A controller can now be used to control the tap changer position depending on the bus voltage.
The DiscreteTapControl from the pandapower control package receives a deadband of permissable voltage and uses the tap changer to keep the voltage within this voltage band. We define such a controller for the first transformer in the oberrhein network with a deadband of 0.99 to 1.01pu:
import pandapower.control as control
trafo_controller = control.DiscreteTapControl(net=net, tid=114, vm_lower_pu=0.99, vm_upper_pu=1.01)
The initiated controller automatically registers in the net. It can be found in the controller table:
net.controller
object | in_service | order | level | initial_run | recycle | |
---|---|---|---|---|---|---|
0 | DiscreteTapControl of trafo 114 | True | 0.0 | 0 | True | {'trafo': True, 'gen': False, 'bus_pq': False} |
We now run a controlled power flow by setting run_control=True within the runpp arguments and check the transformer voltage:
# running a control-loop
pp.runpp(net, run_control=True)
net.res_trafo.vm_lv_pu
114 0.998267 142 1.028804 Name: vm_lv_pu, dtype: float64
The voltage at transformer 114 is now within the given range. If we checke the transformer table, we can see that the tap position of the first transformer as been changed from -2 to -1:
net.trafo["tap_pos"]
114 -1 142 -3 Name: tap_pos, dtype: int32
net.trafo.std_type
114 25 MVA 110/20 kV 142 25 MVA 110/20 kV Name: std_type, dtype: object
net.controller.drop([trafo_controller.index], inplace = True)
The tap step percent is an alternative way to Discrete Tap Control. It sets a single voltage instead of a deadband of permissable voltage and calculates the voltage limit for the upper and lower side by adding and subtracting the vm_delta_pu respectively. We define such a controller for the first transformer as an alternative to Discrete Tap Control in the oberrhein network with a voltage of 1.05 pu:
vm_delta_pu = net.trafo.at[142, "tap_step_percent"] / 100. * .5
vm_lower_pu = 1.05 - vm_delta_pu
vm_upper_pu = 1.05 + vm_delta_pu
print(vm_delta_pu,vm_lower_pu,vm_upper_pu)
0.0075 1.0425 1.0575
We got the limit of upper side voltage by adding the vm_delta_pu to the set voltage and the lower side voltage limit by substracting the vm_delta_pu from the vm_set_pu.
trafo_controller2 = control.DiscreteTapControl.from_tap_step_percent(net=net, tid=114, vm_set_pu=1.05, side="lv", tol=1e-3, in_service=True, order=0,
drop_same_existing_ctrl=False, matching_params=None)
Here we added the tap step percent to the first transformer
pp.runpp(net, run_control=True)
net.controller
object | in_service | order | level | initial_run | recycle | |
---|---|---|---|---|---|---|
0 | DiscreteTapControl of trafo 114 | True | 0.0 | 0 | True | {'trafo': True, 'gen': False, 'bus_pq': False} |
net.res_trafo.vm_lv_pu
114 1.048740 142 1.028804 Name: vm_lv_pu, dtype: float64
The voltage at transformer 114 is now changed to 1.048740 which is between the upper and lower limits
net.res_trafo
p_hv_mw | q_hv_mvar | p_lv_mw | q_lv_mvar | pl_mw | ql_mvar | i_hv_ka | i_lv_ka | vm_hv_pu | va_hv_degree | vm_lv_pu | va_lv_degree | loading_percent | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
114 | 17.245099 | 3.741077 | -17.181802 | -2.511061 | 0.063298 | 1.230016 | 0.092619 | 0.477969 | 1.0 | 0.0 | 1.048740 | -3.944032 | 70.584884 |
142 | 20.863017 | 4.653035 | -20.784893 | -2.789431 | 0.078123 | 1.863605 | 0.112193 | 0.588438 | 1.0 | 0.0 | 1.028804 | -4.943953 | 85.502393 |
net.trafo["tap_pos"]
114 -4 142 -3 Name: tap_pos, dtype: int32
The tap position of the second transformer has been changed from -1 to -4:
It is also possible to control transformer with a ContiniousTapControl strategy. Instead of a range, this type of controller is able to achieve an exact output voltage. For this it assumes tap positions as floating numbers. We define such a controller for the second transformer in the network:
trafo_controller = control.ContinuousTapControl(net=net, tid=142, vm_set_pu=0.98, tol=1e-6)
If we now run the result, the low voltage side of the second transformer is controlled to exactly 0.98 pu:
pp.runpp(net, run_control=True)
net.res_trafo.vm_lv_pu
114 1.04874 142 0.98000 Name: vm_lv_pu, dtype: float64
The tap position is set to -0.07:
net.trafo["tap_pos"]
114 -4.000000 142 -0.067373 Name: tap_pos, dtype: float64
While this obviously would not possible in real transformers, it can be useful to assume continous taps in large scale studies to avoid big steps in the results.