e2nIEE / pandapower
Showing 11 of 31 files from the diff.

@@ -1,4 +1,7 @@
Loading
1 -
__author__ = 'lthurner'
1 +
# -*- coding: utf-8 -*-
2 +
3 +
# Copyright (c) 2016-2021 by University of Kassel and Fraunhofer Institute for Energy Economics
4 +
# and Energy System Technology (IEE), Kassel. All rights reserved.
2 5
3 6
from pandapower.control.controller.trafo_control import TrafoController
4 7
@@ -42,6 +45,46 @@
Loading
42 45
        self.vm_upper_pu = vm_upper_pu
43 46
44 47
        self.tap_pos = net[self.trafotable].at[tid, "tap_pos"]
48 +
        self.vm_delta_pu = net[self.trafotable].at[tid, "tap_step_percent"] / 100. * .5 + self.tol
49 +
        self.vm_set_pu = kwargs.get("vm_set_pu")
50 +
51 +
    @classmethod
52 +
    def from_tap_step_percent(cls, net, tid, vm_set_pu, side="lv", trafotype="2W", tol=1e-3, in_service=True, order=0,
53 +
                              drop_same_existing_ctrl=False, matching_params=None, **kwargs):
54 +
        """
55 +
        Alternative mode of the controller, which uses a set point for voltage and the value of net.trafo.tap_step_percent to calculate
56 +
        vm_upper_pu and vm_lower_pu. To this end, the parameter vm_set_pu should be provided, instead of vm_lower_pu and vm_upper_pu.
57 +
        To use this mode of the controller, the controller can be initialized as following:
58 +
59 +
        >>> c = DiscreteTapControl.from_tap_step_percent(net, tid, vm_set_pu)
60 +
61 +
        INPUT:
62 +
            **net** (attrdict) - Pandapower struct
63 +
64 +
            **tid** (int) - ID of the trafo that is controlled
65 +
66 +
            **vm_set_pu** (float) - Voltage setpoint in pu
67 +
        """
68 +
        self = cls(net, tid=tid, vm_lower_pu=None, vm_upper_pu=None, side=side, trafotype=trafotype, tol=tol,
69 +
                   in_service=in_service, order=order, drop_same_existing_ctrl=drop_same_existing_ctrl,
70 +
                   matching_params=matching_params, vm_set_pu=vm_set_pu, **kwargs)
71 +
        return self
72 +
73 +
    @property
74 +
    def vm_set_pu(self):
75 +
        return self._vm_set_pu
76 +
77 +
    @vm_set_pu.setter
78 +
    def vm_set_pu(self, value):
79 +
        self._vm_set_pu = value
80 +
        if value is None:
81 +
            return
82 +
        self.vm_lower_pu = value - self.vm_delta_pu
83 +
        self.vm_upper_pu = value + self.vm_delta_pu
84 +
85 +
    def initialize_control(self, net):
86 +
        if hasattr(self, 'vm_set_pu') and self.vm_set_pu is not None:
87 +
            self.vm_delta_pu = net[self.trafotable].at[self.tid, "tap_step_percent"] / 100. * .5 + self.tol
45 88
46 89
    def control_step(self, net):
47 90
        """

@@ -14,6 +14,11 @@
Loading
14 14
from pandas import Int64Index
15 15
16 16
from pandapower.toolbox import ensure_iterability
17 +
try:
18 +
    import matplotlib.pyplot as plt
19 +
    MATPLOTLIB_INSTALLED = True
20 +
except ImportError:
21 +
    MATPLOTLIB_INSTALLED = False
17 22
18 23
try:
19 24
    import pplog
@@ -127,7 +132,7 @@
Loading
127 132
        # query of parameters in net.controller dataframe
128 133
        idx = Int64Index(idx)
129 134
        for df_key in df_keys:
130 -
            idx &= net.controller.index[net.controller[df_key] == parameters[df_key]]
135 +
            idx.intersection(net.controller.index[net.controller[df_key] == parameters[df_key]])
131 136
        # query of parameters in controller object attributes
132 137
        idx = [i for i in idx if _controller_attributes_query(
133 138
            net.controller.object.loc[i], attributes_dict)]
@@ -194,3 +199,12 @@
Loading
194 199
        logger.info("Creating controller " + index + " of type %s, " % this_ctrl_type +
195 200
                    "no matching parameters are given to check which " +
196 201
                    "same type controllers should be dropped.")
202 +
203 +
204 +
def plot_characteristic(characteristic, start, stop, num=20):
205 +
    x = np.linspace(start, stop, num)
206 +
    y = characteristic(x)
207 +
    if MATPLOTLIB_INSTALLED:
208 +
        plt.plot(x, y, marker='x')
209 +
    else:
210 +
        logger.info("matplotlib not installed. y-values: %s" % y)

@@ -1795,7 +1795,7 @@
Loading
1795 1795
1796 1796
        **cols_to_keep** (list, None) - list of column names which should be kept while replacing
1797 1797
        ext_grids. If None these columns are kept if values exist: "max_p_mw", "min_p_mw",
1798 -
        "max_q_mvar", "min_q_mvar". However cols_to_keep is given, these columns are alway set:
1798 +
        "max_q_mvar", "min_q_mvar". However cols_to_keep is given, these columns are always set:
1799 1799
        "bus", "vm_pu", "p_mw", "name", "in_service", "controllable"
1800 1800
1801 1801
        **add_cols_to_keep** (list, None) - list of column names which should be added to
@@ -1856,13 +1856,14 @@
Loading
1856 1856
1857 1857
    # --- result data
1858 1858
    if net.res_ext_grid.shape[0]:
1859 -
        to_add = net.res_ext_grid.loc[ext_grids]
1860 -
        to_add.index = new_idx
1859 +
        in_res = pd.Series(ext_grids).isin(net["res_ext_grid"].index).values
1860 +
        to_add = net.res_ext_grid.loc[pd.Index(ext_grids)[in_res]]
1861 +
        to_add.index = pd.Index(new_idx)[in_res]
1861 1862
        if version.parse(pd.__version__) < version.parse("0.23"):
1862 1863
            net.res_gen = pd.concat([net.res_gen, to_add])
1863 1864
        else:
1864 1865
            net.res_gen = pd.concat([net.res_gen, to_add], sort=True)
1865 -
        net.res_ext_grid.drop(ext_grids, inplace=True)
1866 +
        net.res_ext_grid.drop(pd.Index(ext_grids)[in_res], inplace=True)
1866 1867
    return new_idx
1867 1868
1868 1869
@@ -1938,13 +1939,14 @@
Loading
1938 1939
1939 1940
    # --- result data
1940 1941
    if net.res_gen.shape[0]:
1941 -
        to_add = net.res_gen.loc[gens]
1942 -
        to_add.index = new_idx
1942 +
        in_res = pd.Series(gens).isin(net["res_gen"].index).values
1943 +
        to_add = net.res_gen.loc[pd.Index(gens)[in_res]]
1944 +
        to_add.index = pd.Index(new_idx)[in_res]
1943 1945
        if version.parse(pd.__version__) < version.parse("0.23"):
1944 1946
            net.res_ext_grid = pd.concat([net.res_ext_grid, to_add])
1945 1947
        else:
1946 1948
            net.res_ext_grid = pd.concat([net.res_ext_grid, to_add], sort=True)
1947 -
        net.res_gen.drop(gens, inplace=True)
1949 +
        net.res_gen.drop(pd.Index(gens)[in_res], inplace=True)
1948 1950
    return new_idx
1949 1951
1950 1952
@@ -2022,13 +2024,14 @@
Loading
2022 2024
2023 2025
    # --- result data
2024 2026
    if net.res_gen.shape[0]:
2025 -
        to_add = net.res_gen.loc[gens]
2026 -
        to_add.index = new_idx
2027 +
        in_res = pd.Series(gens).isin(net["res_gen"].index).values
2028 +
        to_add = net.res_gen.loc[pd.Index(gens)[in_res]]
2029 +
        to_add.index = pd.Index(new_idx)[in_res]
2027 2030
        if version.parse(pd.__version__) < version.parse("0.23"):
2028 2031
            net.res_sgen = pd.concat([net.res_sgen, to_add])
2029 2032
        else:
2030 2033
            net.res_sgen = pd.concat([net.res_sgen, to_add], sort=True)
2031 -
        net.res_gen.drop(gens, inplace=True)
2034 +
        net.res_gen.drop(pd.Index(gens)[in_res], inplace=True)
2032 2035
    return new_idx
2033 2036
2034 2037
@@ -2122,13 +2125,14 @@
Loading
2122 2125
2123 2126
    # --- result data
2124 2127
    if net.res_sgen.shape[0]:
2125 -
        to_add = net.res_sgen.loc[sgens]
2126 -
        to_add.index = new_idx
2128 +
        in_res = pd.Series(sgens).isin(net["res_sgen"].index).values
2129 +
        to_add = net.res_sgen.loc[pd.Index(sgens)[in_res]]
2130 +
        to_add.index = pd.Index(new_idx)[in_res]
2127 2131
        if version.parse(pd.__version__) < version.parse("0.23"):
2128 2132
            net.res_gen = pd.concat([net.res_gen, to_add])
2129 2133
        else:
2130 2134
            net.res_gen = pd.concat([net.res_gen, to_add], sort=True)
2131 -
        net.res_sgen.drop(sgens, inplace=True)
2135 +
        net.res_sgen.drop(pd.Index(sgens)[in_res], inplace=True)
2132 2136
    return new_idx
2133 2137
2134 2138
@@ -2235,13 +2239,14 @@
Loading
2235 2239
2236 2240
    # --- result data
2237 2241
    if net["res_" + old_elm].shape[0]:
2238 -
        to_add = net["res_" + old_elm].loc[old_indices]
2239 -
        to_add.index = new_idx
2242 +
        in_res = pd.Series(old_indices).isin(net["res_" + old_elm].index).values
2243 +
        to_add = net["res_" + old_elm].loc[pd.Index(old_indices)[in_res]]
2244 +
        to_add.index = pd.Index(new_idx)[in_res]
2240 2245
        if version.parse(pd.__version__) < version.parse("0.23"):
2241 2246
            net["res_" + new_elm] = pd.concat([net["res_" + new_elm], to_add])
2242 2247
        else:
2243 2248
            net["res_" + new_elm] = pd.concat([net["res_" + new_elm], to_add], sort=True)
2244 -
        net["res_" + old_elm].drop(old_indices, inplace=True)
2249 +
        net["res_" + old_elm].drop(pd.Index(old_indices)[in_res], inplace=True)
2245 2250
    return new_idx
2246 2251
2247 2252

@@ -0,0 +1,94 @@
Loading
1 +
2 +
3 +
from pandapower.control import ContinuousTapControl
4 +
from pandapower.control import DiscreteTapControl
5 +
from pandapower.control.basic_controller import Controller
6 +
7 +
__author__ = 'jdollichon'
8 +
9 +
from pandapower.control.controller.trafo_control import TrafoController
10 +
from control.util.characteristic import Characteristic
11 +
12 +
try:
13 +
    import pplog
14 +
except:
15 +
    import logging as pplog
16 +
17 +
logger = pplog.getLogger(__name__)
18 +
19 +
20 +
class UsetOfP(Controller):
21 +
    """
22 +
    Trafo Controller which changes its voltage setpoint in respect to the powerflow at the
23 +
    transformator. It can be used as a con
24 +
25 +
    INPUT:
26 +
       **net** (attrdict) - Pandapower struct
27 +
28 +
       **tid** (int) - ID of the trafo that is controlled
29 +
30 +
    OPTIONAL:
31 +
32 +
       **continuous** (boolean, True) - Switch for using continuous or discrete controlling
33 +
34 +
        **discrete_band** (float, None) - Only used when continuous is False. Voltage limits being
35 +
        used around the set-point. E.g. 0.05 would result in an upper limit of set_point + 0.05
36 +
        and a lower limit of set_point - 0.05.
37 +
38 +
       **side** (string) - Side of the transformer where the voltage is controlled ("hv" or "lv")
39 +
40 +
       **tol** (float, 1e-3) - Voltage tolerance band at bus in Percent
41 +
42 +
        **characteristic** (Characteristic, [10000, 20000]) - Expects a characteristic curve as an
43 +
            instance of control.util.characteristic (also accepts an epsilon for tolerance)
44 +
45 +
        **in_service** (bool, True) - Indicates if the controller is currently in_service
46 +
47 +
    """
48 +
49 +
    def __init__(self, net, tid, continuous=True, discrete_band=None, side="lv", tol=1e-3,
50 +
                 characteristic=Characteristic([10, 20], [0.95, 1.05]), in_service=True, **kwargs):
51 +
        super(UsetOfP, self).__init__(net, in_service=in_service, **kwargs)
52 +
53 +
        self.controlled_bus = net.trafo.at[tid, side+"_bus"]
54 +
55 +
        self.hv_bus = net.trafo.at[tid, "hv_bus"]
56 +
        self.lv_bus = net.trafo.at[tid, "lv_bus"]
57 +
58 +
        # characteristic curve
59 +
        self.cc = characteristic
60 +
        self.u_target = None
61 +
        self.diff = None
62 +
63 +
        self.continuous = continuous
64 +
65 +
        if continuous:
66 +
            if len(net.ext_grid.query("bus==%u" % self.hv_bus).index.values) != 1:
67 +
                raise NotImplementedError("Continuous control is only available for \
68 +
                    transformers connected to an external grid.")
69 +
70 +
            self.t_nom = net.trafo.at[tid, "vn_lv_kv"]/net.trafo.at[tid, "vn_hv_kv"] * \
71 +
                net.bus.at[self.hv_bus, "vn_kv"] / net.bus.at[self.lv_bus, "vn_kv"]
72 +
73 +
            self.ctrl = ContinuousTapControl(net, tid, 1.0, tol=tol, side=side,
74 +
                                             trafotype="2W", in_service=in_service,
75 +
                                             check_tap_bounds=True)
76 +
        else:
77 +
            self.discrete_band = discrete_band
78 +
            self.ctrl = DiscreteTapControl(net, tid, -discrete_band + 1, discrete_band + 1, side=side,
79 +
                                           trafotype="2W", tol=tol, in_service=in_service)
80 +
81 +
    def control_step(self, net):
82 +
        # empty control step: control is executd by underlying trafo controller
83 +
        pass
84 +
85 +
    def is_converged(self, net):
86 +
        self.setpoint = self.cc(net.res_bus.at[self.controlled_bus, "p_mw"])
87 +
88 +
        if self.continuous:
89 +
            self.ctrl.vm_set_pu = self.setpoint
90 +
        else:
91 +
            self.ctrl.vm_lower_pu = -self.discrete_band + self.setpoint
92 +
            self.ctrl.vm_upper_pu = self.discrete_band + self.setpoint
93 +
94 +
        return self.ctrl.is_converged(net)

@@ -19,19 +19,24 @@
Loading
19 19
20 20
def from_mpc(mpc_file, f_hz=50, casename_mpc_file='mpc', validate_conversion=False):
21 21
    """
22 -
    This function converts a matpower case file (.mat) version 2 to a pandapower net.
22 +
    This function converts a matpower case file version 2 to a pandapower net.
23 +
24 +
    Note: The input is a .mat file not an .m script. You need to save the mpc dict variable as .mat
25 +
    file. If the saved variable of the matlab workspace is not named 'mpc', you can adapt the value
26 +
    of 'casename_mpc_file' as needed.
23 27
24 28
    Note: python is 0-based while Matlab is 1-based.
25 29
26 30
    INPUT:
27 31
28 -
        **mpc_file** - path to a matpower case file (.mat).
32 +
        **mpc_file** - path to a matpower case file (.mat format not .m script).
29 33
30 34
    OPTIONAL:
31 35
32 36
        **f_hz** (int, 50) - The frequency of the network.
33 37
34 -
        **casename_mpc_file** (str, 'mpc') - The name of the variable in .mat file which contain the matpower case structure, i.e. the arrays "gen", "branch" and "bus".
38 +
        **casename_mpc_file** (str, 'mpc') - The name of the variable in .mat file which contain
39 +
        the matpower case structure, i.e. the arrays "gen", "branch" and "bus".
35 40
36 41
    OUTPUT:
37 42

@@ -19,6 +19,10 @@
Loading
19 19
    """
20 20
    Class representing a generic time series controller for a specified element and variable
21 21
    Control strategy: "No Control" -> just updates timeseries
22 +
    It is possible to set attributes of objects that are contained in a net table, e.g. attributes of other controllers. This can be helpful
23 +
    e.g. if a voltage setpoint of a transformer tap changer depends on the time step.
24 +
    An attribute of an object in the "object" column of a table (e.g. net.controller["object"] -> net.controller.object.at[0, "vm_set_pu"]
25 +
    can be set if the attribute is specified as "object.attribute" (e.g. "object.vm_set_pu").
22 26
23 27
    INPUT:
24 28
@@ -53,7 +57,7 @@
Loading
53 57
    """
54 58
55 59
    def __init__(self, net, element, variable, element_index, profile_name=None, data_source=None,
56 -
                 scale_factor=1.0, in_service=True, recycle=True, order=0, level=0,
60 +
                 scale_factor=1.0, in_service=True, recycle=True, order=-1, level=-1,
57 61
                 drop_same_existing_ctrl=False, matching_params=None,
58 62
                 initial_run=False, **kwargs):
59 63
        # just calling init of the parent
@@ -76,8 +80,13 @@
Loading
76 80
        self.profile_name = profile_name
77 81
        self.scale_factor = scale_factor
78 82
        self.applied = False
83 +
        self.object_attribute = None
79 84
        # write functions faster, depending on type of self.element_index
80 -
        if isinstance(self.element_index, int):
85 +
        if self.variable.startswith('object'):
86 +
            # write to object attribute
87 +
            self.write = "object"
88 +
            self.object_attribute = self.variable.split(".")[1]
89 +
        elif isinstance(self.element_index, int):
81 90
            # use .at if element_index is integer for speedup
82 91
            self.write = "single_index"
83 92
        # commenting this out for now, see issue 609
@@ -123,9 +132,11 @@
Loading
123 132
            self._write_to_all_index(net)
124 133
        elif self.write == "loc":
125 134
            self._write_with_loc(net)
135 +
        elif self.write == "object":
136 +
            self._write_to_object_attribute(net)
126 137
        else:
127 138
            raise NotImplementedError("ConstControl: self.write must be one of "
128 -
                                      "['single_index', 'all_index', 'loc']")
139 +
                                      "['single_index', 'all_index', 'loc', 'object']")
129 140
130 141
    def time_step(self, net, time):
131 142
        """
@@ -167,3 +178,14 @@
Loading
167 178
168 179
    def _write_with_loc(self, net):
169 180
        net[self.element].loc[self.element_index, self.variable] = self.values
181 +
182 +
    def _write_to_object_attribute(self, net):
183 +
        if hasattr(self.element_index, '__iter__') and len(self.element_index) > 1:
184 +
            for idx, val in zip(self.element_index, self.values):
185 +
                setattr(net[self.element]["object"].at[idx], self.object_attribute, val)
186 +
        else:
187 +
            setattr(net[self.element]["object"].at[self.element_index], self.object_attribute, self.values)
188 +
189 +
    def __str__(self):
190 +
        return super().__str__() + " [%s.%s]" % (self.element, self.variable)
191 +

@@ -0,0 +1,117 @@
Loading
1 +
# -*- coding: utf-8 -*-
2 +
3 +
# Copyright (c) 2016-2021 by University of Kassel and Fraunhofer Institute for Energy Economics
4 +
# and Energy System Technology (IEE), Kassel. All rights reserved.
5 +
6 +
from builtins import zip
7 +
from builtins import object
8 +
9 +
from numpy import interp
10 +
from pandapower.io_utils import JSONSerializableClass
11 +
12 +
13 +
class Characteristic(JSONSerializableClass):
14 +
    """
15 +
    This class represents a characteristics curve. The curve is described as a
16 +
    piecewise linear function.
17 +
18 +
    |   **pts** - Expects two (or more) points of the function (i.e. kneepoints)
19 +
    |   **eps** - Optional: An epsilon to compare the difference to.
20 +
21 +
    The class has an implementation of the __call__ method, which allows using it interchangeably with other interpolator objects,
22 +
    e.g. scipy.interpolate.interp1d, scipy.interpolate.CubicSpline, scipy.interpolate.PPoly, etc.
23 +
24 +
    Example usage:
25 +
26 +
    Create a simple function from two points and ask for the target y-value for a
27 +
    given x-value.
28 +
    Assume a characteristics curve in which for voltages < 0.95pu a power of 10kW
29 +
    is desired, linear rising to a max. of 20kW at 1.05pu
30 +
31 +
    # You can give points by lists of x/y-values
32 +
    >>> c = Characteristic(x_values=[0.95, 1.05], y_values=[10, 20])
33 +
    >>> c.target(x=1.0)
34 +
    15.0
35 +
36 +
    # or pass a list of points (x,y)
37 +
    >>> c = Characteristic.from_points(points=[(0.95, 10), (1.05, 20)])
38 +
    >>> c.target(x=1.0)
39 +
    15.0
40 +
41 +
    # or in a simple case from a gradient, its zero crossing and the maximal values for y
42 +
    >>> c = Characteristic.from_gradient(zero_crossing=-85, gradient=100, y_min=10, y_max=20)
43 +
    >>> c.target(x=1.0)
44 +
    15.0
45 +
46 +
    # Values are constant beyond the first and last defined points
47 +
    >>> c.target(x=42)
48 +
    20.0
49 +
    >>> c.target(x=-42)
50 +
    10.0
51 +
52 +
    # Create a curve with many points and ask for the difference between the y-value being measured
53 +
    and the expected y-value for a given x-value
54 +
    >>> c = Characteristic.from_points(points=[(1,2),(2,4),(3,2),(42,24)])
55 +
    >>> c.diff(x=2.5, measured=3)
56 +
    0.0
57 +
58 +
    # You can also ask if a y-values satisfies the curve at a certain x-value. Note how the use of
59 +
    an epsilon behaves (for x=2.5 we expect 3.0):
60 +
    >>> c.satisfies(x=2.5, measured=3.099999999, epsilon=0.1)
61 +
    True
62 +
    >>> c.satisfies(x=2.5, measured=3.1, epsilon=0.1)
63 +
    False
64 +
    """
65 +
66 +
    def __init__(self, x_values, y_values):
67 +
        super().__init__()
68 +
        self.x_vals = x_values
69 +
        self.y_vals = y_values
70 +
71 +
    @classmethod
72 +
    def from_points(cls, points):
73 +
        unzipped = list(zip(*points))
74 +
        return cls(unzipped[0], unzipped[1])
75 +
76 +
    @classmethod
77 +
    def from_gradient(cls, zero_crossing, gradient, y_min, y_max):
78 +
        x_left = (y_min - zero_crossing) / float(gradient)
79 +
        x_right = (y_max - zero_crossing) / float(gradient)
80 +
        return cls([x_left, x_right], [y_min, y_max])
81 +
82 +
    def diff(self, x, measured):
83 +
        """
84 +
        :param x: The x-value at which the current y-value is measured
85 +
        :param actual: The actual y-value being measured.
86 +
        :return: The difference between actual and expected value.
87 +
        """
88 +
        return measured - interp(x, self.x_vals, self.y_vals)
89 +
90 +
    def target(self, x):
91 +
        """
92 +
        Note: Deprecated. Use the __call__ interface instead.
93 +
        :param x: An x-value
94 +
        :return: The corresponding target value of this characteristics
95 +
        """
96 +
        # return interp(x, self.x_vals, self.y_vals)
97 +
        raise DeprecationWarning("target method is deprecated. Use the __call__ interface instead.")
98 +
99 +
    def satisfies(self, x, measured, epsilon):
100 +
        """
101 +
102 +
        :param x: The x-value at which the current y-value is measured
103 +
        :param measured: The actual y-value being measured.
104 +
        :return: Whether or not the point satisfies the characteristics curve with respect to the
105 +
        epsilon being set
106 +
        """
107 +
        if abs(self.diff(x, measured)) < epsilon:
108 +
            return True
109 +
        else:
110 +
            return False
111 +
112 +
    def __call__(self, x):
113 +
        """
114 +
        :param x: An x-value
115 +
        :return: The corresponding target value of this characteristics
116 +
        """
117 +
        return interp(x, self.x_vals, self.y_vals)

@@ -0,0 +1,205 @@
Loading
1 +
# -*- coding: utf-8 -*-
2 +
3 +
# Copyright (c) 2016-2021 by University of Kassel and Fraunhofer Institute for Energy Economics
4 +
# and Energy System Technology (IEE), Kassel. All rights reserved.
5 +
6 +
import numpy as np
7 +
from pandapower.control.basic_controller import Controller
8 +
9 +
10 +
class CharacteristicControl(Controller):
11 +
    """
12 +
    Controller that adjusts a certain parameter of an element in pandapower net based on a specified input parameter in pandapower net,
13 +
    according to a provided characteristic.
14 +
    Example: change the tap position of the transformers (net.trafo.tap_pos) based on transformer loading (net.res_trafo.loading_percent)
15 +
    according to a specified linear relationship. To this end, the input element is "res_trafo", the input variable is "loading_percent",
16 +
    the output element is "trafo" and the output variable is "tap_pos". The relationship between the values of the input and output
17 +
    variables is specified using the Characteristic class (or a scipy interpolator, e.g. scipy.interpolate.interp1d).
18 +
19 +
    INPUT:
20 +
        **net** (attrdict) - Pandapower net
21 +
22 +
        **output_element** (str) - name of the element table in pandapower net where the values are adjusted by the controller
23 +
24 +
        **output_variable** (str) - variable in the output element table, values of which are adjusted by the controller. Can also be an
25 +
                                        attribute of an object (e.g. parameter of a controller object), for this case it must start with
26 +
                                        "object." (e.g. "object.vm_set_pu")
27 +
28 +
        **output_element_index** (int or list or numpy array) - index of the elements, values fro which are adjusted
29 +
30 +
        **input_element** (str) - name of the element table or the element result table in pandapower net that provides input values for
31 +
                                    the controller
32 +
33 +
        **input_variable** (str) - name of the input variable in the input element table. Can also be an attribute of an object,
34 +
                                    similarly to output_variable
35 +
36 +
        **input_element_index** (int or list or numpy array) - index of elements in the input element table
37 +
38 +
        **characteristic** (object of class Characteristic, or a scipy interpolator object) - characteristic curve that describes the
39 +
                                                                                                relationship between the input and
40 +
                                                                                                output values
41 +
42 +
        **tol** (float) - tolerance for convergence
43 +
44 +
    OPTIONAL:
45 +
46 +
        **in_service** (bool, True) - Indicates if the controller is currently in_service
47 +
48 +
        **drop_same_existing_ctrl** (bool, False) - Indicates if already existing controllers of the same type and with the same matching parameters (e.g. at same element) should be dropped
49 +
    """
50 +
    def __init__(self, net, output_element, output_variable, output_element_index, input_element, input_variable, input_element_index,
51 +
                 characteristic, tol=1e-3, in_service=True, order=0, level=0, drop_same_existing_ctrl=False, matching_params=None,
52 +
                 **kwargs):
53 +
        if matching_params is None:
54 +
            matching_params = {"element": output_element, "input_variable": input_variable, "output_variable": output_variable,
55 +
                               "element_index": input_element_index}
56 +
        super().__init__(net, in_service=in_service, order=order, level=level, drop_same_existing_ctrl=drop_same_existing_ctrl,
57 +
                         matching_params=matching_params, **kwargs)
58 +
        self.input_element = input_element
59 +
        self.input_variable = input_variable
60 +
        self.input_element_index = input_element_index
61 +
        self.output_element = output_element
62 +
        self.output_variable = output_variable
63 +
        self.output_element_index = output_element_index
64 +
        self.characteristic = characteristic
65 +
        self.tol = tol
66 +
        self.applied = False
67 +
        self.values = None
68 +
        self.input_object_attribute = None
69 +
        self.output_object_attribute = None
70 +
        # write functions faster, depending on type of self.output_element_index
71 +
        if self.output_variable.startswith('object'):
72 +
            # write to object attribute
73 +
            self.write = "object"
74 +
            self.output_object_attribute = self.output_variable.split(".")[1]
75 +
        elif isinstance(self.output_element_index, int):
76 +
            # use .at if element_index is integer for speedup
77 +
            self.write = "single_index"
78 +
        else:
79 +
            # use common .loc
80 +
            self.write = "loc"
81 +
82 +
        # read functions faster, depending on type of self.input_element_index
83 +
        if self.input_variable.startswith('object'):
84 +
            # write to object attribute
85 +
            self.read = "object"
86 +
            self.input_object_attribute = self.input_variable.split(".")[1]
87 +
        elif isinstance(self.input_element_index, int):
88 +
            # use .at if element_index is integer for speedup
89 +
            self.read = "single_index"
90 +
        else:
91 +
            # use common .loc
92 +
            self.read = "loc"
93 +
94 +
    def initialize_control(self, net):
95 +
        """
96 +
        At the beginning of each run_control call reset applied-flag
97 +
        """
98 +
        self.values = None
99 +
        self.applied = False
100 +
101 +
    def is_converged(self, net):
102 +
        """
103 +
        Actual implementation of the convergence criteria: If controller is applied, it can stop
104 +
        """
105 +
        # read input values
106 +
        input_values = self.read_from_net(net)
107 +
        # calculate set values
108 +
        self.values = self.characteristic(input_values)
109 +
        # read previous set values
110 +
        output_values = self.read_from_net(net, output=True)
111 +
        # compare old and new set values
112 +
        diff = self.values - output_values
113 +
        # write new set values
114 +
        self.write_to_net(net)
115 +
        return self.applied and np.all(np.abs(diff) < self.tol)
116 +
117 +
    def control_step(self, net):
118 +
        """
119 +
        Set applied to true to make sure it runs at least once
120 +
        """
121 +
        self.applied = True
122 +
123 +
    def read_from_net(self, net, output=False):
124 +
        """
125 +
        Writes to self.element at index self.element_index in the column self.variable the data
126 +
        from self.values
127 +
        """
128 +
        # write functions faster, depending on type of self.element_index
129 +
        if output:
130 +
            element = self.output_element
131 +
            variable = self.output_variable
132 +
            object_attribute = self.output_object_attribute
133 +
            index = self.output_element_index
134 +
            flag = self.write
135 +
        else:
136 +
            element = self.input_element
137 +
            variable = self.input_variable
138 +
            object_attribute = self.input_object_attribute
139 +
            index = self.input_element_index
140 +
            flag = self.read
141 +
142 +
        if flag == "single_index":
143 +
            return self._read_from_single_index(net, element, variable, index)
144 +
        elif flag == "loc":
145 +
            return self._read_with_loc(net, element, variable, index)
146 +
        elif flag == "object":
147 +
            return self._read_from_object_attribute(net, element, object_attribute, index)
148 +
        else:
149 +
            raise NotImplementedError("CharacteristicControl: self.read must be one of "
150 +
                                      "['single_index', 'all_index', 'loc']")
151 +
152 +
    def write_to_net(self, net):
153 +
        """
154 +
        Writes to self.element at index self.element_index in the column self.variable the data
155 +
        from self.values
156 +
        """
157 +
        # write functions faster, depending on type of self.element_index
158 +
        if self.write == "single_index":
159 +
            self._write_to_single_index(net)
160 +
        elif self.write == "all_index":
161 +
            self._write_to_all_index(net)
162 +
        elif self.write == "loc":
163 +
            self._write_with_loc(net)
164 +
        elif self.write == "object":
165 +
            self._write_to_object_attribute(net)
166 +
        else:
167 +
            raise NotImplementedError("CharacteristicControl: self.write must be one of "
168 +
                                      "['single_index', 'all_index', 'loc']")
169 +
170 +
    def _write_to_single_index(self, net):
171 +
        net[self.output_element].at[self.output_element_index, self.output_variable] = self.values
172 +
173 +
    def _write_to_all_index(self, net):
174 +
        net[self.output_element].loc[:, self.output_variable] = self.values
175 +
176 +
    def _write_with_loc(self, net):
177 +
        net[self.output_element].loc[self.output_element_index, self.output_variable] = self.values
178 +
179 +
    def _write_to_object_attribute(self, net):
180 +
        if hasattr(self.output_element_index, '__iter__') and len(self.output_element_index) > 1:
181 +
            for idx, val in zip(self.output_element_index, self.values):
182 +
                setattr(net[self.output_element]["object"].at[idx], self.output_object_attribute, val)
183 +
        else:
184 +
            setattr(net[self.output_element]["object"].at[self.output_element_index], self.output_object_attribute, self.values)
185 +
186 +
    def _read_from_single_index(self, net, element, variable, index):
187 +
        return net[element].at[index, variable]
188 +
189 +
    def _read_from_all_index(self, net, element, variable):
190 +
        return net[element].loc[:, variable]
191 +
192 +
    def _read_with_loc(self, net, element, variable, index):
193 +
        return net[element].loc[index, variable]
194 +
195 +
    def _read_from_object_attribute(self, net, element, object_attribute, index):
196 +
        if hasattr(index, '__iter__') and len(index) > 1:
197 +
            values = np.array(shape=index.shape)
198 +
            for i, idx in enumerate(index):
199 +
                values[i] = setattr(net[element]["object"].at[idx], object_attribute)
200 +
        else:
201 +
            values = getattr(net[element]["object"].at[index], object_attribute)
202 +
        return values
203 +
204 +
    def __str__(self):
205 +
        return super().__str__() + " [%s.%s.%s.%s]" % (self.input_element, self.input_variable, self.output_element, self.output_variable)

@@ -2,13 +2,16 @@
Loading
2 2
import pandapower.control.controller
3 3
# --- Controller ---
4 4
from pandapower.control.controller.const_control import ConstControl
5 +
from pandapower.control.controller.characteristic_control import CharacteristicControl
5 6
from pandapower.control.controller.trafo.ContinuousTapControl import ContinuousTapControl
6 7
from pandapower.control.controller.trafo.DiscreteTapControl import DiscreteTapControl
8 +
from pandapower.control.controller.trafo.USetTapControl import USetTapControl
7 9
from pandapower.control.controller.trafo_control import TrafoController
8 10
9 11
# --- Other ---
10 12
from pandapower.control.run_control import *
11 13
from pandapower.control.run_control import ControllerNotConverged
14 +
from pandapower.control.util.characteristic import Characteristic
12 15
from pandapower.control.util.auxiliary import get_controller_index
13 16
from pandapower.control.util.diagnostic import control_diagnostic
14 17

@@ -1,9 +1,9 @@
Loading
1 -
from __future__ import division
1 +
# -*- coding: utf-8 -*-
2 2
3 -
__author__ = 'lthurner'
3 +
# Copyright (c) 2016-2021 by University of Kassel and Fraunhofer Institute for Energy Economics
4 +
# and Energy System Technology (IEE), Kassel. All rights reserved.
4 5
5 6
import numpy as np
6 -
7 7
from pandapower.control.controller.trafo_control import TrafoController
8 8
9 9

@@ -0,0 +1,36 @@
Loading
1 +
# -*- coding: utf-8 -*-
2 +
3 +
# Copyright (c) 2016-2021 by University of Kassel and Fraunhofer Institute for Energy Economics
4 +
# and Energy System Technology (IEE), Kassel. All rights reserved.
5 +
6 +
from pandapower.control.controller.characteristic_control import CharacteristicControl
7 +
from pandapower.control.util.characteristic import Characteristic
8 +
9 +
class USetTapControl(CharacteristicControl):
10 +
    """
11 +
    Controller that adjusts the setpoint of a local tap changer voltage control based on a load flow result (e.g. p_lv_mw, i_lv_ka etc.)
12 +
    according to a defined characteristic.
13 +
14 +
    INPUT:
15 +
        **net** (attrdict) - Pandapower net
16 +
17 +
        **cid** (int) - ID of the tap changer controller, an attribute of which is controlled
18 +
19 +
        **variable** (float) - Variable from the result table that is used for the characteristic
20 +
21 +
    OPTIONAL:
22 +
23 +
        **in_service** (bool, True) - Indicates if the controller is currently in_service
24 +
25 +
        **drop_same_existing_ctrl** (bool, False) - Indicates if already existing controllers of the same type and with the same matching parameters (e.g. at same element) should be dropped
26 +
    """
27 +
28 +
    def __init__(self, net, cid, variable='p_hv_mw', characteristic=Characteristic([10, 20], [0.95, 1.05]), tol=1e-3, in_service=True,
29 +
                 order=0, level=0, drop_same_existing_ctrl=False, matching_params=None, **kwargs):
30 +
        if matching_params is None:
31 +
            matching_params = {"cid": cid, 'variable': variable}
32 +
        c = net.controller.at[cid, 'object']
33 +
        super().__init__(net, output_element="controller", output_variable="object.vm_set_pu", output_element_index=cid,
34 +
                         input_element="res_" + c.trafotable, input_variable=variable, input_element_index=c.tid,
35 +
                         characteristic=characteristic, tol=tol, in_service=in_service, order=order,
36 +
                         level=level, drop_same_existing_ctrl=drop_same_existing_ctrl, matching_params=matching_params, **kwargs)
Files Coverage
pandapower 87.60%
setup.py 0.00%
Project Totals (168 files) 87.52%

No yaml found.

Create your codecov.yml to customize your Codecov experience

Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading