Ejemplo n.º 1
0
def _extract_result_ppci_to_pp(net, ppc, ppci):
    # convert to pandapower indices
    ppc = _copy_results_ppci_to_ppc(ppci, ppc, mode="se")

    # inits empty result tables
    init_results(net, mode="se")

    # writes res_bus.vm_pu / va_degree and branch res
    _extract_results_se(net, ppc)

    # additionally, write bus power demand results (these are not written in _extract_results)
    mapping_table = net["_pd2ppc_lookups"]["bus"]
    net.res_bus_est.index = net.bus.index
    net.res_bus_est.p_mw = get_values(ppc["bus"][:, 2], net.bus.index.values,
                                      mapping_table)
    net.res_bus_est.q_mvar = get_values(ppc["bus"][:, 3], net.bus.index.values,
                                        mapping_table)
    return net
Ejemplo n.º 2
0
def _calc_branch_values_from_trafo_df(net, ppc, trafo_df=None):
    """
    Calculates the MAT/PYPOWER-branch-attributes from the pandapower trafo dataframe.

    PYPOWER and MATPOWER uses the PI-model to model transformers.
    This function calculates the resistance r, reactance x, complex susceptance c and the tap ratio
    according to the given parameters.

    .. warning:: This function returns the subsceptance b as a complex number
        **(-img + -re*i)**. MAT/PYPOWER is only intended to calculate the
        imaginary part of the subceptance. However, internally c is
        multiplied by i. By using subsceptance in this way, it is possible
        to consider the ferromagnetic loss of the coil. Which would
        otherwise be neglected.


    .. warning:: Tab switches effect calculation as following:
        On **high-voltage** side(=1) -> only **tab** gets adapted.
        On **low-voltage** side(=2) -> **tab, x, r** get adapted.
        This is consistent with Sincal.
        The Sincal method in this case is questionable.


    **INPUT**:
        **pd_trafo** - The pandapower format Transformer Dataframe.
                        The Transformer modell will only readfrom pd_net

    **RETURN**:
        **temp_para** - Temporary transformer parameter. Which is a complex128
                        Nunmpy array. with the following order:
                        0:r_pu; 1:x_pu; 2:b_pu; 3:tab;

    """
    bus_lookup = net["_pd2ppc_lookups"]["bus"]
    if trafo_df is None:
        trafo_df = net["trafo"]
    parallel = trafo_df["parallel"].values
    vn_lv = get_values(ppc["bus"][:, BASE_KV], trafo_df["lv_bus"].values,
                       bus_lookup)
    ### Construct np.array to parse results in ###
    # 0:r_pu; 1:x_pu; 2:b_pu; 3:tab;
    temp_para = np.zeros(shape=(len(trafo_df), 5), dtype=np.complex128)
    vn_trafo_hv, vn_trafo_lv, shift = _calc_tap_from_dataframe(
        net, trafo_df, vn_lv)
    ratio = _calc_nominal_ratio_from_dataframe(ppc, trafo_df, vn_trafo_hv,
                                               vn_trafo_lv, bus_lookup)
    r, x, y = _calc_r_x_y_from_dataframe(net, trafo_df, vn_trafo_lv, vn_lv,
                                         net.sn_kva)
    temp_para[:, 0] = r / parallel
    temp_para[:, 1] = x / parallel
    temp_para[:, 2] = y * parallel
    temp_para[:, 3] = ratio
    temp_para[:, 4] = shift
    return temp_para
Ejemplo n.º 3
0
def _calc_xward_parameter(net, ppc):
    bus_lookup = net["_pd2ppc_lookups"]["bus"]
    baseR = np.square(get_values(ppc["bus"][:, BASE_KV], net["xward"]["bus"].values, bus_lookup)) /\
                net.sn_kva * 1e3
    t = np.zeros(shape=(len(net["xward"].index), 5), dtype=np.complex128)
    xw_is = net["_is_elements"]["xward"]
    t[:, 0] = bus_lookup[net["xward"]["bus"].values]
    t[:, 1] = bus_lookup[net["xward"]["ad_bus"].values]
    t[:, 2] = net["xward"]["r_ohm"] / baseR
    t[:, 3] = net["xward"]["x_ohm"] / baseR
    t[:, 4] = xw_is
    return t
Ejemplo n.º 4
0
def _calc_nominal_ratio_from_dataframe(ppc, trafo_df, vn_hv_kv, vn_lv_kv, bus_lookup):
    """
    Calculates (Vectorized) the off nominal tap ratio::

                  (vn_hv_kv / vn_lv_kv) / (ub1_in_kv / ub2_in_kv)

    INPUT:
        **net** (Dataframe) - The net for which to calc the tap ratio.

        **vn_hv_kv** (1d array, float) - The adjusted nominal high voltages

        **vn_lv_kv** (1d array, float) - The adjusted nominal low voltages

    OUTPUT:
        **tab** (1d array, float) - The off-nominal tap ratio
    """
    # Calculating tab (trasformer off nominal turns ratio)
    tap_rat = vn_hv_kv / vn_lv_kv
    nom_rat = get_values(ppc["bus"][:, BASE_KV], trafo_df["hv_bus"].values, bus_lookup) / \
              get_values(ppc["bus"][:, BASE_KV], trafo_df["lv_bus"].values, bus_lookup)
    return tap_rat / nom_rat
Ejemplo n.º 5
0
def _calc_xward_parameter(net, ppc):
    bus_lookup = net["_pd2ppc_lookups"]["bus"]
    f, t = net["_pd2ppc_lookups"]["branch"]["xward"]
    branch = ppc["branch"]
    baseR = np.square(get_values(ppc["bus"][:, BASE_KV], net["xward"]["bus"].values, bus_lookup)) / \
            net.sn_mva
    xw_is = net["_is_elements"]["xward"]
    branch[f:t, F_BUS] = bus_lookup[net["xward"]["bus"].values]
    branch[f:t, T_BUS] = bus_lookup[net._pd2ppc_lookups["aux"]["xward"]]
    branch[f:t, BR_R] = net["xward"]["r_ohm"] / baseR
    branch[f:t, BR_X] = net["xward"]["x_ohm"] / baseR
    branch[f:t, BR_STATUS] = xw_is
Ejemplo n.º 6
0
def _calc_xward_parameter(net, ppc, is_elems, bus_lookup):
    bus_is = is_elems['bus']
    baseR = np.square(get_values(ppc["bus"][:, BASE_KV], net["xward"]["bus"].values, bus_lookup))
    t = np.zeros(shape=(len(net["xward"].index), 5), dtype=np.complex128)
    xw_is = np.in1d(net["xward"].bus.values, bus_is.index) \
        & net["xward"].in_service.values.astype(bool)
    t[:, 0] = get_indices(net["xward"]["bus"].values, bus_lookup)
    t[:, 1] = get_indices(net["xward"]["ad_bus"].values, bus_lookup)
    t[:, 2] = net["xward"]["r_ohm"] / baseR
    t[:, 3] = net["xward"]["x_ohm"] / baseR
    t[:, 4] = xw_is
    return t
Ejemplo n.º 7
0
def _extract_result_ppci_to_pp(net, ppc, ppci):
    # convert to pandapower indices
    ppc = _copy_results_ppci_to_ppc(ppci, ppc, mode="se")

    # extract results from ppc
    try:
        _add_pf_options(net,
                        tolerance_mva=1e-8,
                        trafo_loading="current",
                        numba=True,
                        ac=True,
                        algorithm='nr',
                        max_iteration="auto")
    except:
        pass
    # writes res_bus.vm_pu / va_degree and res_line
    _extract_results_se(net, ppc)

    # restore backup of previous results
    _rename_results(net)

    # additionally, write bus power demand results (these are not written in _extract_results)
    mapping_table = net["_pd2ppc_lookups"]["bus"]
    net.res_bus_est.index = net.bus.index
    net.res_bus_est.p_mw = get_values(ppc["bus"][:, 2], net.bus.index.values,
                                      mapping_table)
    net.res_bus_est.q_mvar = get_values(ppc["bus"][:, 3], net.bus.index.values,
                                        mapping_table)

    _clean_up(net)
    # delete results which are not correctly calculated
    for k in list(net.keys()):
        if k.startswith("res_") and k.endswith("_est") and \
                k not in ("res_bus_est", "res_line_est", "res_trafo_est", "res_trafo3w_est"):
            del net[k]
    return net
Ejemplo n.º 8
0
def _branches_with_oos_buses(net, ppc):
    """
    Updates the ppc["branch"] matrix with the changed from or to values
    if the branch is connected to an out of service bus

    Adds auxiliary buses if branch is connected to an out of service bus
    Sets branch out of service if connected to two out of service buses

    **INPUT**:
        **n** - The pandapower format network

        **ppc** - The PYPOWER format network to fill in values
        **bus_is** - The in service buses
    """
    bus_lookup = net["_pd2ppc_lookups"]["bus"]
    # get in service elements
    _is_elements = net["_is_elements"]
    bus_is = _is_elements['bus']
    line_is = _is_elements['line']

    n_oos_buses = len(net['bus']) - len(bus_is)

    # only filter lines at oos buses if oos buses exists
    if n_oos_buses > 0:
        n_bus = len(ppc["bus"])
        future_buses = [ppc["bus"]]
        # out of service buses
        bus_oos = net['bus'].drop(bus_is.index).index
        # from buses of line
        f_bus = line_is.from_bus.values
        t_bus = line_is.to_bus.values

        # determine on which side of the line the oos bus is located
        mask_from = np.in1d(f_bus, bus_oos)
        mask_to = np.in1d(t_bus, bus_oos)

        # determine if line is only connected to out of service buses -> set
        # branch in ppc out of service
        mask_and = mask_to & mask_from

        if np.any(mask_and):
            ppc["branch"][line_is[mask_and].index, BR_STATUS] = 0
            line_is = line_is[~mask_and]
            f_bus = f_bus[~mask_and]
            t_bus = t_bus[~mask_and]
            mask_from = mask_from[~mask_and]
            mask_to = mask_to[~mask_and]

        mask_or = mask_to | mask_from
        # check whether buses are connected to line
        oos_buses_at_lines = np.r_[f_bus[mask_from], t_bus[mask_to]]
        n_oos_buses_at_lines = len(oos_buses_at_lines)

        # only if oos_buses are at lines (they could be isolated as well)
        if n_oos_buses_at_lines > 0:
            ls_info = np.zeros((n_oos_buses_at_lines, 3), dtype=int)
            ls_info[:, 0] = mask_to[mask_or] & ~mask_from[mask_or]
            ls_info[:, 1] = oos_buses_at_lines
            ls_info[:, 2] = np.nonzero(
                np.in1d(net['line'].index, line_is.index[mask_or]))[0]

            # ls_info = list(map(mapfunc,
            #               line_switches["bus"].values,
            #               line_switches["element"].values))
            # we now have the following matrix
            # 0: 1 if switch is at to_bus, 0 else
            # 1: bus of the switch
            # 2: position of the line a switch is connected to
            # ls_info = np.array(ls_info, dtype=int)

            # build new buses
            new_ls_buses = np.zeros(shape=(n_oos_buses_at_lines, 13),
                                    dtype=float)
            new_indices = np.arange(n_bus, n_bus + n_oos_buses_at_lines)
            # the newly created buses
            new_ls_buses[:] = np.array(
                [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1.1, 0.9])
            new_ls_buses[:, 0] = new_indices
            new_ls_buses[:, 9] = get_values(ppc["bus"][:, BASE_KV],
                                            ls_info[:, 1], bus_lookup)

            future_buses.append(new_ls_buses)

            # re-route the end of lines to a new bus
            ppc["branch"][ls_info[ls_info[:, 0].astype(bool), 2], 1] = \
                new_indices[ls_info[:, 0].astype(bool)]
            ppc["branch"][ls_info[np.logical_not(ls_info[:, 0]), 2], 0] = \
                new_indices[np.logical_not(ls_info[:, 0])]

            ppc["bus"] = np.vstack(future_buses)
Ejemplo n.º 9
0
def _switch_branches(net, ppc):
    """
    Updates the ppc["branch"] matrix with the changed from or to values
    according of the status of switches

    **INPUT**:
        **pd_net** - The pandapower format network

        **ppc** - The PYPOWER format network to fill in values
    """
    bus_lookup = net["_pd2ppc_lookups"]["bus"]
    connectivity_check = net["_options"]["check_connectivity"]
    mode = net._options["mode"]
    # get in service elements
    _is_elements = net["_is_elements"]
    lines_is = _is_elements['line']
    bus_is = _is_elements['bus']

    # opened bus line switches
    slidx = (net["switch"]["closed"].values == 0) \
        & (net["switch"]["et"].values == "l")

    # check if there are multiple opened switches at a line (-> set line out of service)
    sw_elem = net['switch'].ix[slidx].element
    m = np.zeros_like(sw_elem, dtype=bool)
    m[np.unique(sw_elem, return_index=True)[1]] = True

    # if non unique elements are in sw_elem (= multiple opened bus line switches)
    if np.count_nonzero(m) < len(sw_elem):
        from_bus = lines_is.ix[sw_elem[~m]].from_bus
        to_bus = lines_is.ix[sw_elem[~m]].to_bus
        # check if branch is already out of service -> ignore switch
        from_bus = from_bus[~np.isnan(from_bus)].values.astype(int)
        to_bus = to_bus[~np.isnan(to_bus)].values.astype(int)

        # set branch in ppc out of service if from and to bus are at a line which is in service
        if not connectivity_check and from_bus.size and to_bus.size:
            # get from and to buses of these branches
            ppc_from = bus_lookup[from_bus]
            ppc_to = bus_lookup[to_bus]
            ppc_idx = np.in1d(ppc['branch'][:, 0], ppc_from)\
                & np.in1d(ppc['branch'][:, 1], ppc_to)
            ppc["branch"][ppc_idx, BR_STATUS] = 0

            # drop from in service lines as well
            lines_is = lines_is.drop(sw_elem[~m])

    # opened switches at in service lines
    slidx = slidx\
        & (np.in1d(net["switch"]["element"].values, lines_is.index)) \
        & (np.in1d(net["switch"]["bus"].values, bus_is.index))
    nlo = np.count_nonzero(slidx)

    stidx = (net.switch["closed"].values == 0) & (net.switch["et"].values
                                                  == "t")
    nto = np.count_nonzero(stidx)

    if (nlo + nto) > 0:
        n_bus = len(ppc["bus"])

        if nlo:
            future_buses = [ppc["bus"]]
            line_switches = net["switch"].loc[slidx]

            # determine on which side the switch is located
            mapfunc = partial(_gather_branch_switch_info,
                              branch_type="l",
                              net=net)
            ls_info = list(
                map(mapfunc, line_switches["bus"].values,
                    line_switches["element"].values))
            # we now have the following matrix
            # 0: 1 if switch is at to_bus, 0 else
            # 1: bus of the switch
            # 2: position of the line a switch is connected to
            ls_info = np.array(ls_info, dtype=int)

            # build new buses
            new_ls_buses = np.zeros(shape=(nlo, 13), dtype=float)
            new_indices = np.arange(n_bus, n_bus + nlo)
            # the newly created buses
            new_ls_buses[:] = np.array(
                [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1.1, 0.9])
            new_ls_buses[:, 0] = new_indices
            new_ls_buses[:, 9] = get_values(ppc["bus"][:, BASE_KV],
                                            ls_info[:, 1], bus_lookup)
            #             set voltage of new buses to voltage on other branch end
            to_buses = ppc["branch"][ls_info[ls_info[:, 0].astype(bool), 2],
                                     1].real.astype(int)
            from_buses = ppc["branch"][ls_info[np.logical_not(ls_info[:, 0]), 2], 0].real\
                .astype(int)
            if len(to_buses):
                ix = ls_info[:, 0] == 1
                new_ls_buses[ix, 7] = ppc["bus"][to_buses, 7]
                new_ls_buses[ix, 8] = ppc["bus"][to_buses, 8]
            if len(from_buses):
                ix = ls_info[:, 0] == 0
                new_ls_buses[ix, 7] = ppc["bus"][from_buses, 7]
                new_ls_buses[ix, 8] = ppc["bus"][from_buses, 8]

            future_buses.append(new_ls_buses)
            if mode == "sc":
                ppc["bus_sc"] = np.vstack(
                    [ppc["bus_sc"],
                     np.zeros(shape=(nlo, 10), dtype=float)])

            # re-route the end of lines to a new bus
            ppc["branch"][ls_info[ls_info[:, 0].astype(bool), 2], 1] = \
                new_indices[ls_info[:, 0].astype(bool)]
            ppc["branch"][ls_info[np.logical_not(ls_info[:, 0]), 2], 0] = \
                new_indices[np.logical_not(ls_info[:, 0])]

            ppc["bus"] = np.vstack(future_buses)

        if nto:
            future_buses = [ppc["bus"]]
            trafo_switches = net["switch"].loc[stidx]

            # determine on which side the switch is located
            mapfunc = partial(_gather_branch_switch_info,
                              branch_type="t",
                              net=net)
            ts_info = list(
                map(mapfunc, trafo_switches["bus"].values,
                    trafo_switches["element"].values))
            # we now have the following matrix
            # 0: 1 if switch is at lv_bus, 0 else
            # 1: bus of the switch
            # 2: position of the trafo a switch is connected to
            ts_info = np.array(ts_info, dtype=int)

            # build new buses
            new_ts_buses = np.zeros(shape=(nto, 13), dtype=float)
            new_indices = np.arange(n_bus + nlo, n_bus + nlo + nto)
            new_ts_buses[:] = np.array(
                [0, 1, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1.1, 0.9])
            new_ts_buses[:, 0] = new_indices
            new_ts_buses[:, 9] = get_values(ppc["bus"][:, BASE_KV],
                                            ts_info[:, 1], bus_lookup)
            # set voltage of new buses to voltage on other branch end
            to_buses = ppc["branch"][ts_info[ts_info[:, 0].astype(bool), 2],
                                     1].real.astype(int)
            from_buses = ppc["branch"][ts_info[np.logical_not(ts_info[:, 0]), 2], 0].real\
                .astype(int)

            # set newly created buses to voltage on other side of
            if len(to_buses):
                ix = ts_info[:, 0] == 1
                taps = ppc["branch"][ts_info[ts_info[:, 0].astype(bool), 2],
                                     8].real
                shift = ppc["branch"][ts_info[ts_info[:, 0].astype(bool), 2],
                                      9].real
                new_ts_buses[ix, 7] = ppc["bus"][to_buses, 7] * taps
                new_ts_buses[ix, 8] = ppc["bus"][to_buses, 8] + shift
            if len(from_buses):
                ix = ts_info[:, 0] == 0
                taps = ppc["branch"][ts_info[np.logical_not(ts_info[:, 0]), 2],
                                     8].real
                shift = ppc["branch"][ts_info[np.logical_not(ts_info[:, 0]),
                                              2], 9].real
                new_ts_buses[ix, 7] = ppc["bus"][from_buses, 7] * taps
                new_ts_buses[ix, 8] = ppc["bus"][from_buses, 8] + shift

            future_buses.append(new_ts_buses)

            # re-route the hv/lv-side of the trafo to a new bus
            # (trafo entries follow line entries)
            at_lv_bus = ts_info[:, 0].astype(bool)
            at_hv_bus = ~at_lv_bus
            ppc["branch"][len(net.line) + ts_info[at_lv_bus, 2], 1] = \
                new_indices[at_lv_bus]
            ppc["branch"][len(net.line) + ts_info[at_hv_bus, 2], 0] = \
                new_indices[at_hv_bus]

            ppc["bus"] = np.vstack(future_buses)
Ejemplo n.º 10
0
    def estimate(self,
                 v_start=None,
                 delta_start=None,
                 calculate_voltage_angles=True):
        """
        The function estimate is the main function of the module. It takes up to three input
        arguments: v_start, delta_start and calculate_voltage_angles. The first two are the initial
        state variables for the estimation process. Usually they can be initialized in a
        "flat-start" condition: All voltages being 1.0 pu and all voltage angles being 0 degrees.
        In this case, the parameters can be left at their default values (None). If the estimation
        is applied continuously, using the results from the last estimation as the starting
        condition for the current estimation can decrease the  amount of iterations needed to
        estimate the current state. The third parameter defines whether all voltage angles are
        calculated absolutely, including phase shifts from transformers. If only the relative
        differences between buses are required, this parameter can be set to False. Returned is a
        boolean value, which is true after a successful estimation and false otherwise.
        The resulting complex voltage will be written into the pandapower network. The result
        fields are found res_bus_est of the pandapower network.

        INPUT:
            **net** - The net within this line should be created

            **v_start** (np.array, shape=(1,), optional) - Vector with initial values for all
            voltage magnitudes in p.u. (sorted by bus index)

            **delta_start** (np.array, shape=(1,), optional) - Vector with initial values for all
            voltage angles in degrees (sorted by bus index)
        
        OPTIONAL:
            **calculate_voltage_angles** - (bool) - Take into account absolute voltage angles and
            phase shifts in transformers Default is True.

        OUTPUT:
            **successful** (boolean) - True if the estimation process was successful

        Optional estimation variables:
            The bus power injections can be accessed with *se.s_node_powers* and the estimated
            values corresponding to the (noisy) measurement values with *se.hx*. (*hx* denotes h(x))

        EXAMPLE:
            success = estimate(np.array([1.0, 1.0, 1.0]), np.array([0.0, 0.0, 0.0]))

        """
        if self.net is None:
            raise UserWarning("Component was not initialized with a network.")

        # add initial values for V and delta
        # node voltages
        # V<delta
        if v_start is None:
            v_start = np.ones(self.net.bus.shape[0])
        if delta_start is None:
            delta_start = np.zeros(self.net.bus.shape[0])

        # initialize the ppc bus with the initial values given
        vm_backup, va_backup = self.net.res_bus.vm_pu.copy(
        ), self.net.res_bus.va_degree.copy()
        self.net.res_bus.vm_pu = v_start
        self.net.res_bus.vm_pu[self.net.bus.index[self.net.bus.in_service ==
                                                  False]] = np.nan
        self.net.res_bus.va_degree = delta_start

        # select elements in service and convert pandapower ppc to ppc
        self.net._options = {}
        _add_ppc_options(self.net,
                         check_connectivity=False,
                         init="results",
                         trafo_model="t",
                         copy_constraints_to_ppc=False,
                         mode="pf",
                         enforce_q_lims=False,
                         calculate_voltage_angles=calculate_voltage_angles,
                         r_switch=0.0,
                         recycle=dict(_is_elements=False,
                                      ppc=False,
                                      Ybus=False))
        self.net["_is_elements"] = _select_is_elements(self.net)
        ppc, _ = _pd2ppc(self.net)
        mapping_table = self.net["_pd2ppc_lookups"]["bus"]
        br_cols = ppc["branch"].shape[1]
        bs_cols = ppc["bus"].shape[1]

        self.net.res_bus.vm_pu = vm_backup
        self.net.res_bus.va_degree = va_backup

        # add 6 columns to ppc[bus] for Vm, Vm std dev, P, P std dev, Q, Q std dev
        bus_append = np.full((ppc["bus"].shape[0], 6),
                             np.nan,
                             dtype=ppc["bus"].dtype)

        v_measurements = self.net.measurement[
            (self.net.measurement.type == "v")
            & (self.net.measurement.element_type == "bus")]
        if len(v_measurements):
            bus_positions = mapping_table[v_measurements.bus.values.astype(
                int)]
            bus_append[bus_positions, 0] = v_measurements.value.values
            bus_append[bus_positions, 1] = v_measurements.std_dev.values

        p_measurements = self.net.measurement[
            (self.net.measurement.type == "p")
            & (self.net.measurement.element_type == "bus")]
        if len(p_measurements):
            bus_positions = mapping_table[p_measurements.bus.values.astype(
                int)]
            bus_append[bus_positions,
                       2] = p_measurements.value.values * 1e3 / self.s_ref
            bus_append[bus_positions,
                       3] = p_measurements.std_dev.values * 1e3 / self.s_ref

        q_measurements = self.net.measurement[
            (self.net.measurement.type == "q")
            & (self.net.measurement.element_type == "bus")]
        if len(q_measurements):
            bus_positions = mapping_table[q_measurements.bus.values.astype(
                int)]
            bus_append[bus_positions,
                       4] = q_measurements.value.values * 1e3 / self.s_ref
            bus_append[bus_positions,
                       5] = q_measurements.std_dev.values * 1e3 / self.s_ref

        # add virtual measurements for artificial buses, which were created because
        # of an open line switch. p/q are 0. and std dev is 1. (small value)
        new_in_line_buses = np.setdiff1d(np.arange(ppc["bus"].shape[0]),
                                         mapping_table[mapping_table >= 0])
        bus_append[new_in_line_buses, 2] = 0.
        bus_append[new_in_line_buses, 3] = 1.
        bus_append[new_in_line_buses, 4] = 0.
        bus_append[new_in_line_buses, 5] = 1.

        # add 12 columns to mpc[branch] for Im_from, Im_from std dev, Im_to, Im_to std dev,
        # P_from, P_from std dev, P_to, P_to std dev, Q_from,Q_from std dev,  Q_to, Q_to std dev
        branch_append = np.full((ppc["branch"].shape[0], 12),
                                np.nan,
                                dtype=ppc["branch"].dtype)

        i_measurements = self.net.measurement[
            (self.net.measurement.type == "i")
            & (self.net.measurement.element_type == "line")]
        if len(i_measurements):
            meas_from = i_measurements[(i_measurements.bus.values.astype(
                int) == self.net.line.from_bus[i_measurements.element]).values]
            meas_to = i_measurements[(i_measurements.bus.values.astype(
                int) == self.net.line.to_bus[i_measurements.element]).values]
            ix_from = meas_from.element.values.astype(int)
            ix_to = meas_to.element.values.astype(int)
            i_a_to_pu_from = (self.net.bus.vn_kv[meas_from.bus] * 1e3 /
                              self.s_ref).values
            i_a_to_pu_to = (self.net.bus.vn_kv[meas_to.bus] * 1e3 /
                            self.s_ref).values
            branch_append[ix_from, 0] = meas_from.value.values * i_a_to_pu_from
            branch_append[ix_from,
                          1] = meas_from.std_dev.values * i_a_to_pu_from
            branch_append[ix_to, 2] = meas_to.value.values * i_a_to_pu_to
            branch_append[ix_to, 3] = meas_to.std_dev.values * i_a_to_pu_to

        p_measurements = self.net.measurement[
            (self.net.measurement.type == "p")
            & (self.net.measurement.element_type == "line")]
        if len(p_measurements):
            meas_from = p_measurements[(p_measurements.bus.values.astype(
                int) == self.net.line.from_bus[p_measurements.element]).values]
            meas_to = p_measurements[(p_measurements.bus.values.astype(
                int) == self.net.line.to_bus[p_measurements.element]).values]
            ix_from = meas_from.element.values.astype(int)
            ix_to = meas_to.element.values.astype(int)
            branch_append[ix_from,
                          4] = meas_from.value.values * 1e3 / self.s_ref
            branch_append[ix_from,
                          5] = meas_from.std_dev.values * 1e3 / self.s_ref
            branch_append[ix_to, 6] = meas_to.value.values * 1e3 / self.s_ref
            branch_append[ix_to, 7] = meas_to.std_dev.values * 1e3 / self.s_ref

        q_measurements = self.net.measurement[
            (self.net.measurement.type == "q")
            & (self.net.measurement.element_type == "line")]
        if len(q_measurements):
            meas_from = q_measurements[(q_measurements.bus.values.astype(
                int) == self.net.line.from_bus[q_measurements.element]).values]
            meas_to = q_measurements[(q_measurements.bus.values.astype(
                int) == self.net.line.to_bus[q_measurements.element]).values]
            ix_from = meas_from.element.values.astype(int)
            ix_to = meas_to.element.values.astype(int)
            branch_append[ix_from,
                          8] = meas_from.value.values * 1e3 / self.s_ref
            branch_append[ix_from,
                          9] = meas_from.std_dev.values * 1e3 / self.s_ref
            branch_append[ix_to, 10] = meas_to.value.values * 1e3 / self.s_ref
            branch_append[ix_to,
                          11] = meas_to.std_dev.values * 1e3 / self.s_ref

        i_tr_measurements = self.net.measurement[
            (self.net.measurement.type == "i")
            & (self.net.measurement.element_type == "transformer")]
        if len(i_tr_measurements):
            meas_from = i_tr_measurements[(
                i_tr_measurements.bus.values.astype(int) ==
                self.net.trafo.hv_bus[i_tr_measurements.element]).values]
            meas_to = i_tr_measurements[(
                i_tr_measurements.bus.values.astype(int) ==
                self.net.trafo.lv_bus[i_tr_measurements.element]).values]
            ix_from = meas_from.element.values.astype(int)
            ix_to = meas_to.element.values.astype(int)
            i_a_to_pu_from = (self.net.bus.vn_kv[meas_from.bus] * 1e3 /
                              self.s_ref).values
            i_a_to_pu_to = (self.net.bus.vn_kv[meas_to.bus] * 1e3 /
                            self.s_ref).values
            branch_append[ix_from, 0] = meas_from.value.values * i_a_to_pu_from
            branch_append[ix_from,
                          1] = meas_from.std_dev.values * i_a_to_pu_from
            branch_append[ix_to, 2] = meas_to.value.values * i_a_to_pu_to
            branch_append[ix_to, 3] = meas_to.std_dev.values * i_a_to_pu_to

        p_tr_measurements = self.net.measurement[
            (self.net.measurement.type == "p")
            & (self.net.measurement.element_type == "transformer")]
        if len(p_tr_measurements):
            meas_from = p_tr_measurements[(
                p_tr_measurements.bus.values.astype(int) ==
                self.net.trafo.hv_bus[p_tr_measurements.element]).values]
            meas_to = p_tr_measurements[(
                p_tr_measurements.bus.values.astype(int) ==
                self.net.trafo.lv_bus[p_tr_measurements.element]).values]
            ix_from = len(self.net.line) + meas_from.element.values.astype(int)
            ix_to = len(self.net.line) + meas_to.element.values.astype(int)
            branch_append[ix_from,
                          4] = meas_from.value.values * 1e3 / self.s_ref
            branch_append[ix_from,
                          5] = meas_from.std_dev.values * 1e3 / self.s_ref
            branch_append[ix_to, 6] = meas_to.value.values * 1e3 / self.s_ref
            branch_append[ix_to, 7] = meas_to.std_dev.values * 1e3 / self.s_ref

        q_tr_measurements = self.net.measurement[
            (self.net.measurement.type == "q")
            & (self.net.measurement.element_type == "transformer")]
        if len(q_tr_measurements):
            meas_from = q_tr_measurements[(
                q_tr_measurements.bus.values.astype(int) ==
                self.net.trafo.hv_bus[q_tr_measurements.element]).values]
            meas_to = q_tr_measurements[(
                q_tr_measurements.bus.values.astype(int) ==
                self.net.trafo.lv_bus[q_tr_measurements.element]).values]
            ix_from = len(self.net.line) + meas_from.element.values.astype(int)
            ix_to = len(self.net.line) + meas_to.element.values.astype(int)
            branch_append[ix_from,
                          8] = meas_from.value.values * 1e3 / self.s_ref
            branch_append[ix_from,
                          9] = meas_from.std_dev.values * 1e3 / self.s_ref
            branch_append[ix_to, 10] = meas_to.value.values * 1e3 / self.s_ref
            branch_append[ix_to,
                          11] = meas_to.std_dev.values * 1e3 / self.s_ref

        ppc["bus"] = np.hstack((ppc["bus"], bus_append))
        ppc["branch"] = np.hstack((ppc["branch"], branch_append))

        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            ppc_i = ext2int(ppc)

        p_bus_not_nan = ~np.isnan(ppc_i["bus"][:, bs_cols + 2])
        p_line_f_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 4])
        p_line_t_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 6])
        q_bus_not_nan = ~np.isnan(ppc_i["bus"][:, bs_cols + 4])
        q_line_f_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 8])
        q_line_t_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 10])
        v_bus_not_nan = ~np.isnan(ppc_i["bus"][:, bs_cols + 0])
        i_line_f_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 0])
        i_line_t_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 2])

        # piece together our measurement vector z
        z = np.concatenate(
            (ppc_i["bus"][p_bus_not_nan,
                          bs_cols + 2], ppc_i["branch"][p_line_f_not_nan,
                                                        br_cols + 4],
             ppc_i["branch"][p_line_t_not_nan,
                             br_cols + 6], ppc_i["bus"][q_bus_not_nan,
                                                        bs_cols + 4],
             ppc_i["branch"][q_line_f_not_nan,
                             br_cols + 8], ppc_i["branch"][q_line_t_not_nan,
                                                           br_cols + 10],
             ppc_i["bus"][v_bus_not_nan,
                          bs_cols + 0], ppc_i["branch"][i_line_f_not_nan,
                                                        br_cols + 0],
             ppc_i["branch"][i_line_t_not_nan,
                             br_cols + 2])).real.astype(np.float64)

        # number of nodes
        n_active = len(np.where(ppc_i["bus"][:, 1] != 4)[0])
        slack_buses = np.where(ppc_i["bus"][:, 1] == 3)[0]

        # Check if observability criterion is fulfilled and the state estimation is possible
        if len(z) < 2 * n_active - 1:
            self.logger.error("System is not observable (cancelling)")
            self.logger.error(
                "Measurements available: %d. Measurements required: %d" %
                (len(z), 2 * n_active - 1))
            return False

        # Set the starting values for all active buses
        v_m = ppc_i["bus"][:, 7]
        delta = ppc_i["bus"][:, 8] * np.pi / 180  # convert to rad
        delta_masked = np.ma.array(delta, mask=False)
        delta_masked.mask[slack_buses] = True
        non_slack_buses = np.arange(len(delta))[~delta_masked.mask]

        # Matrix calculation object
        sem = wls_matrix_ops(ppc_i, slack_buses, non_slack_buses, self.s_ref,
                             bs_cols, br_cols)

        # state vector
        E = np.concatenate((delta_masked.compressed(), v_m))

        # Covariance matrix R
        r_cov = np.concatenate(
            (ppc_i["bus"][p_bus_not_nan,
                          bs_cols + 3], ppc_i["branch"][p_line_f_not_nan,
                                                        br_cols + 5],
             ppc_i["branch"][p_line_t_not_nan,
                             br_cols + 7], ppc_i["bus"][q_bus_not_nan,
                                                        bs_cols + 5],
             ppc_i["branch"][q_line_f_not_nan,
                             br_cols + 9], ppc_i["branch"][q_line_t_not_nan,
                                                           br_cols + 11],
             ppc_i["bus"][v_bus_not_nan,
                          bs_cols + 1], ppc_i["branch"][i_line_f_not_nan,
                                                        br_cols + 1],
             ppc_i["branch"][i_line_t_not_nan,
                             br_cols + 3])).real.astype(np.float64)

        r_inv = csr_matrix(np.linalg.inv(np.diagflat(r_cov)**2))

        current_error = 100
        current_iterations = 0

        while current_error > self.tolerance and current_iterations < self.max_iterations:
            self.logger.debug(" Starting iteration %d" %
                              (1 + current_iterations))

            try:

                # create h(x) for the current iteration
                h_x = sem.create_hx(v_m, delta)

                # Residual r
                r = csr_matrix(z - h_x).T

                # Jacobian matrix H
                H = csr_matrix(sem.create_jacobian(v_m, delta))

                # if not np.linalg.cond(H) < 1 / sys.float_info.epsilon:
                #    self.logger.error("Error in matrix H")

                # Gain matrix G_m
                # G_m = H^t * R^-1 * H
                G_m = H.T * (r_inv * H)

                # State Vector difference d_E
                # d_E = G_m^-1 * (H' * R^-1 * r)
                d_E = spsolve(G_m, H.T * (r_inv * r))
                E += d_E

                # Update V/delta
                delta[non_slack_buses] = E[:len(non_slack_buses)]
                v_m = np.squeeze(E[len(non_slack_buses):])

                current_iterations += 1
                current_error = np.max(np.abs(d_E))
                self.logger.debug("Current error: %.4f" % current_error)
            except np.linalg.linalg.LinAlgError:
                self.logger.error(
                    "A problem appeared while using the linear algebra methods."
                    "Check and change the measurement set.")
                return False
        # Print output for results
        if current_error <= self.tolerance:
            successful = True
            self.logger.info(
                "WLS State Estimation successful (%d iterations)" %
                current_iterations)
        else:
            successful = False
            self.logger.info(
                "WLS State Estimation not successful (%d/%d iterations" %
                (current_iterations, self.max_iterations))

        # write voltage into ppc
        ppc_i["bus"][:, 7] = v_m
        ppc_i["bus"][:, 8] = delta * 180 / np.pi  # convert to degree

        # calculate bus powers
        v_cpx = v_m * np.exp(1j * delta)
        bus_powers_conj = np.zeros(len(v_cpx), dtype=np.complex128)
        for i in range(len(v_cpx)):
            bus_powers_conj[i] = np.dot(sem.Y_bus[i, :], v_cpx) * np.conjugate(
                v_cpx[i])
        ppc_i["bus"][:, 2] = bus_powers_conj.real  # saved in per unit
        ppc_i["bus"][:, 3] = -bus_powers_conj.imag  # saved in per unit

        # convert to pandapower indices
        with warnings.catch_warnings():
            warnings.simplefilter("ignore")
            ppc = int2ext(ppc_i)
            _set_buses_out_of_service(ppc)

        # Store results, overwrite old results
        self.net.res_bus_est = pd.DataFrame(
            columns=["vm_pu", "va_degree", "p_kw", "q_kvar"],
            index=self.net.bus.index)
        self.net.res_line_est = pd.DataFrame(columns=[
            "p_from_kw", "q_from_kvar", "p_to_kw", "q_to_kvar", "pl_kw",
            "ql_kvar", "i_from_ka", "i_to_ka", "i_ka", "loading_percent"
        ],
                                             index=self.net.line.index)

        bus_idx = mapping_table[self.net["bus"].index.values]
        self.net["res_bus_est"]["vm_pu"] = ppc["bus"][bus_idx][:, 7]
        self.net["res_bus_est"]["va_degree"] = ppc["bus"][bus_idx][:, 8]

        self.net.res_bus_est.p_kw = -get_values(
            ppc["bus"][:, 2], self.net.bus.index,
            mapping_table) * self.s_ref / 1e3
        self.net.res_bus_est.q_kvar = -get_values(
            ppc["bus"][:, 3], self.net.bus.index,
            mapping_table) * self.s_ref / 1e3
        self.net.res_line_est = calculate_line_results(self.net,
                                                       use_res_bus_est=True)

        # Store some variables required for Chi^2 and r_N_max test:
        self.R_inv = r_inv.toarray()
        self.Gm = G_m.toarray()
        self.r = r.toarray()
        self.H = H.toarray()
        self.Ht = self.H.T
        self.hx = h_x
        self.V = v_m
        self.delta = delta

        return successful
Ejemplo n.º 11
0
def _branch_df_from_trafo3w(net, ppc, trafo3w_df=None):
    """
    Calculates the MAT/PYPOWER-branch-attributes from the pandapower trafo dataframe.

    PYPOWER and MATPOWER uses the PI-model to model transformers.
    This function calculates the resistance r, reactance x, complex susceptance c and the tap ratio
    according to the given parameters.

    .. warning:: This function returns the subsceptance b as a complex number
        **(-img + -re*i)**. MAT/PYPOWER is only intended to calculate the
        imaginary part of the subceptance. However, internally c is
        multiplied by i. By using subsceptance in this way, it is possible
        to consider the ferromagnetic loss of the coil. Which would
        otherwise be neglected.


    .. warning:: Tab switches effect calculation as following:
        On **high-voltage** side(=1) -> only **tab** gets adapted.
        On **low-voltage** side(=2) -> **tab, x, r** get adapted.
        This is consistent with Sincal.
        The Sincal method in this case is questionable.


    **INPUT**:
        **pd_trafo** - The pandapower format Transformer Dataframe.
                        The Transformer modell will only readfrom pd_net

    **RETURN**:
        **temp_para** - Temporary transformer parameter. Which is a complex128
                        Nunmpy array. with the following order:
                        0:r_pu; 1:x_pu; 2:b_pu; 3:tab;

    """
    bus_lookup = net["_pd2ppc_lookups"]["bus"]

    if trafo3w_df is None:
        trafo3w_df = net["trafo3w"]

    # TODO consider parallel trafos
    # parallel = trafo3w_df["parallel"].values

    max_load = trafo3w_df.max_loading_percent.values if "max_loading_percent" in trafo3w_df else 0

    s12 = trafo3w_df[['sn_hv_kva', 'sn_mv_kva']].min(axis=1)
    s23 = trafo3w_df[['sn_mv_kva', 'sn_lv_kva']].min(axis=1)
    s13 = trafo3w_df[['sn_hv_kva', 'sn_lv_kva']].min(axis=1)

    z12 = trafo3w_df.vsc_hv_percent / 100. * (net.sn_kva / s12)
    z23 = trafo3w_df.vsc_mv_percent / 100. * (net.sn_kva / s23)
    z13 = trafo3w_df.vsc_lv_percent / 100. * (net.sn_kva / s13)

    r12 = (trafo3w_df.vscr_hv_percent / 100.) * (net.sn_kva / s12)
    r23 = (trafo3w_df.vscr_mv_percent / 100.) * (net.sn_kva / s23)
    r13 = (trafo3w_df.vscr_lv_percent / 100.) * (net.sn_kva / s13)

    x12 = np.sqrt(z12**2 - r12**2)
    x23 = np.sqrt(z23**2 - r23**2)
    x13 = np.sqrt(z13**2 - r13**2)

    r1 = 0.5 * (r12 + r13 - r23)
    r2 = 0.5 * (r12 + r23 - r13)
    r3 = 0.5 * (r13 + r23 - r12)
    r = {1: r1, 2: r2, 3: r3}

    x1 = 0.5 * (x12 + x13 - x23)
    x2 = 0.5 * (x12 + x23 - x13)
    x3 = 0.5 * (x13 + x23 - x12)

    x = {1: x1, 2: x2, 3: x3}

    g = ((trafo3w_df.pfe_kw / trafo3w_df['sn_hv_kva']) *
         (trafo3w_df['sn_hv_kva'] / net.sn_kva))
    b = -np.sqrt((trafo3w_df.i0_percent / 100.)**2 -
                 (trafo3w_df.pfe_kw / trafo3w_df['sn_hv_kva'])**2) * (
                     trafo3w_df['sn_hv_kva'] / net.sn_kva)
    y = -g * 1j + b  # * np.sign(trafo3w_df.i0_percent)

    # g = (trafo3w_df.pfe_kw / s12) * (s12 / net.sn_kva)
    # b = - np.sqrt((trafo3w_df.i0_percent / 100.) ** 2 - (
    # trafo3w_df.pfe_kw / s12) ** 2) * (s12 / net.sn_kva)
    # y = - g * 1j + b  # * np.sign(trafo3w_df.i0_percent)

    tap_variables = ["tp_pos", "tp_mid", "tp_max", "tp_min", "tp_st_percent"]
    branch_variables = [
        'F_BUS', 'T_BUS', 'BR_R', 'BR_X', 'BR_B', 'TAP', 'SHIFT', 'BR_STATUS',
        'RATE_A'
    ]

    columns_branchtrafo = ['hv_bus', 'lv_bus', 'vn_hv_kv', 'vn_lv_kv'] +\
                          branch_variables +\
                          tap_variables + ['shift_degree']

    branches_trafo3w = dict()
    for tr_i, side in zip((1, 2, 3), ('hv', 'mv', 'lv')):

        branches_trafo3w[tr_i] = pd.DataFrame(index=trafo3w_df.index,
                                              columns=columns_branchtrafo,
                                              dtype=trafo3w_df.dtypes)

        branches_trafo3w[tr_i]['hv_bus'] = trafo3w_df[side + '_bus'].values
        branches_trafo3w[tr_i]['lv_bus'] = trafo3w_df.ad_bus.values

        branches_trafo3w[tr_i]['F_BUS'] = bus_lookup[(
            trafo3w_df[side + '_bus'].values).astype(int)]
        branches_trafo3w[tr_i]['T_BUS'] = bus_lookup[(
            trafo3w_df.ad_bus.values).astype(int)]

        branches_trafo3w[tr_i]['vn_hv_kv'] = trafo3w_df['vn_' + side +
                                                        '_kv'].values
        branches_trafo3w[tr_i]['vn_lv_kv'] = trafo3w_df.vn_hv_kv.values

        # phase shift
        if side == 'hv':
            branches_trafo3w[tr_i]['shift_degree'] = 0
        elif side == 'mv':
            branches_trafo3w[tr_i][
                'shift_degree'] = -trafo3w_df['shift_mv_degree']
        elif side == 'lv':
            branches_trafo3w[tr_i][
                'shift_degree'] = -trafo3w_df['shift_lv_degree']

        tap_mask = trafo3w_df.tp_side == side
        branches_trafo3w[tr_i].loc[tap_mask, tap_variables] = trafo3w_df.loc[
            tap_mask, tap_variables].values
        branches_trafo3w[tr_i].loc[tap_mask, 'tp_side'] = 'hv'

        for var in tap_variables:
            branches_trafo3w[tr_i][var] = pd.to_numeric(
                branches_trafo3w[tr_i][var])
        # TODO try to avoid this (this is only in order to make tp_side object and comparable with a string)
        branches_trafo3w[tr_i]['tp_side'] = branches_trafo3w[tr_i][
            'tp_side'].astype(object)

        vn = get_values(ppc["bus"][:, BASE_KV],
                        trafo3w_df[side + "_bus"].values, bus_lookup)

        vn_trafo, vn_trafo_ad, shift = _calc_tap_from_dataframe(
            net, branches_trafo3w[tr_i])

        ratio = vn_trafo / vn

        branches_trafo3w[tr_i]['TAP'] = ratio
        branches_trafo3w[tr_i]['SHIFT'] = shift

        branches_trafo3w[tr_i].loc[:, 'BR_R'] = r[tr_i]
        branches_trafo3w[tr_i].loc[:, 'BR_X'] = x[tr_i]

        if tr_i == 1:
            branches_trafo3w[tr_i].loc[:, 'BR_B'] = y
        else:
            branches_trafo3w[tr_i].loc[:, 'BR_B'] = 0

        branches_trafo3w[tr_i].loc[:, 'BR_STATUS'] = trafo3w_df[
            'in_service'].values

        branches_trafo3w[tr_i].loc[:, 'RATE_A'] = max_load

    return pd.concat(
        [branches_trafo3w[1], branches_trafo3w[2], branches_trafo3w[3]],
        ignore_index=True)
Ejemplo n.º 12
0
    def estimate(self,
                 v_start=None,
                 delta_start=None,
                 calculate_voltage_angles=True):
        """
        The function estimate is the main function of the module. It takes up to three input
        arguments: v_start, delta_start and calculate_voltage_angles. The first two are the initial
        state variables for the estimation process. Usually they can be initialized in a
        "flat-start" condition: All voltages being 1.0 pu and all voltage angles being 0 degrees.
        In this case, the parameters can be left at their default values (None). If the estimation
        is applied continuously, using the results from the last estimation as the starting
        condition for the current estimation can decrease the  amount of iterations needed to
        estimate the current state. The third parameter defines whether all voltage angles are
        calculated absolutely, including phase shifts from transformers. If only the relative
        differences between buses are required, this parameter can be set to False. Returned is a
        boolean value, which is true after a successful estimation and false otherwise.
        The resulting complex voltage will be written into the pandapower network. The result
        fields are found res_bus_est of the pandapower network.

        INPUT:
            **net** - The net within this line should be created

            **v_start** (np.array, shape=(1,), optional) - Vector with initial values for all
            voltage magnitudes in p.u. (sorted by bus index)

            **delta_start** (np.array, shape=(1,), optional) - Vector with initial values for all
            voltage angles in degrees (sorted by bus index)

        OPTIONAL:
            **calculate_voltage_angles** - (bool) - Take into account absolute voltage angles and
            phase shifts in transformers Default is True.

        OUTPUT:
            **successful** (boolean) - True if the estimation process was successful

        Optional estimation variables:
            The bus power injections can be accessed with *se.s_node_powers* and the estimated
            values corresponding to the (noisy) measurement values with *se.hx*. (*hx* denotes h(x))

        EXAMPLE:
            success = estimate(np.array([1.0, 1.0, 1.0]), np.array([0.0, 0.0, 0.0]))

        """
        if self.net is None:
            raise UserWarning("Component was not initialized with a network.")
        t0 = time()
        # add initial values for V and delta
        # node voltages
        # V<delta
        if v_start is None:
            v_start = np.ones(self.net.bus.shape[0])
        if delta_start is None:
            delta_start = np.zeros(self.net.bus.shape[0])

        # initialize result tables if not existent
        _copy_power_flow_results(self.net)

        # initialize ppc
        ppc, ppci = _init_ppc(self.net, v_start, delta_start,
                              calculate_voltage_angles)

        # add measurements to ppci structure
        ppci = _add_measurements_to_ppc(self.net, ppci, self.s_ref)

        # calculate relevant vectors from ppci measurements
        z, self.pp_meas_indices, r_cov = _build_measurement_vectors(ppci)

        # number of nodes
        n_active = len(np.where(ppci["bus"][:, 1] != 4)[0])
        slack_buses = np.where(ppci["bus"][:, 1] == 3)[0]

        # Check if observability criterion is fulfilled and the state estimation is possible
        if len(z) < 2 * n_active - 1:
            self.logger.error("System is not observable (cancelling)")
            self.logger.error(
                "Measurements available: %d. Measurements required: %d" %
                (len(z), 2 * n_active - 1))
            return False

        # set the starting values for all active buses
        v_m = ppci["bus"][:, 7]
        delta = ppci["bus"][:, 8] * np.pi / 180  # convert to rad
        delta_masked = np.ma.array(delta, mask=False)
        delta_masked.mask[slack_buses] = True
        non_slack_buses = np.arange(len(delta))[~delta_masked.mask]

        # matrix calculation object
        sem = wls_matrix_ops(ppci, slack_buses, non_slack_buses, self.s_ref)

        # state vector
        E = np.concatenate((delta_masked.compressed(), v_m))

        # invert covariance matrix
        r_inv = csr_matrix(np.linalg.inv(np.diagflat(r_cov)**2))

        current_error = 100.
        cur_it = 0
        G_m, r, H, h_x = None, None, None, None

        while current_error > self.tolerance and cur_it < self.max_iterations:
            self.logger.debug(" Starting iteration %d" % (1 + cur_it))
            try:
                # create h(x) for the current iteration
                h_x = sem.create_hx(v_m, delta)

                # residual r
                r = csr_matrix(z - h_x).T

                # jacobian matrix H
                H = csr_matrix(sem.create_jacobian(v_m, delta))

                # gain matrix G_m
                # G_m = H^t * R^-1 * H
                G_m = H.T * (r_inv * H)

                # state vector difference d_E
                # d_E = G_m^-1 * (H' * R^-1 * r)
                d_E = spsolve(G_m, H.T * (r_inv * r))
                E += d_E

                # update V/delta
                delta[non_slack_buses] = E[:len(non_slack_buses)]
                v_m = np.squeeze(E[len(non_slack_buses):])

                # prepare next iteration
                cur_it += 1
                current_error = np.max(np.abs(d_E))
                self.logger.debug("Current error: %.7f" % current_error)

            except np.linalg.linalg.LinAlgError:
                self.logger.error(
                    "A problem appeared while using the linear algebra methods."
                    "Check and change the measurement set.")
                return False

        # print output for results
        if current_error <= self.tolerance:
            successful = True
            self.logger.debug(
                "WLS State Estimation successful (%d iterations)" % cur_it)
        else:
            successful = False
            self.logger.debug(
                "WLS State Estimation not successful (%d/%d iterations)" %
                (cur_it, self.max_iterations))

        # store results for all elements
        # calculate bus power injections
        v_cpx = v_m * np.exp(1j * delta)
        bus_powers_conj = np.zeros(len(v_cpx), dtype=np.complex128)
        for i in range(len(v_cpx)):
            bus_powers_conj[i] = np.dot(sem.Y_bus[i, :], v_cpx) * np.conjugate(
                v_cpx[i])

        ppci["bus"][:, 2] = bus_powers_conj.real  # saved in per unit
        ppci["bus"][:, 3] = -bus_powers_conj.imag  # saved in per unit
        ppci["bus"][:, 7] = v_m
        ppci["bus"][:, 8] = delta * 180 / np.pi  # convert to degree

        # calculate line results (in ppc_i)
        s_ref, bus, gen, branch = _get_pf_variables_from_ppci(ppci)[0:4]
        out = np.flatnonzero(branch[:,
                                    BR_STATUS] == 0)  # out-of-service branches
        br = np.flatnonzero(branch[:, BR_STATUS]).astype(
            int)  # in-service branches
        # complex power at "from" bus
        Sf = v_cpx[np.real(branch[br, F_BUS]).astype(int)] * np.conj(
            sem.Yf[br, :] * v_cpx) * s_ref
        # complex power injected at "to" bus
        St = v_cpx[np.real(branch[br, T_BUS]).astype(int)] * np.conj(
            sem.Yt[br, :] * v_cpx) * s_ref
        branch[np.ix_(br, [PF, QF, PT, QT])] = np.c_[Sf.real, Sf.imag, St.real,
                                                     St.imag]
        branch[np.ix_(out, [PF, QF, PT, QT])] = np.zeros((len(out), 4))
        et = time() - t0
        ppci = _store_results_from_pf_in_ppci(ppci, bus, gen, branch,
                                              successful, cur_it, et)

        # convert to pandapower indices
        ppc = _copy_results_ppci_to_ppc(ppci, ppc, mode="se")

        # extract results from ppc
        _add_pf_options(self.net,
                        tolerance_kva=1e-5,
                        trafo_loading="current",
                        numba=True,
                        ac=True,
                        algorithm='nr',
                        max_iteration="auto")
        # writes res_bus.vm_pu / va_degree and res_line
        _extract_results_se(self.net, ppc)

        # restore backup of previous results
        _rename_results(self.net)

        # additionally, write bus power injection results (these are not written in _extract_results)
        mapping_table = self.net["_pd2ppc_lookups"]["bus"]
        self.net.res_bus_est.p_kw = -get_values(
            ppc["bus"][:, 2], self.net.bus.index.values,
            mapping_table) * self.s_ref / 1e3
        self.net.res_bus_est.q_kvar = -get_values(
            ppc["bus"][:, 3], self.net.bus.index.values,
            mapping_table) * self.s_ref / 1e3

        # store variables required for chi^2 and r_N_max test:
        self.R_inv = r_inv.toarray()
        self.Gm = G_m.toarray()
        self.r = r.toarray()
        self.H = H.toarray()
        self.Ht = self.H.T
        self.hx = h_x
        self.V = v_m
        self.delta = delta

        # delete results which are not correctly calculated
        for k in list(self.net.keys()):
            if k.startswith("res_") and k.endswith("_est") and \
                    k not in ("res_bus_est", "res_line_est", "res_trafo_est", "res_trafo3w_est"):
                del self.net[k]

        return successful
Ejemplo n.º 13
0
    def estimate(self,
                 v_start='flat',
                 delta_start='flat',
                 calculate_voltage_angles=True,
                 zero_injection=None,
                 fuse_buses_with_bb_switch='all'):
        """
        The function estimate is the main function of the module. It takes up to three input
        arguments: v_start, delta_start and calculate_voltage_angles. The first two are the initial
        state variables for the estimation process. Usually they can be initialized in a
        "flat-start" condition: All voltages being 1.0 pu and all voltage angles being 0 degrees.
        In this case, the parameters can be left at their default values (None). If the estimation
        is applied continuously, using the results from the last estimation as the starting
        condition for the current estimation can decrease the  amount of iterations needed to
        estimate the current state. The third parameter defines whether all voltage angles are
        calculated absolutely, including phase shifts from transformers. If only the relative
        differences between buses are required, this parameter can be set to False. Returned is a
        boolean value, which is true after a successful estimation and false otherwise.
        The resulting complex voltage will be written into the pandapower network. The result
        fields are found res_bus_est of the pandapower network.

        INPUT:
            **net** - The net within this line should be created

            **v_start** (np.array, shape=(1,), optional) - Vector with initial values for all
            voltage magnitudes in p.u. (sorted by bus index)

            **delta_start** (np.array, shape=(1,), optional) - Vector with initial values for all
            voltage angles in degrees (sorted by bus index)

        OPTIONAL:
            **calculate_voltage_angles** - (bool) - Take into account absolute voltage angles and
            phase shifts in transformers Default is True
            
            **zero_injection** - (str, iterable, None) - Defines which buses are zero injection bus or the method
            to identify zero injection bus, with 'wls_estimator' virtual measurements will be added, with 
            'wls_estimator with zero constraints' the buses will be handled as constraints
            "auto": all bus without p,q measurement, without p, q value (load, sgen...) and aux buses will be
                identified as zero injection bus  
            "aux_bus": only aux bus will be identified as zero injection bus
            None: no bus will be identified as zero injection bus
            iterable: the iterable should contain index of the zero injection bus and also aux bus will be identified
                as zero-injection bus
    
            **fuse_buses_with_bb_switch** - (str, iterable, None) - Defines how buses with closed bb switches should 
            be handled, if fuse buses will only fused to one for calculation, if not fuse, an auxiliary bus and 
            auxiliary line will be automatically added to the network to make the buses with different p,q injection
            measurements identifieble
            "all": all buses with bb-switches will be fused, the same as the default behaviour in load flow
            None: buses with bb-switches and individual p,q measurements will be reconfigurated
                by auxiliary elements
            iterable: the iterable should contain the index of buses to be fused, the behaviour is contigous e.g.
                if one of the bus among the buses connected through bb switch is given, then all of them will still
                be fused
        OUTPUT:
            **successful** (boolean) - True if the estimation process was successful

        Optional estimation variables:
            The bus power injections can be accessed with *se.s_node_powers* and the estimated
            values corresponding to the (noisy) measurement values with *se.hx*. (*hx* denotes h(x))

        EXAMPLE:
            success = estimate(np.array([1.0, 1.0, 1.0]), np.array([0.0, 0.0, 0.0]))

        """
        if self.net is None:
            raise UserWarning("Component was not initialized with a network.")
        t0 = time()

        # change the configuration of the pp net to avoid auto fusing of buses connected
        # through bb switch with elements on each bus if this feature enabled
        bus_to_be_fused = None
        if fuse_buses_with_bb_switch != 'all' and not self.net.switch.empty:
            if isinstance(fuse_buses_with_bb_switch, str):
                raise UserWarning(
                    "fuse_buses_with_bb_switch parameter is not correctly initialized"
                )
            elif hasattr(fuse_buses_with_bb_switch, '__iter__'):
                bus_to_be_fused = fuse_buses_with_bb_switch
            _add_aux_elements_for_bb_switch(self.net, bus_to_be_fused)

        # add initial values for V and delta
        # node voltages
        # V<delta
        if v_start is None:
            v_start = "flat"
        if delta_start is None:
            delta_start = "flat"

        # initialize result tables if not existent
        _copy_power_flow_results(self.net)

        # initialize ppc
        ppc, ppci = _init_ppc(self.net, v_start, delta_start,
                              calculate_voltage_angles)

        # add measurements to ppci structure
        ppci = _add_measurements_to_ppc(self.net, ppci, zero_injection)

        # Finished converting pandapower network to ppci
        # Estimate voltage magnitude and angle with the given estimator
        delta, v_m = self.estimator.estimate(ppci)

        # store results for all elements
        # calculate bus power injections
        v_cpx = v_m * np.exp(1j * delta)
        bus_powers_conj = np.zeros(len(v_cpx), dtype=np.complex128)
        for i in range(len(v_cpx)):
            bus_powers_conj[i] = np.dot(ppci['internal']['Y_bus'][i, :],
                                        v_cpx) * np.conjugate(v_cpx[i])

        ppci["bus"][:, 2] = bus_powers_conj.real  # saved in per unit
        ppci["bus"][:, 3] = -bus_powers_conj.imag  # saved in per unit
        ppci["bus"][:, 7] = v_m
        ppci["bus"][:, 8] = delta * 180 / np.pi  # convert to degree

        # calculate line results (in ppc_i)
        s_ref, bus, gen, branch = _get_pf_variables_from_ppci(ppci)[0:4]
        out = np.flatnonzero(branch[:,
                                    BR_STATUS] == 0)  # out-of-service branches
        br = np.flatnonzero(branch[:, BR_STATUS]).astype(
            int)  # in-service branches
        # complex power at "from" bus
        Sf = v_cpx[np.real(branch[br, F_BUS]).astype(int)] * np.conj(
            ppci['internal']['Yf'][br, :] * v_cpx) * s_ref
        # complex power injected at "to" bus
        St = v_cpx[np.real(branch[br, T_BUS]).astype(int)] * np.conj(
            ppci['internal']['Yt'][br, :] * v_cpx) * s_ref
        branch[np.ix_(br, [PF, QF, PT, QT])] = np.c_[Sf.real, Sf.imag, St.real,
                                                     St.imag]
        branch[np.ix_(out, [PF, QF, PT, QT])] = np.zeros((len(out), 4))
        et = time() - t0
        ppci = _store_results_from_pf_in_ppci(ppci, bus, gen, branch,
                                              self.estimator.successful,
                                              self.estimator.iterations, et)

        # convert to pandapower indices
        ppc = _copy_results_ppci_to_ppc(ppci, ppc, mode="se")

        # extract results from ppc
        _add_pf_options(self.net,
                        tolerance_mva=1e-8,
                        trafo_loading="current",
                        numba=True,
                        ac=True,
                        algorithm='nr',
                        max_iteration="auto")
        # writes res_bus.vm_pu / va_degree and res_line
        _extract_results_se(self.net, ppc)

        # restore backup of previous results
        _rename_results(self.net)

        # additionally, write bus power injection results (these are not written in _extract_results)
        mapping_table = self.net["_pd2ppc_lookups"]["bus"]
        self.net.res_bus_est.p_mw = -get_values(
            ppc["bus"][:, 2], self.net.bus.index.values, mapping_table)
        self.net.res_bus_est.q_mvar = -get_values(
            ppc["bus"][:, 3], self.net.bus.index.values, mapping_table)
        _clean_up(self.net)
        # clear the aux elements and calculation results created for the substitution of bb switches
        if fuse_buses_with_bb_switch != 'all' and not self.net.switch.empty:
            _drop_aux_elements_for_bb_switch(self.net)

        # delete results which are not correctly calculated
        for k in list(self.net.keys()):
            if k.startswith("res_") and k.endswith("_est") and \
                    k not in ("res_bus_est", "res_line_est", "res_trafo_est", "res_trafo3w_est"):
                del self.net[k]

        return self.estimator.successful