Exemplo n.º 1
0
def _run_bfsw_ppc(ppc, ppopt=None):
    """
    SPARSE version of distribution power flow solution according to [1]
    :References:
    [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003.

    :param ppc: matpower-style case data
    :return: results (pypower style), success (flag about PF convergence)
    """
    ppci = ppc

    ppopt = ppoption(ppopt)

    baseMVA, bus, gen, branch = \
        ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"]
    nbus = bus.shape[0]

    # get bus index lists of each type of bus
    ref, pv, pq = bustypes(bus, gen)

    # depth-first-search bus ordering and generating Direct Load Flow matrix DLF = BCBV * BIBC
    DLF, ppc_bfsw, buses_ordered_bfsw = bibc_bcbv(ppci)


    baseMVA_bfsw, bus_bfsw, gen_bfsw, branch_bfsw = \
        ppc_bfsw["baseMVA"], ppc_bfsw["bus"], ppc_bfsw["gen"], ppc_bfsw["branch"]

    time_start = time() # starting pf calculation timing

    # initialize voltages to flat start and buses with gens to their setpoints
    V0 = np.ones(nbus, dtype=complex)
    V0[gen[:, GEN_BUS].astype(int)] = gen[:, VG]

    Sbus_bfsw = makeSbus(baseMVA_bfsw, bus_bfsw, gen_bfsw)

    # update data matrices with solution
    Ybus_bfsw, Yf_bfsw, Yt_bfsw = makeYbus(baseMVA_bfsw, bus_bfsw, branch_bfsw)
    ## get bus index lists of each type of bus
    ref_bfsw, pv_bfsw, pq_bfsw = bustypes(bus_bfsw, gen_bfsw)

    # #-----  run the power flow  -----
    V_final, success = bfsw(DLF, bus_bfsw, gen_bfsw, branch_bfsw, baseMVA_bfsw, Ybus_bfsw, Sbus_bfsw, V0,
                                   ref_bfsw, pv_bfsw, pq_bfsw, ppopt=ppopt)

    V_final = V_final[np.argsort(buses_ordered_bfsw)]  # return bus voltages in original bus order


    # #----- output results to ppc ------
    ppci["et"] = time() - time_start    # pf time end

    # generate results for original bus ordering
    Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch)

    bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V_final, ref, pv, pq)

    ppci["success"] = success

    ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch

    return ppci, success
Exemplo n.º 2
0
def _get_pf_variables_from_ppci(ppci):
    ## default arguments
    if ppci is None:
        ValueError('ppci is empty')
    # ppopt = ppoption(ppopt)

    # get data for calc
    baseMVA, bus, gen, branch = \
        ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"]

    ## get bus index lists of each type of bus
    ref, pv, pq = bustypes(bus, gen)

    ## generator info
    on = find(gen[:, GEN_STATUS] > 0)  ## which generators are on?
    gbus = gen[on, GEN_BUS].astype(int)  ## what buses are they at?

    ## initial state
    # V0    = ones(bus.shape[0])            ## flat start
    V0 = bus[:, VM] * exp(1j * pi / 180 * bus[:, VA])
    V0[gbus] = gen[on, VG] / abs(V0[gbus]) * V0[gbus]

    return baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, V0
Exemplo n.º 3
0
def _run_ac_pf_with_qlims_enforced(ppci, recycle, makeYbus, ppopt):
    baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, V0 = _get_pf_variables_from_ppci(
        ppci)

    qlim = ppopt["ENFORCE_Q_LIMS"]
    limited = []  ## list of indices of gens @ Q lims
    fixedQg = zeros(gen.shape[0])  ## Qg of gens at Q limits

    while True:
        ppci, success, bus, gen, branch = _run_ac_pf_without_qlims_enforced(
            ppci, recycle, makeYbus, ppopt)

        ## find gens with violated Q constraints
        gen_status = gen[:, GEN_STATUS] > 0
        qg_max_lim = gen[:, QG] > gen[:, QMAX]
        qg_min_lim = gen[:, QG] < gen[:, QMIN]

        mx = find(gen_status & qg_max_lim)
        mn = find(gen_status & qg_min_lim)

        if len(mx) > 0 or len(mn) > 0:  ## we have some Q limit violations
            # No PV generators
            if len(pv) == 0:
                if ppopt["VERBOSE"]:
                    if len(mx) > 0:
                        logger.info(
                            'Gen %d [only one left] exceeds upper Q limit : INFEASIBLE PROBLEM\n'
                            % mx + 1)
                    else:
                        logger.info(
                            'Gen %d [only one left] exceeds lower Q limit : INFEASIBLE PROBLEM\n'
                            % mn + 1)

                success = 0
                break

            ## one at a time?
            if qlim == 2:  ## fix largest violation, ignore the rest
                k = argmax(r_[gen[mx, QG] - gen[mx, QMAX],
                              gen[mn, QMIN] - gen[mn, QG]])
                if k > len(mx):
                    mn = mn[k - len(mx)]
                    mx = []
                else:
                    mx = mx[k]
                    mn = []

            if ppopt["VERBOSE"] and len(mx) > 0:
                for i in range(len(mx)):
                    logger.info('Gen ' + str(mx[i] + 1) +
                                ' at upper Q limit, converting to PQ bus\n')

            if ppopt["VERBOSE"] and len(mn) > 0:
                for i in range(len(mn)):
                    logger.info('Gen ' + str(mn[i] + 1) +
                                ' at lower Q limit, converting to PQ bus\n')

            ## save corresponding limit values
            fixedQg[mx] = gen[mx, QMAX]
            fixedQg[mn] = gen[mn, QMIN]
            mx = r_[mx, mn].astype(int)

            ## convert to PQ bus
            gen[mx, QG] = fixedQg[mx]  ## set Qg to binding
            for i in range(len(
                    mx)):  ## [one at a time, since they may be at same bus]
                gen[mx[i], GEN_STATUS] = 0  ## temporarily turn off gen,
                bi = gen[mx[i],
                         GEN_BUS].astype(int)  ## adjust load accordingly,
                bus[bi, [PD, QD]] = (bus[bi, [PD, QD]] - gen[mx[i], [PG, QG]])

            if len(ref) > 1 and any(bus[gen[mx, GEN_BUS].astype(int),
                                        BUS_TYPE] == REF):
                raise ValueError('Sorry, pandapower cannot enforce Q '
                                 'limits for slack buses in systems '
                                 'with multiple slacks.')

            bus[gen[mx, GEN_BUS].astype(int),
                BUS_TYPE] = PQ  ## & set bus type to PQ

            ## update bus index lists of each type of bus
            ref_temp = ref
            ref, pv, pq = bustypes(bus, gen)
            if ppopt["VERBOSE"] and ref != ref_temp:
                print('Bus %d is new slack bus\n' % ref)

            limited = r_[limited, mx].astype(int)
        else:
            break  ## no more generator Q limits violated

    if len(limited) > 0:
        ## restore injections from limited gens [those at Q limits]
        gen[limited, QG] = fixedQg[limited]  ## restore Qg value,
        for i in range(len(
                limited)):  ## [one at a time, since they may be at same bus]
            bi = gen[limited[i], GEN_BUS].astype(int)  ## re-adjust load,
            bus[bi, [PD, QD]] = bus[bi, [PD, QD]] + gen[limited[i], [PG, QG]]
            gen[limited[i], GEN_STATUS] = 1  ## and turn gen back on

    return ppci, success, bus, gen, branch
Exemplo n.º 4
0
def _run_fbsw_dense(ppc, ppopt=None):
    """
    DENSE version of distribution power flow solution according to [1]
    :References:
    [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003.

    :param ppc: matpower-style case data
    :return: results (pypower style), success (flag about PF convergence)
    """
    # time_start = time()

    # path_search = nx.shortest_path

    # ppci = ext2int(ppc)
    ppci = ppc

    ppopt = ppoption(ppopt)

    baseMVA, bus, gen, branch = \
        ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"]
    nbus = bus.shape[0]

    # get bus index lists of each type of bus
    ref, pv, pq = bustypes(bus, gen)

    root_bus = ref[
        0]  # reference bus is assumed as root bus for a radial network

    DLF, ppc_fbsw, buses_ordered_fbsw = bibc_bcbv_dense(ppci)

    baseMVA_fbsw, bus_fbsw, gen_fbsw, branch_fbsw = \
        ppc_fbsw["baseMVA"], ppc_fbsw["bus"], ppc_fbsw["gen"], ppc_fbsw["branch"]

    time_start = time()

    # initialize voltages to flat start and buses with gens to their setpoints
    V0 = np.ones(nbus, dtype=complex)
    V0[gen[:, GEN_BUS].astype(int)] = gen[:, VG]

    Sbus_fbsw = makeSbus(baseMVA_fbsw, bus_fbsw, gen_fbsw)

    # update data matrices with solution
    Ybus_fbsw, Yf_fbsw, Yt_fbsw = makeYbus(baseMVA_fbsw, bus_fbsw, branch_fbsw)
    ## get bus index lists of each type of bus
    ref_fbsw, pv_fbsw, pq_fbsw = bustypes(bus_fbsw, gen_fbsw)

    ##-----  run the power flow  -----

    # ###
    # LF initialization and calculation
    V_final, success = fbsw_dense(DLF,
                                  bus_fbsw,
                                  gen_fbsw,
                                  branch_fbsw,
                                  baseMVA_fbsw,
                                  Ybus_fbsw,
                                  Sbus_fbsw,
                                  V0,
                                  ref_fbsw,
                                  pv_fbsw,
                                  pq_fbsw,
                                  ppopt=ppopt)
    V_final = V_final[np.argsort(
        buses_ordered_fbsw)]  # return bus voltages in original bus order

    ppci["et"] = time() - time_start

    Sbus = makeSbus(baseMVA, bus, gen)
    # update data matrices with solution
    Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch)

    bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V_final,
                              ref, pv, pq)

    ppci["success"] = success

    ##-----  output results  -----
    ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch
    results = ppci

    return results, success
Exemplo n.º 5
0
def bibc_bcbv(ppc):
    """
    performs depth-first-search bus ordering and creates Direct Load Flow (DLF) matrix
    which establishes direct relation between bus current injections and voltage drops from each bus to the root bus

    :param ppc: matpower-type case data
    :return: DLF matrix DLF = BIBC * BCBV where
                    BIBC - Bus Injection to Branch-Current
                    BCBV - Branch-Current to Bus-Voltage
            ppc with bfs ordering
            original bus names bfs ordered (used to convert voltage array back to normal)
    """

    ppci = ppc
    baseMVA, bus, gen, branch = \
        ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"]
    nbus = bus.shape[0]

    # get bus index lists of each type of bus
    ref, pv, pq = bustypes(bus, gen)

    root_bus = ref[
        0]  # reference bus is assumed as root bus for a radial network

    branches = branch[:, F_BUS:T_BUS + 1].real.astype(int)

    # creating networkx graph from list of branches
    G = nx.Graph()
    G.add_edges_from(branches)

    # ordering buses according to breadth-first-search (bfs)
    edges_ordered_bfs = list(nx.bfs_edges(G, root_bus))
    indices = np.unique(np.array(edges_ordered_bfs).flatten(),
                        return_index=True)[1]
    buses_ordered_bfs = np.array(edges_ordered_bfs).flatten()[sorted(indices)]
    buses_bfs_dict = dict(zip(buses_ordered_bfs,
                              range(0,
                                    nbus)))  # old to new bus names dictionary
    # renaming buses in graph and in ppc
    G = nx.relabel_nodes(G, buses_bfs_dict)
    root_bus = buses_bfs_dict[root_bus]
    ppc_bfs = bus_reindex(ppci, buses_bfs_dict)
    # ordered list of branches
    branches_ord = zip(ppc_bfs['branch'][:, F_BUS].real.astype(int),
                       ppc_bfs['branch'][:, T_BUS].real.astype(int))

    # searching loops in the graph if it is not a tree
    loops = []
    branches_loops = []
    if not nx.is_tree(G):  # network is meshed, i.e. has loops
        G_bfs_tree = nx.bfs_tree(G, root_bus)
        branches_loops = list(set(G.edges()) - set(G_bfs_tree.edges()))
        G.remove_edges_from(branches_loops)
        # finding loops
        for i, j in branches_loops:
            G.add_edge(i, j)
            loops.append(nx.find_cycle(G))
            G.remove_edge(i, j)

    nloops = len(loops)
    nbr_rad = len(G.edges())  # number of edges in the radial network

    # searching leaves of the tree
    succ = nx.bfs_successors(G, root_bus)
    leaves = set(G.nodes()) - set(succ.keys())

    # dictionary with impedance values keyed by branch tuple (frombus, tobus)
    Z_brch_dict = dict(
        zip(
            branches_ord, ppc_bfs['branch'][:, BR_R].real +
            1j * ppc_bfs['branch'][:, BR_X].real))

    # #------ building BIBC and BCBV martrices ------

    # order branches for BIBC and BCBV matrices and set loop-closing branches to the end
    branches_ord_radial = list(branches_ord)
    for brch in branches_loops:  # TODO eliminated this for loop
        branches_ord_radial.remove(brch)
    branches_ind_dict = dict(zip(branches_ord_radial, range(0, nbr_rad)))
    branches_ind_dict.update(
        dict(zip(branches_loops,
                 range(nbr_rad,
                       nbr_rad + nloops))))  # add loop-closing branches

    rowi_BIBC = []
    coli_BIBC = []
    data_BIBC = []
    data_BCBV = []
    for bri, (i, j) in enumerate(branches_ord_radial):
        G.remove_edge(i, j)
        buses_down = set()
        for leaf in leaves:
            try:
                buses_down.update(nx.shortest_path(G, leaf, j))
            except:
                pass
        rowi_BIBC += [bri] * len(buses_down)
        coli_BIBC += list(buses_down)
        data_BCBV += [Z_brch_dict[(i, j)]] * len(buses_down)
        data_BIBC += [1] * len(buses_down)
        G.add_edge(i, j)

    for loop_i, loop in enumerate(loops):
        loop_size = len(loop)
        coli_BIBC += [nbus + loop_i] * loop_size
        for brch in loop:
            if brch[0] < brch[1]:
                i, j = brch
                brch_direct = 1
                data_BIBC.append(brch_direct)
            else:
                j, i = brch
                brch_direct = -1
                data_BIBC.append(brch_direct)
            rowi_BIBC.append(branches_ind_dict[(i, j)])

            data_BCBV.append(Z_brch_dict[(i, j)] * brch_direct)

    # construction of the BIBC matrix
    # column indices correspond to buses: assuming root bus is always 0 after ordering indices are subtracted by 1
    BIBC = csr_matrix((data_BIBC, (rowi_BIBC, np.array(coli_BIBC) - 1)),
                      shape=(nbus - 1 + nloops, nbus - 1 + nloops))
    BCBV = csr_matrix(
        (data_BCBV, (rowi_BIBC, np.array(coli_BIBC) - 1)),
        shape=(nbus - 1 + nloops, nbus - 1 + nloops)).transpose()

    if BCBV.shape[0] > nbus - 1:  # if nbrch > nbus - 1 -> network has loops
        DLF_loop = BCBV * BIBC
        # DLF = [A  M.T ]
        #       [M  N   ]
        A = DLF_loop[0:nbus - 1, 0:nbus - 1]
        M = DLF_loop[nbus - 1:, 0:nbus - 1]
        N = DLF_loop[nbus - 1:, nbus - 1:].A
        # considering the fact that number of loops is relatively small, N matrix is expected to be small and dense
        # ...in that case dense version is more efficient, i.e. N is transformed to dense and
        # inverted using sp.linalg.inv(N)
        DLF = A - M.T * csr_matrix(sp.linalg.inv(N)) * M  # Kron's Reduction
    else:  # no loops -> radial network
        DLF = BCBV * BIBC

    return DLF, ppc_bfs, buses_ordered_bfs
Exemplo n.º 6
0
def bibc_bcbv_dense(ppc):
    """
    creates 2 matrices required for direct LF:
        BIBC - Bus Injection to Branch-Current
        BCBV - Branch-Current to Bus-Voltage
    :param ppc:
    matpower-type power system dictionary
    :return: DLF matrix DLF = BIBC * BCBV in a dense form
    """
    ppci = ppc
    baseMVA, bus, gen, branch = \
        ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"]
    nbus = bus.shape[0]
    nbrch = branch.shape[0]

    # get bus index lists of each type of bus
    ref, pv, pq = bustypes(bus, gen)

    root_bus = ref[
        0]  # reference bus is assumed as root bus for a radial network

    branches = branch[:, F_BUS:T_BUS + 1].real.astype(int)

    # power system graph created from list of branches as a dictionary
    system_graph = graph_dict(branches)

    # ###
    # bus ordering according to BFS search

    # TODO check if bfs bus ordering is necessary for the direct power flow
    buses_ordered_bfs, edges_ordered_bfs, branches_loops = bfs_edges(
        system_graph, root_bus)
    buses_bfs_dict = dict(zip(buses_ordered_bfs,
                              range(0, nbus)))  # old to new bus names
    ppc_bfs = bus_reindex(ppci, buses_bfs_dict)

    branches_loops_bfs = []
    if len(branches_loops) > 0:
        print(
            "  {0} loops detected\n  following branches are cut to obtain radial network: {1}"
            .format(len(branches_loops), branches_loops))
        branches_loops_bfs = [(buses_bfs_dict[fbus], buses_bfs_dict[tbus])
                              for fbus, tbus in branches_loops]
        branches_loops_bfs = np.sort(branches_loops_bfs,
                                     axis=1)  # sort in order to T_BUS > F_BUS
        branches_loops_bfs = list(
            zip(branches_loops_bfs[:, 0], branches_loops_bfs[:, 1]))

    buses = ppc_bfs['bus'][:, BUS_I]
    branches_bfs = zip(ppc_bfs['branch'][:, F_BUS], ppc_bfs['branch'][:,
                                                                      T_BUS])

    nbus = buses.shape[0]

    mask_root = ~(ppc_bfs['bus'][:, BUS_TYPE] == 3)
    root_bus = buses[~mask_root][0]

    # dictionaries with bus/branch indices
    bus_ind_dict = dict(zip(buses[mask_root],
                            range(nbus - 1)))  # dictionary bus_name-> bus_ind
    bus_ind_dict[root_bus] = -1
    # dictionary brch_name-> brch_ind
    brch_ind_dict = dict(zip(branches_bfs, range(nbrch)))

    # list of branches in the radial network, i.e. without branches that close loops
    branches_radial = list(branches_bfs)
    for brch_l in branches_loops_bfs:
        branches_radial.remove(brch_l)

    # reorder branches so that loop-forming branches are at the end

    Z_brchs = []
    nloops = len(branches_loops_bfs)

    BIBC = np.zeros((nbrch - nloops, nbus - 1))
    BCBV = np.zeros((nbus - 1, nbrch - nloops), dtype=complex)

    for br_i, branch in enumerate(branches_radial):
        bus_f = branch[0]
        bus_t = branch[1]
        bus_f_i = bus_ind_dict[bus_f]
        bus_t_i = bus_ind_dict[bus_t]
        brch_ind = brch_ind_dict[branch]
        if bus_f != root_bus and bus_t != root_bus:
            BIBC[:, bus_t_i] = BIBC[:, bus_f_i].copy()
            BCBV[bus_t_i, :] = BCBV[bus_f_i, :].copy()

        if branch[T_BUS] != root_bus:
            BIBC[br_i, bus_t_i] += 1
            Z = (ppc_bfs['branch'][brch_ind, BR_R] +
                 1j * ppc_bfs['branch'][brch_ind, BR_X])
            BCBV[bus_t_i, br_i] = Z
            Z_brchs.append(Z)

    for br_i, branch in enumerate(branches_loops_bfs):
        bus_f = branch[0]
        bus_t = branch[1]
        bus_f_i = bus_ind_dict[bus_f]
        bus_t_i = bus_ind_dict[bus_t]
        brch_ind = brch_ind_dict[branch]

        BIBC = np.vstack((BIBC, np.zeros(BIBC.shape[1])))  # add row
        BIBC = np.hstack((BIBC, np.zeros((BIBC.shape[0], 1))))  # add column
        last_col_i = BIBC.shape[1] - 1  # last column index
        BIBC[:, last_col_i] = BIBC[:, bus_f_i] - BIBC[:, bus_t_i]
        BIBC[last_col_i, last_col_i] += 1

        BCBV = np.vstack((BCBV, np.zeros(BCBV.shape[1])))  # add row
        BCBV = np.hstack((BCBV, np.zeros((BCBV.shape[0], 1))))  # add column
        Z = (ppc_bfs['branch'][brch_ind, BR_R] +
             1j * ppc_bfs['branch'][brch_ind, BR_X])
        Z_brchs.append(Z)
        BCBV[BCBV.shape[0] -
             1, :] = np.array(Z_brchs) * BIBC[:,
                                              last_col_i]  # KVL for the loop

    if BCBV.shape[0] > nbus - 1:  # if nbrch > nbus - 1 -> network has loops
        DLF_loop = sp.dot(BCBV, BIBC)
        A = DLF_loop[0:nbus - 1, 0:nbus - 1]
        M = DLF_loop[nbus - 1:, 0:nbus - 1]
        N = DLF_loop[nbus - 1:, nbus - 1:]
        # TODO: use more efficient inversion !?
        DLF = A - sp.dot(sp.dot(M.T, sp.linalg.inv(N)), M)  # Krons Reduction
    else:  # no loops -> radial network
        DLF = sp.dot(BCBV, BIBC)

    return DLF, ppc_bfs, buses_ordered_bfs
Exemplo n.º 7
0
def _run_bfswpf(ppc, options, **kwargs):
    """
    SPARSE version of distribution power flow solution according to [1]
    :References:
    [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003.

    :param ppc: matpower-style case data
    :return: results (pypower style), success (flag about PF convergence)
    """
    time_start = time()  # starting pf calculation timing

    tap_shift = ppc['branch'][:, SHIFT].real

    enforce_q_lims, tolerance_kva, max_iteration, calculate_voltage_angles, numba = _get_options(
        options)

    numba, makeYbus = _import_numba_extensions_if_flag_is_true(numba)

    ppci = ppc

    baseMVA, bus, gen, branch = \
        ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"]
    nbus = bus.shape[0]
    # generate results for original bus ordering
    Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch)

    # get bus index lists of each type of bus
    ref, pv, pq = bustypes(bus, gen)

    # creating networkx graph from list of branches
    G = nx.Graph()
    G.add_edges_from((int(fb), int(tb), {
        "shift": float(shift)
    }) for fb, tb, shift in list(
        zip(branch[:, F_BUS].real, branch[:, T_BUS].real, tap_shift)))
    if not nx.is_connected(G):
        Graphs = list(nx.connected_component_subgraphs(G))
    else:
        Graphs = [G]

    V_final = np.zeros(nbus, dtype=complex)
    V_tapshifts = np.zeros(nbus)
    for subi, G in enumerate(Graphs):

        ppci_sub = _cut_ppc(ppci, G.nodes())
        nbus_sub = len(G)

        # depth-first-search bus ordering and generating Direct Load Flow matrix DLF = BCBV * BIBC
        DLF, ppc_bfsw, buses_ordered_bfsw = _bibc_bcbv(ppci_sub, G)
        ppc_bfsw['branch'][:, SHIFT] = 0

        baseMVA_bfsw, bus_bfsw, gen_bfsw, branch_bfsw, ref_bfsw, pv_bfsw, pq_bfsw,\
        on, gbus, V0 = _get_pf_variables_from_ppci(ppc_bfsw)

        Sbus_bfsw = makeSbus(baseMVA_bfsw, bus_bfsw, gen_bfsw)

        Ybus_bfsw, Yf_bfsw, Yt_bfsw = makeYbus(baseMVA_bfsw, bus_bfsw,
                                               branch_bfsw)

        # #-----  run the power flow  -----
        V_res, success = _bfswpf(DLF, bus_bfsw, gen_bfsw, branch_bfsw, baseMVA,
                                 Ybus_bfsw, buses_ordered_bfsw, Sbus_bfsw, V0,
                                 ref_bfsw, pv_bfsw, pq_bfsw, enforce_q_lims,
                                 tolerance_kva, max_iteration, **kwargs)

        V_final[
            buses_ordered_bfsw] = V_res  # return bus voltages in original bus order
        # TODO: find the better way to consider transformer phase shift and remove this workaround
        if calculate_voltage_angles:
            predecessors = nx.bfs_predecessors(G, ref[subi])
            branches = list(zip(branch[:, F_BUS].real, branch[:, T_BUS].real))
            for bus_start in predecessors.iterkeys():
                bus_pred = bus_start
                bus_next = bus_start
                while predecessors.get(bus_next) is not None:
                    bus_next = predecessors.get(bus_pred)
                    shift_angle = G.get_edge_data(bus_pred, bus_next)['shift']
                    if (bus_pred, bus_next) in branches:
                        V_tapshifts[bus_start] += shift_angle
                    else:
                        V_tapshifts[bus_start] -= shift_angle
                    bus_pred = bus_next

            V_final *= np.exp(1j * np.pi / 180 * V_tapshifts)

    # #----- output results to ppc ------
    ppci["et"] = time() - time_start  # pf time end

    bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V_final,
                              ref, pv, pq)

    ppci["success"] = success

    ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch

    return ppci, success
Exemplo n.º 8
0
def _bfswpf(DLF, bus, gen, branch, baseMVA, Ybus, bus_ord_bfsw, Sbus, V0, ref,
            pv, pq, enforce_q_lims, tolerance_kva, max_iteration, **kwargs):
    """
    distribution power flow solution according to [1]
    :param DLF: direct-Load-Flow matrix which relates bus current injections to voltage drops from the root bus

    :param bus: buses martix
    :param gen: generators matrix
    :param branch: branches matrix
    :param baseMVA:
    :param Ybus: bus admittance matrix
    :param Sbus: vector of power injections
    :param V0: initial voltage state vector
    :param ref: reference bus index
    :param pv: PV buses indices
    :param pq: PQ buses indices

    :return: power flow result

    :References:
    [1] Jen-Hao Teng, "A Direct Approach for Distribution System Load Flow Solutions", IEEE Transactions on Power Delivery, vol. 18, no. 3, pp. 882-887, July 2003.
    """
    # setting options
    tolerance_mva = tolerance_kva * 1e-3
    max_it = max_iteration  # maximum iterations
    verbose = kwargs["VERBOSE"]  # verbose is set in run._runpppf() #
    # tolerance for the inner loop for PV nodes
    if 'tolerance_kva_pv' in kwargs:
        tol_mva_inner = kwargs['tolerance_kva_pv'] * 1e-3
    else:
        tol_mva_inner = 1.e-2

    if 'max_iter_pv' in kwargs:
        max_iter_pv = kwargs['max_iter_pv']
    else:
        max_iter_pv = 20

    nbus = bus.shape[0]
    ngen = gen.shape[0]

    mask_root = ~(bus[:, BUS_TYPE] == 3)  # mask for eliminating root bus
    root_bus_i = ref
    Vref = V0[ref]

    # bus_ind_mask_dict = dict(zip(bus[mask_root, BUS_I], range(nbus - 1)))
    bus_ord_bfsw_inv = np.argsort(bus_ord_bfsw)

    # compute shunt admittance
    # if Psh is the real power consumed by the shunt at V = 1.0 p.u. and Qsh is the reactive power injected by
    # the shunt at V = 1.0 p.u. then Psh - j Qsh = V * conj(Ysh * V) = conj(Ysh) = Gs - j Bs,
    # vector of shunt admittances
    Ysh = (bus[:, GS] + 1j * bus[:, BS]) / baseMVA

    # Line charging susceptance BR_B is also added as shunt admittance:
    # summation of charging susceptances per each bus

    stat = branch[:, BR_STATUS]  ## ones at in-service branches
    Ys = stat / (branch[:, BR_R] + 1j * branch[:, BR_X])
    ysh = (-branch[:, BR_B].imag + 1j * (branch[:, BR_B].real)) / 2
    tap = branch[:, TAP]  # * np.exp(1j * np.pi / 180 * branch[:, SHIFT])

    ysh_f = Ys * (1 - tap) / (tap * np.conj(tap)) + ysh / (tap * np.conj(tap))
    ysh_t = Ys * (tap - 1) / tap + ysh

    Gch = (np.bincount(
        branch[:, F_BUS].real.astype(int), weights=ysh_f.real, minlength=nbus)
           + np.bincount(branch[:, T_BUS].real.astype(int),
                         weights=ysh_t.real,
                         minlength=nbus))
    Bch = (np.bincount(
        branch[:, F_BUS].real.astype(int), weights=ysh_f.imag, minlength=nbus)
           + np.bincount(branch[:, T_BUS].real.astype(int),
                         weights=ysh_t.imag,
                         minlength=nbus))

    Ysh += Gch + 1j * Bch

    # Gch_f = - np.bincount(branch[:, F_BUS].real.astype(int), weights=branch[:, BR_B].imag / 2, minlength=nbus)
    # Bch_f = np.bincount(branch[:, F_BUS].real.astype(int), weights=branch[:, BR_B].real / 2, minlength=nbus)
    # Gch_t = - np.bincount(branch[:, T_BUS].real.astype(int), weights=branch[:, BR_B].imag / 2, minlength=nbus)
    # Bch_t = np.bincount(branch[:, T_BUS].real.astype(int), weights=branch[:, BR_B].real / 2, minlength=nbus)

    # Ysh += (Gch_f + Gch_t) + 1j * (Bch_f + Bch_t)  # adding line charging to shunt impedance vector

    # detect generators on PV buses which have status ON
    gen_pv = np.in1d(gen[:, GEN_BUS], pv) & (gen[:, GEN_STATUS] > 0)
    qg_lim = np.zeros(
        ngen, dtype=bool)  #initialize generators which violated Q limits

    V_iter = V0[mask_root].copy()  # initial voltage vector without root bus
    V = V0.copy()
    Iinj = np.conj(Sbus / V) - Ysh * V  # Initial current injections

    n_iter = 0
    converged = 0

    if verbose:
        print(' -- AC Power Flow Backward/Forward sweep\n')

    while not converged and n_iter < max_it:
        n_iter_inner = 0
        n_iter += 1

        deltaV = DLF * Iinj[mask_root]
        V_iter = np.ones(nbus - 1) * Vref + deltaV
        # ##
        # inner loop for considering PV buses
        inner_loop_converged = False

        while not inner_loop_converged and len(pv) > 0:

            pvi = pv - 1  # internal PV buses indices, assuming reference node is always 0

            Vmis = (np.abs(gen[gen_pv, VG]))**2 - (np.abs(V_iter[pvi]))**2
            dQ = (Vmis / (2 * DLF[pvi, pvi].A1.imag)).flatten()

            gen[gen_pv, QG] += dQ

            if enforce_q_lims:  #check Q violation limits
                ## find gens with violated Q constraints
                qg_max_lim = (gen[:, QG] > gen[:, QMAX]) & gen_pv
                qg_min_lim = (gen[:, QG] < gen[:, QMIN]) & gen_pv

                if qg_min_lim.any():
                    gen[qg_min_lim, QG] = gen[qg_min_lim, QMIN]
                    bus[gen[qg_min_lim, GEN_BUS].astype(int),
                        BUS_TYPE] = 1  # convert to PQ bus

                if qg_max_lim.any():
                    gen[qg_max_lim, QG] = gen[qg_max_lim, QMAX]
                    bus[gen[qg_max_lim, GEN_BUS].astype(int),
                        BUS_TYPE] = 1  # convert to PQ bus

                # TODO: correct: once all the PV buses are converted to PQ buses, conversion back to PV is not possible
                qg_lim_new = qg_min_lim | qg_max_lim
                if qg_lim_new.any():
                    pq2pv = (qg_lim != qg_lim_new) & qg_lim
                    # convert PQ to PV bus
                    if pq2pv.any():
                        bus[gen[qg_max_lim, GEN_BUS].astype(int),
                            BUS_TYPE] = 2  # convert to PV bus

                    qg_lim = qg_lim_new.copy()
                    ref, pv, pq = bustypes(bus, gen)

            Sbus = makeSbus(baseMVA, bus, gen)
            V = np.insert(V_iter, root_bus_i, Vref)
            Iinj = np.conj(Sbus / V) - Ysh * V
            deltaV = DLF * Iinj[mask_root]
            V_iter = np.ones(nbus - 1) * V0[root_bus_i] + deltaV

            if n_iter_inner > max_iter_pv:
                raise LoadflowNotConverged(
                    " FBSW Power Flow did not converge - inner iterations for PV nodes "
                    "reached maximum value of {0}!".format(max_iter_pv))

            n_iter_inner += 1

            if np.all(np.abs(dQ) < tol_mva_inner
                      ):  # inner loop termination criterion
                inner_loop_converged = True

        # testing termination criterion -
        V = np.insert(V_iter, root_bus_i, Vref)
        mis = V * np.conj(Ybus * V) - Sbus
        F = np.r_[mis[pv].real, mis[pq].real, mis[pq].imag]

        # check tolerance
        normF = np.linalg.norm(F, np.Inf)
        if normF < tolerance_mva:
            converged = 1
            if verbose:
                print("\nFwd-back sweep power flow converged in "
                      "{0} iterations.\n".format(n_iter))
        elif n_iter == max_it:
            raise LoadflowNotConverged(
                " FBSW Power Flow did not converge - "
                "reached maximum iterations = {0}!".format(max_it))

        # updating injected currents
        Iinj = np.conj(Sbus / V) - Ysh * V

    return V, converged
Exemplo n.º 9
0
def _runpf(casedata=None, init='flat', ac=True, Numba=True, ppopt=None):
    """Runs a power flow.

    Similar to runpf() from pypower. See Pypower documentation for more information.

    Changes by University of Kassel (Florian Schaefer):
        Numba can be used for pf calculations.
        Changes in structure (AC as well as DC PF can be calculated)
    """

    ## default arguments
    if casedata is None:
        casedata = join(dirname(__file__), 'case9')
    ppopt = ppoption(ppopt)

    ## options
    verbose = ppopt["VERBOSE"]

    ## read data
    ppci = loadcase(casedata)

    # get data for calc
    baseMVA, bus, gen, branch = \
        ppci["baseMVA"], ppci["bus"], ppci["gen"], ppci["branch"]

    ## get bus index lists of each type of bus
    ref, pv, pq = bustypes(bus, gen)

    ## generator info
    on = find(gen[:, GEN_STATUS] > 0)  ## which generators are on?
    gbus = gen[on, GEN_BUS].astype(int)  ## what buses are they at?

    ##-----  run the power flow  -----
    t0 = time()

    if not ac or (ac and init == 'dc'):  # DC formulation
        if verbose:
            print(' -- DC Power Flow\n')

        ## initial state
        Va0 = bus[:, VA] * (pi / 180)

        ## build B matrices and phase shift injections
        B, Bf, Pbusinj, Pfinj = makeBdc(baseMVA, bus, branch)

        ## compute complex bus power injections [generation - load]
        ## adjusted for phase shifters and real shunts
        Pbus = makeSbus(baseMVA, bus, gen) - Pbusinj - bus[:, GS] / baseMVA

        ## "run" the power flow
        Va = dcpf(B, Pbus, Va0, ref, pv, pq)

        ## update data matrices with solution
        branch[:, [QF, QT]] = zeros((branch.shape[0], 2))
        branch[:, PF] = (Bf * Va + Pfinj) * baseMVA
        branch[:, PT] = -branch[:, PF]
        bus[:, VM] = ones(bus.shape[0])
        bus[:, VA] = Va * (180 / pi)
        ## update Pg for slack generator (1st gen at ref bus)
        ## (note: other gens at ref bus are accounted for in Pbus)
        ##      Pg = Pinj + Pload + Gs
        ##      newPg = oldPg + newPinj - oldPinj

        refgen = zeros(len(ref), dtype=int)
        for k in range(len(ref)):
            temp = find(gbus == ref[k])
            refgen[k] = on[temp[0]]
        gen[refgen,
            PG] = gen[refgen, PG] + (B[ref, :] * Va - Pbus[ref]) * baseMVA
        success = 1

        if ac and init == 'dc':
            # get results from DC powerflow for AC powerflow
            ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch

    if ac:  ## AC formulation
        # options
        qlim = ppopt["ENFORCE_Q_LIMS"]  ## enforce Q limits on gens?

        ## check if numba is available and the corresponding flag
        try:
            from numba import _version as nb_version

            # get Numba Version (in order to use it it must be > 0.25)
            nbVersion = float(nb_version.version_version[:4])

            if nbVersion < 0.25:
                print(
                    'Warning: Numba version too old -> Upgrade to a version > 0.25. Numba is disabled\n'
                )
                Numba = False

        except ImportError:
            # raise UserWarning('Numba cannot be imported. Call runpp() with Numba=False!')
            print(
                'Warning: Numba cannot be imported. Numba is disabled. Call runpp() with Numba=False!\n'
            )
            Numba = False

        if Numba:
            from pandapower.pypower_extensions.makeYbus import makeYbus
        else:
            from pypower.makeYbus import makeYbus

        alg = ppopt['PF_ALG']
        if verbose > 0:
            if alg == 1:
                solver = 'Newton'
            elif alg == 2:
                solver = 'fast-decoupled, XB'
            elif alg == 3:
                solver = 'fast-decoupled, BX'
            elif alg == 4:
                solver = 'Gauss-Seidel'
            else:
                solver = 'unknown'
            print(' -- AC Power Flow (%s)\n' % solver)

        ## initial state
        # V0    = ones(bus.shape[0])            ## flat start
        V0 = bus[:, VM] * exp(1j * pi / 180 * bus[:, VA])
        V0[gbus] = gen[on, VG] / abs(V0[gbus]) * V0[gbus]

        if qlim:
            ref0 = ref  ## save index and angle of
            Varef0 = bus[ref0, VA]  ##   original reference bus(es)
            limited = []  ## list of indices of gens @ Q lims
            fixedQg = zeros(gen.shape[0])  ## Qg of gens at Q limits

        repeat = True
        while repeat:
            ## build admittance matrices
            Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch)

            ## compute complex bus power injections [generation - load]
            Sbus = makeSbus(baseMVA, bus, gen)

            ## run the power flow
            alg = ppopt["PF_ALG"]
            if alg == 1:
                V, success, _ = newtonpf(Ybus, Sbus, V0, ref, pv, pq, ppopt,
                                         Numba)
            elif alg == 2 or alg == 3:
                Bp, Bpp = makeB(baseMVA, bus, branch, alg)
                V, success, _ = fdpf(Ybus, Sbus, V0, Bp, Bpp, ref, pv, pq,
                                     ppopt)
            elif alg == 4:
                V, success, _ = gausspf(Ybus, Sbus, V0, ref, pv, pq, ppopt)
            else:
                raise ValueError(
                    'Only Newton'
                    's method, fast-decoupled, and '
                    'Gauss-Seidel power flow algorithms currently '
                    'implemented.\n')

            ## update data matrices with solution
            bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt,
                                      V, ref, pv, pq)

            if qlim:  ## enforce generator Q limits
                ## find gens with violated Q constraints
                gen_status = gen[:, GEN_STATUS] > 0
                qg_max_lim = gen[:, QG] > gen[:, QMAX]
                qg_min_lim = gen[:, QG] < gen[:, QMIN]

                mx = find(gen_status & qg_max_lim)
                mn = find(gen_status & qg_min_lim)

                if len(mx) > 0 or len(
                        mn) > 0:  ## we have some Q limit violations
                    # No PV generators
                    if len(pv) == 0:
                        if verbose:
                            if len(mx) > 0:
                                print(
                                    'Gen %d [only one left] exceeds upper Q limit : INFEASIBLE PROBLEM\n'
                                    % mx + 1)
                            else:
                                print(
                                    'Gen %d [only one left] exceeds lower Q limit : INFEASIBLE PROBLEM\n'
                                    % mn + 1)

                        success = 0
                        break

                    ## one at a time?
                    if qlim == 2:  ## fix largest violation, ignore the rest
                        k = argmax(r_[gen[mx, QG] - gen[mx, QMAX],
                                      gen[mn, QMIN] - gen[mn, QG]])
                        if k > len(mx):
                            mn = mn[k - len(mx)]
                            mx = []
                        else:
                            mx = mx[k]
                            mn = []

                    if verbose and len(mx) > 0:
                        for i in range(len(mx)):
                            print('Gen ' + str(mx[i] + 1) +
                                  ' at upper Q limit, converting to PQ bus\n')

                    if verbose and len(mn) > 0:
                        for i in range(len(mn)):
                            print('Gen ' + str(mn[i] + 1) +
                                  ' at lower Q limit, converting to PQ bus\n')

                    ## save corresponding limit values
                    fixedQg[mx] = gen[mx, QMAX]
                    fixedQg[mn] = gen[mn, QMIN]
                    mx = r_[mx, mn].astype(int)

                    ## convert to PQ bus
                    gen[mx, QG] = fixedQg[mx]  ## set Qg to binding
                    for i in range(
                            len(mx)
                    ):  ## [one at a time, since they may be at same bus]
                        gen[mx[i],
                            GEN_STATUS] = 0  ## temporarily turn off gen,
                        bi = gen[mx[i], GEN_BUS]  ## adjust load accordingly,
                        bus[bi, [PD, QD]] = (bus[bi, [PD, QD]] -
                                             gen[mx[i], [PG, QG]])

                    if len(ref) > 1 and any(bus[gen[mx, GEN_BUS].astype(int),
                                                BUS_TYPE] == REF):
                        raise ValueError('Sorry, PYPOWER cannot enforce Q '
                                         'limits for slack buses in systems '
                                         'with multiple slacks.')

                    bus[gen[mx, GEN_BUS].astype(int),
                        BUS_TYPE] = PQ  ## & set bus type to PQ

                    ## update bus index lists of each type of bus
                    ref_temp = ref
                    ref, pv, pq = bustypes(bus, gen)
                    if verbose and ref != ref_temp:
                        print('Bus %d is new slack bus\n' % ref)

                    limited = r_[limited, mx].astype(int)
                else:
                    repeat = 0  ## no more generator Q limits violated
            else:
                repeat = 0  ## don't enforce generator Q limits, once is enough

        if qlim and len(limited) > 0:
            ## restore injections from limited gens [those at Q limits]
            gen[limited, QG] = fixedQg[limited]  ## restore Qg value,
            for i in range(
                    len(limited
                        )):  ## [one at a time, since they may be at same bus]
                bi = gen[limited[i], GEN_BUS]  ## re-adjust load,
                bus[bi,
                    [PD, QD]] = bus[bi, [PD, QD]] + gen[limited[i], [PG, QG]]
                gen[limited[i], GEN_STATUS] = 1  ## and turn gen back on

                #            if ref != ref0:
                #                ## adjust voltage angles to make original ref bus correct
                #                bus[:, VA] = bus[:, VA] - bus[ref0, VA] + Varef0

    ppci["et"] = time() - t0
    ppci["success"] = success

    ##-----  output results  -----
    ppci["bus"], ppci["gen"], ppci["branch"] = bus, gen, branch
    results = ppci

    return results, success
Exemplo n.º 10
0
def _bfswpf(DLF, bus, gen, branch, baseMVA, Ybus, Sbus, V0, ref, pv, pq,
            buses_ordered_bfs_nets, enforce_q_lims, tolerance_kva,
            max_iteration, **kwargs):
    """
    distribution power flow solution according to [1]
    :param DLF: direct-Load-Flow matrix which relates bus current injections to voltage drops from the root bus

    :param bus: buses martix
    :param gen: generators matrix
    :param branch: branches matrix
    :param baseMVA:
    :param Ybus: bus admittance matrix
    :param Sbus: vector of power injections
    :param V0: initial voltage state vector
    :param ref: reference bus index
    :param pv: PV buses indices
    :param pq: PQ buses indices

    :return: power flow result
    """

    # setting options
    tolerance_mva = tolerance_kva * 1e-3
    max_it = max_iteration  # maximum iterations
    verbose = kwargs["VERBOSE"]  # verbose is set in run._runpppf() #

    # tolerance for the inner loop for PV nodes
    if 'tolerance_kva_pv' in kwargs:
        tol_mva_inner = kwargs['tolerance_kva_pv'] * 1e-3
    else:
        tol_mva_inner = 1.e-2

    if 'max_iter_pv' in kwargs:
        max_iter_pv = kwargs['max_iter_pv']
    else:
        max_iter_pv = 20

    nobus = bus.shape[0]
    ngen = gen.shape[0]

    mask_root = ~(bus[:, BUS_TYPE] == 3)  # mask for eliminating root bus
    norefs = len(ref)

    # compute shunt admittance
    # if Psh is the real power consumed by the shunt at V = 1.0 p.u. and Qsh is the reactive power injected by
    # the shunt at V = 1.0 p.u. then Psh - j Qsh = V * conj(Ysh * V) = conj(Ysh) = Gs - j Bs,
    # vector of shunt admittances
    Ysh = (bus[:, GS] + 1j * bus[:, BS]) / baseMVA

    # Line charging susceptance BR_B is also added as shunt admittance:
    # summation of charging susceptances per each bus
    stat = branch[:, BR_STATUS]  ## ones at in-service branches
    Ys = stat / (branch[:, BR_R] + 1j * branch[:, BR_X])
    ysh = (-branch[:, BR_B].imag + 1j * (branch[:, BR_B].real)) / 2
    tap = branch[:, TAP]  # * np.exp(1j * np.pi / 180 * branch[:, SHIFT])

    ysh_f = Ys * (1 - tap) / (tap * np.conj(tap)) + ysh / (tap * np.conj(tap))
    ysh_t = Ys * (tap - 1) / tap + ysh

    Gch = (np.bincount(
        branch[:, F_BUS].real.astype(int), weights=ysh_f.real, minlength=nobus)
           + np.bincount(branch[:, T_BUS].real.astype(int),
                         weights=ysh_t.real,
                         minlength=nobus))
    Bch = (np.bincount(
        branch[:, F_BUS].real.astype(int), weights=ysh_f.imag, minlength=nobus)
           + np.bincount(branch[:, T_BUS].real.astype(int),
                         weights=ysh_t.imag,
                         minlength=nobus))

    Ysh += Gch + 1j * Bch

    # detect generators on PV buses which have status ON
    gen_pv = np.in1d(gen[:, GEN_BUS], pv) & (gen[:, GEN_STATUS] > 0)
    qg_lim = np.zeros(
        ngen, dtype=bool)  #initialize generators which violated Q limits

    Iinj = np.conj(Sbus / V0) - Ysh * V0  # Initial current injections

    # initiate reference voltage vector
    V_ref = np.ones(nobus, dtype=complex)
    for neti, buses_ordered_bfs in enumerate(buses_ordered_bfs_nets):
        V_ref[buses_ordered_bfs] *= V0[ref[neti]]
    V = V0.copy()

    n_iter = 0
    converged = 0

    if verbose:
        print(' -- AC Power Flow (Backward/Forward sweep)\n')

    while not converged and n_iter < max_it:
        n_iter_inner = 0
        n_iter += 1

        deltaV = DLF * Iinj[mask_root]
        V[mask_root] = V_ref[mask_root] + deltaV

        # ##
        # inner loop for considering PV buses
        # TODO improve PV buses inner loop
        inner_loop_converged = False
        while not inner_loop_converged and len(pv) > 0:

            pvi = pv - norefs  # internal PV buses indices, assuming reference node is always 0

            Vmis = (np.abs(gen[gen_pv, VG]))**2 - (np.abs(V[pv]))**2
            # TODO improve getting values from sparse DLF matrix - DLF[pvi, pvi] is unefficient
            dQ = (Vmis / (2 * DLF[pvi, pvi].A1.imag)).flatten()

            gen[gen_pv, QG] += dQ

            if enforce_q_lims:  #check Q violation limits
                ## find gens with violated Q constraints
                qg_max_lim = (gen[:, QG] > gen[:, QMAX]) & gen_pv
                qg_min_lim = (gen[:, QG] < gen[:, QMIN]) & gen_pv

                if qg_min_lim.any():
                    gen[qg_min_lim, QG] = gen[qg_min_lim, QMIN]
                    bus[gen[qg_min_lim, GEN_BUS].astype(int),
                        BUS_TYPE] = 1  # convert to PQ bus

                if qg_max_lim.any():
                    gen[qg_max_lim, QG] = gen[qg_max_lim, QMAX]
                    bus[gen[qg_max_lim, GEN_BUS].astype(int),
                        BUS_TYPE] = 1  # convert to PQ bus

                # TODO: correct: once all the PV buses are converted to PQ buses, conversion back to PV is not possible
                qg_lim_new = qg_min_lim | qg_max_lim
                if qg_lim_new.any():
                    pq2pv = (qg_lim != qg_lim_new) & qg_lim
                    # convert PQ to PV bus
                    if pq2pv.any():
                        bus[gen[qg_max_lim, GEN_BUS].astype(int),
                            BUS_TYPE] = 2  # convert to PV bus

                    qg_lim = qg_lim_new.copy()
                    ref, pv, pq = bustypes(bus, gen)

            # avoid calling makeSbus, update only Sbus for pv nodes
            Sbus = makeSbus(baseMVA, bus, gen)
            Iinj = np.conj(Sbus / V) - Ysh * V
            deltaV = DLF * Iinj[mask_root]
            V[mask_root] = V_ref[mask_root] + deltaV

            if n_iter_inner > max_iter_pv:
                raise LoadflowNotConverged(
                    " FBSW Power Flow did not converge - inner iterations for PV nodes "
                    "reached maximum value of {0}!".format(max_iter_pv))

            n_iter_inner += 1

            if np.all(np.abs(dQ) < tol_mva_inner
                      ):  # inner loop termination criterion
                inner_loop_converged = True

        # testing termination criterion -
        mis = V * np.conj(Ybus * V) - Sbus
        F = np.r_[mis[pv].real, mis[pq].real, mis[pq].imag]

        # check tolerance
        normF = np.linalg.norm(F, np.Inf)
        if normF < tolerance_mva:
            converged = 1
            if verbose:
                print("\nFwd-back sweep power flow converged in "
                      "{0} iterations.\n".format(n_iter))
        elif n_iter == max_it:
            raise LoadflowNotConverged(
                " FBSW Power Flow did not converge - "
                "reached maximum iterations = {0}!".format(max_it))

        # updating injected currents
        Iinj = np.conj(Sbus / V) - Ysh * V

    return V, converged