Beispiel #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
Beispiel #2
0
    def admmopf_consfcn(self, x=None):
        if x is None:
            x = self.pb['x0']
        nx = len(x)
        nb = self.region['nb']
        ng = self.region['ng']
        iv = self.idx['var']
        bus = self.region['bus']
        gen = self.region['gen']
        Ybus = self.region['Ybus']
        ridx = self.idx['rbus']['int']
        # idx ranges
        iVa = iv['iVa']
        iVm = iv['iVm']
        iPg = iv['iPg']
        iQg = iv['iQg']
        # grab Pg and Qg
        gen[:, PG] = x[iPg]
        gen[:, QG] = x[iQg]
        # rebuid Sbus
        Sbus = makeSbus(1, bus, gen)
        # reconstruct V
        Va, Vm = x[iVa], x[iVm]
        V = Vm * exp(1j * Va)
        # evaluate power flow equations
        mis = V * conj(Ybus * V) - Sbus
        g = r_[mis.real, mis.imag]
        row = ridx + [i + nb for i in ridx]
        g = g[row]
        # ---- evaluate constraint gradients -------
        # compute partials of injected bus powers
        dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus, V)  # w.r.t. V
        neg_Cg = sparse((-ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng))
        # construct Jacobian of equality constraints (power flow) and transpose it
        dg = lil_matrix((2 * nb, nx))
        blank = sparse((nb, ng))
        dg = vstack([ \
			#P mismatch w.r.t Va, Vm, Pg, Qg

         hstack([dSbus_dVa.real, dSbus_dVm.real, neg_Cg, blank]),
         # Q mismatch w.r.t Va, Vm, Pg, Qg
         hstack([dSbus_dVa.imag, dSbus_dVm.imag, blank, neg_Cg])
         ], "csr")
        dg = dg[row, :]
        dg = dg.T
        h = None
        dh = None
        return h, g, dh, dg
Beispiel #3
0
def _run_dc_pf(ppci):
    t0 = time()
    baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, _ = _get_pf_variables_from_ppci(
        ppci)

    ppci["bus"][:, VM] = 1.0
    ## initial state
    Va0 = bus[:, VA] * (pi / 180.)

    ## build B matrices and phase shift injections
    B, Bf, Pbusinj, Pfinj = makeBdc(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] = real(gen[refgen, PG] + (B[ref, :] * Va - Pbus[ref]) * baseMVA)
    success = True

    # store results from DC powerflow for AC powerflow
    ppci = _store_results_from_pf_in_ppci(ppci, bus, gen, branch)

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

    return ppci
Beispiel #4
0
def _run_ac_pf_without_qlims_enforced(ppci, recycle, makeYbus, ppopt):
    baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, V0 = _get_pf_variables_from_ppci(
        ppci)

    ppci, Ybus, Yf, Yt = _get_Y_bus(ppci, recycle, makeYbus, baseMVA, bus,
                                    branch)

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

    ## run the power flow
    V, success = _call_power_flow_function(baseMVA, bus, branch, Ybus, Sbus,
                                           V0, ref, pv, pq, ppopt)

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

    return ppci, success, bus, gen, branch
Beispiel #5
0
def _run_ac_pf_without_qlims_enforced(ppci, options):
    numba, makeYbus = _import_numba_extensions_if_flag_is_true(
        options["numba"])

    baseMVA, bus, gen, branch, ref, pv, pq, on, gbus, V0 = _get_pf_variables_from_ppci(
        ppci)

    ppci, Ybus, Yf, Yt = _get_Y_bus(ppci, options, makeYbus, baseMVA, bus,
                                    branch)

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

    ## run the newton power  flow
    V, success, _ = newtonpf(Ybus, Sbus, V0, pv, pq, options, numba)

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

    return ppci, success, bus, gen, branch
Beispiel #6
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
Beispiel #7
0
def fbsw(DLF,
         bus,
         gen,
         branch,
         baseMVA,
         Ybus,
         Sbus,
         V0,
         ref,
         pv,
         pq,
         ppopt=None,
         tol_inner=1e-2):
    """
    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.
    """
    ## options
    if ppopt is None:
        ppopt = ppoption()
    tol = ppopt['PF_TOL']
    max_it = ppopt['PF_MAX_IT_GS']  # maximum iterations from Gauss-Seidel
    verbose = ppopt['VERBOSE']

    nbus = bus.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)))

    # detect PV buses
    busPV_ind_mask = [bus_ind_mask_dict[pvbus] for pvbus in pv]
    genPV_ind = np.where(np.in1d(gen[:, GEN_BUS], pv))[0].flatten()

    # 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
    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

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

    n_iter = 0
    converged = 0

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

        deltaV = DLF * Iinj
        V_new = np.ones(nbus - 1) * Vref + deltaV

        # ##
        # inner loop for considering PV buses
        inner_loop_converged = False
        V_inner = V_new.copy()

        success_inner = 1
        while not inner_loop_converged and len(pv) > 0:
            Vmis = (np.abs(gen[genPV_ind, VG]))**2 - (np.abs(
                V_inner[busPV_ind_mask]))**2
            dQ = (Vmis /
                  (2 * DLF[busPV_ind_mask, busPV_ind_mask].A1.imag)).flatten()

            gen[genPV_ind, QG] += dQ
            if (gen[genPV_ind, QG] < gen[genPV_ind, QMIN]).any():
                Qviol_ind = np.argwhere(
                    (gen[genPV_ind, QG] < gen[genPV_ind, QMIN])).flatten()
                gen[genPV_ind[Qviol_ind], QG] = gen[genPV_ind[Qviol_ind], QMIN]
            elif (gen[genPV_ind, QG] > gen[genPV_ind, QMAX]).any():
                Qviol_ind = np.argwhere(
                    (gen[genPV_ind, QG] < gen[genPV_ind, QMIN])).flatten()
                gen[genPV_ind[Qviol_ind], QG] = gen[genPV_ind[Qviol_ind], QMAX]

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

            if n_iter_inner > 20 or np.any(
                    np.abs(V_inner[busPV_ind_mask]) > 2):
                success_inner = 0
                break
                # raise ConvergenceError("\n\t inner iterations (PV nodes) did not converge!!!")

            n_iter_inner += 1

            if np.all(np.abs(dQ) < tol_inner
                      ):  # inner loop termination criterion
                inner_loop_converged = True
                V_new = V_inner.copy()

        if not success_inner:
            if verbose:
                sys.stdout.write(
                    "\nFwd-back sweep power flow did not converge in "
                    "{0} iterations.\n".format(n_iter))
            break

        # testing termination criterion -
        V = np.insert(V_new, 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)

        # deltaVmax = np.max(np.abs(V_new - V_iter))

        if normF < tol:
            converged = 1
            if verbose:
                sys.stdout.write("\nFwd-back sweep power flow converged in "
                                 "{0} iterations.\n".format(n_iter))

        V_iter = V_new.copy()  # update iterating complex voltage vector

        # updating injected currents
        Iinj = np.conj(Sbus[mask_root] / V_iter) - Ysh[mask_root] * V_iter

    return V, converged
Beispiel #8
0
def runpf(casedata=None, ppopt=None, fname='', solvedcase=''):
    """Runs a power flow.

    Runs a power flow [full AC Newton's method by default] and optionally
    returns the solved values in the data matrices, a flag which is C{True} if
    the algorithm was successful in finding a solution, and the elapsed
    time in seconds. All input arguments are optional. If C{casename} is
    provided it specifies the name of the input data file or dict
    containing the power flow data. The default value is 'case9'.

    If the ppopt is provided it overrides the default PYPOWER options
    vector and can be used to specify the solution algorithm and output
    options among other things. If the 3rd argument is given the pretty
    printed output will be appended to the file whose name is given in
    C{fname}. If C{solvedcase} is specified the solved case will be written
    to a case file in PYPOWER format with the specified name. If C{solvedcase}
    ends with '.mat' it saves the case as a MAT-file otherwise it saves it
    as a Python-file.

    If the C{ENFORCE_Q_LIMS} options is set to C{True} [default is false] then
    if any generator reactive power limit is violated after running the AC
    power flow, the corresponding bus is converted to a PQ bus, with Qg at
    the limit, and the case is re-run. The voltage magnitude at the bus
    will deviate from the specified value in order to satisfy the reactive
    power limit. If the reference bus is converted to PQ, the first
    remaining PV bus will be used as the slack bus for the next iteration.
    This may result in the real power output at this generator being
    slightly off from the specified values.

    Enforcing of generator Q limits inspired by contributions from Mu Lin,
    Lincoln University, New Zealand (1/14/05).

    @author: Ray Zimmerman (PSERC Cornell)
    """
    ## default arguments
    if casedata is None:
        casedata = join(dirname(__file__), 'case9')
    ppopt = ppoption(ppopt)

    ## options
    verbose = ppopt["VERBOSE"]
    qlim = ppopt["ENFORCE_Q_LIMS"]  ## enforce Q limits on gens?
    dc = ppopt["PF_DC"]             ## use DC formulation?

    ## read data
    ppc = loadcase(casedata)

    ## add zero columns to branch for flows if needed
    if ppc["branch"].shape[1] < QT:
        ppc["branch"] = c_[ppc["branch"],
                           zeros((ppc["branch"].shape[0],
                                  QT - ppc["branch"].shape[1] + 1))]

    ## convert to internal indexing
    ppc = ext2int(ppc)
    baseMVA, bus, gen, branch = \
        ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["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 verbose > 0:
        v = ppver('all')
        stdout.write('PYPOWER Version %s, %s' % (v["Version"], v["Date"]))

    if dc:                               # DC formulation
        if verbose:
            stdout.write(' -- 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).real - 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
    else:                                ## AC formulation
        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)
            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:
                stderr.write('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], 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

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

    ##-----  output results  -----
    ## convert back to original bus numbering & print results
    ppc["bus"], ppc["gen"], ppc["branch"] = bus, gen, branch
    results = int2ext(ppc)

    ## zero out result fields of out-of-service gens & branches
    if len(results["order"]["gen"]["status"]["off"]) > 0:
        results["gen"][ix_(results["order"]["gen"]["status"]["off"], [PG, QG])] = 0

    if len(results["order"]["branch"]["status"]["off"]) > 0:
        results["branch"][ix_(results["order"]["branch"]["status"]["off"], [PF, QF, PT, QT])] = 0

    if fname:
        fd = None
        try:
            fd = open(fname, "a")
        except Exception as detail:
            stderr.write("Error opening %s: %s.\n" % (fname, detail))
        finally:
            if fd is not None:
                printpf(results, fd, ppopt)
                fd.close()
    else:
        printpf(results, stdout, ppopt)

    ## save solved case
    if solvedcase:
        savecase(solvedcase, results)

    return results, success
Beispiel #9
0
def runcpf(basecasedata=None,
           targetcasedata=None,
           ppopt=None,
           fname='',
           solvedcase=''):

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

    # options
    verbose = ppopt["VERBOSE"]
    step = ppopt["CPF_STEP"]
    parameterization = ppopt["CPF_PARAMETERIZATION"]
    adapt_step = ppopt["CPF_ADAPT_STEP"]
    cb_args = ppopt["CPF_USER_CALLBACK_ARGS"]

    # set up callbacks
    callback_names = ["cpf_default_callback"]
    if len(ppopt["CPF_USER_CALLBACK"]) > 0:
        if isinstance(ppopt["CPF_USER_CALLBACK"], list):
            callback_names = r_[callback_names, ppopt["CPF_USER_CALLBACK"]]
        else:
            callback_names.append(ppopt["CPF_USER_CALLBACK"])
    callbacks = []
    for callback_name in callback_names:
        callbacks.append(getattr(cpf_callbacks, callback_name))

    # read base case data
    ppcbase = loadcase(basecasedata)
    nb = ppcbase["bus"].shape[0]

    # add zero columns to branch for flows if needed
    if ppcbase["branch"].shape[1] < QT:
        ppcbase["branch"] = c_[ppcbase["branch"],
                               zeros((ppcbase["branch"].shape[0],
                                      QT - ppcbase["branch"].shape[1] + 1))]

    # convert to internal indexing
    ppcbase = ext2int(ppcbase)
    baseMVAb, busb, genb, branchb = \
        ppcbase["baseMVA"], ppcbase["bus"], ppcbase["gen"], ppcbase["branch"]

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

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

    # read target case data
    ppctarget = loadcase(targetcasedata)

    # add zero columns to branch for flows if needed
    if ppctarget["branch"].shape[1] < QT:
        ppctarget["branch"] = c_[ppctarget["branch"],
                                 zeros(
                                     (ppctarget["branch"].shape[0],
                                      QT - ppctarget["branch"].shape[1] + 1))]

    # convert to internal indexing
    ppctarget = ext2int(ppctarget)
    baseMVAt, bust, gent, brancht = \
        ppctarget["baseMVA"], ppctarget["bus"], ppctarget["gen"], ppctarget["branch"]

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

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

    # -----  run the power flow  -----
    t0 = time()
    if verbose > 0:
        v = ppver('all')
        stdout.write('PYPOWER Version %s, %s' % (v["Version"], v["Date"]))
        stdout.write(' -- AC Continuation Power Flow\n')

    # initial state
    # V0    = ones(bus.shape[0])            ## flat start
    V0 = busb[:, VM] * exp(1j * pi / 180 * busb[:, VA])
    vcb = ones(V0.shape)  # create mask of voltage-controlled buses
    vcb[pq] = 0  # exclude PQ buses
    k = find(vcb[gbusb])  # in-service gens at v-c buses
    V0[gbusb[k]] = genb[onb[k], VG] / abs(V0[gbusb[k]]) * V0[gbusb[k]]

    # build admittance matrices
    Ybus, Yf, Yt = makeYbus(baseMVAb, busb, branchb)

    # compute base case complex bus power injections (generation - load)
    Sbusb = makeSbus(baseMVAb, busb, genb)
    # compute target case complex bus power injections (generation - load)
    Sbust = makeSbus(baseMVAt, bust, gent)

    # scheduled transfer
    Sxfr = Sbust - Sbusb

    # Run the base case power flow solution
    if verbose > 2:
        ppopt_pf = ppoption(ppopt, VERBOSE=max(0, verbose - 1))
    else:
        ppopt_pf = ppoption(ppopt, VERBOSE=max(0, verbose - 2))

    lam = 0
    V, success, iterations = newtonpf(Ybus, Sbusb, V0, ref, pv, pq, ppopt_pf)
    if verbose > 2:
        print('step %3d : lambda = %6.3f\n' % (0, 0))
    elif verbose > 1:
        print('step %3d : lambda = %6.3f, %2d Newton steps\n',
              (0, 0, iterations))

    lamprv = lam  # lam at previous step
    Vprv = V  # V at previous step
    continuation = 1
    cont_steps = 0

    # input args for callbacks
    cb_data = {
        "ppc_base": ppcbase,
        "ppc_target": ppctarget,
        "Sxfr": Sxfr,
        "Ybus": Ybus,
        "Yf": Yf,
        "Yt": Yt,
        "ref": ref,
        "pv": pv,
        "pq": pq,
        "ppopt": ppopt
    }
    cb_state = {}

    # invoke callbacks
    for k in range(len(callbacks)):
        cb_state, _ = callbacks[k](cont_steps, V, lam, V, lam, cb_data,
                                   cb_state, cb_args)

    if linalg.norm(Sxfr) == 0:
        if verbose:
            print(
                'base case and target case have identical load and generation\n'
            )

        continuation = 0
        V0 = V
        lam0 = lam

    # tangent predictor z = [dx;dlam]
    z = zeros(2 * len(V) + 1)
    z[-1] = 1.0
    while continuation:
        cont_steps = cont_steps + 1
        # prediction for next step
        V0, lam0, z = cpf_predictor(V, lam, Ybus, Sxfr, pv, pq, step, z, Vprv,
                                    lamprv, parameterization)

        # save previous voltage, lambda before updating
        Vprv = V
        lamprv = lam

        # correction
        V, success, i, lam = cpf_corrector(Ybus, Sbusb, V0, ref, pv, pq, lam0,
                                           Sxfr, Vprv, lamprv, z, step,
                                           parameterization, ppopt_pf)

        if not success:
            continuation = 0
            if verbose:
                print(
                    'step %3d : lambda = %6.3f, corrector did not converge in %d iterations\n'
                    % (cont_steps, lam, i))
            break

        if verbose > 2:
            print('step %3d : lambda = %6.3f\n' % (cont_steps, lam))
        elif verbose > 1:
            print('step %3d : lambda = %6.3f, %2d corrector Newton steps\n' %
                  (cont_steps, lam, i))

        # invoke callbacks
        for k in range(len(callbacks)):
            cb_state, _ = callbacks[k](cont_steps, V, lam, V0, lam0, cb_data,
                                       cb_state, cb_args)

        if isinstance(ppopt["CPF_STOP_AT"], str):
            if ppopt["CPF_STOP_AT"].upper() == "FULL":
                if abs(lam) < 1e-8:  # traced the full continuation curve
                    if verbose:
                        print(
                            '\nTraced full continuation curve in %d continuation steps\n'
                            % cont_steps)
                    continuation = 0
                elif lam < lamprv and lam - step < 0:  # next step will overshoot
                    step = lam  # modify step-size
                    parameterization = 1  # change to natural parameterization
                    adapt_step = False  # disable step-adaptivity

            else:  # == 'NOSE'
                if lam < lamprv:  # reached the nose point
                    if verbose:
                        print(
                            '\nReached steady state loading limit in %d continuation steps\n'
                            % cont_steps)
                    continuation = 0

        else:
            if lam < lamprv:
                if verbose:
                    print(
                        '\nReached steady state loading limit in %d continuation steps\n'
                        % cont_steps)
                continuation = 0
            elif abs(ppopt["CPF_STOP_AT"] -
                     lam) < 1e-8:  # reached desired lambda
                if verbose:
                    print(
                        '\nReached desired lambda %3.2f in %d continuation steps\n'
                        % (ppopt["CPF_STOP_AT"], cont_steps))
                continuation = 0
            # will reach desired lambda in next step
            elif lam + step > ppopt["CPF_STOP_AT"]:
                step = ppopt["CPF_STOP_AT"] - lam  # modify step-size
                parameterization = 1  # change to natural parameterization
                adapt_step = False  # disable step-adaptivity

        if adapt_step and continuation:
            pvpq = r_[pv, pq]
            # Adapt stepsize
            cpf_error = linalg.norm(
                r_[angle(V[pq]), abs(V[pvpq]), lam] -
                r_[angle(V0[pq]), abs(V0[pvpq]), lam0], inf)
            if cpf_error < ppopt["CPF_ERROR_TOL"]:
                # Increase stepsize
                step = step * ppopt["CPF_ERROR_TOL"] / cpf_error
                if step > ppopt["CPF_STEP_MAX"]:
                    step = ppopt["CPF_STEP_MAX"]
            else:
                # decrese stepsize
                step = step * ppopt["CPF_ERROR_TOL"] / cpf_error
                if step < ppopt["CPF_STEP_MIN"]:
                    step = ppopt["CPF_STEP_MIN"]

    # invoke callbacks
    if success:
        cpf_results = {}
        for k in range(len(callbacks)):
            cb_state, cpf_results = callbacks[k](cont_steps,
                                                 V,
                                                 lam,
                                                 V0,
                                                 lam0,
                                                 cb_data,
                                                 cb_state,
                                                 cb_args,
                                                 results=cpf_results,
                                                 is_final=True)
    else:
        cpf_results["iterations"] = i

    # update bus and gen matrices to reflect the loading and generation
    # at the noise point
    bust[:, PD] = busb[:, PD] + lam * (bust[:, PD] - busb[:, PD])
    bust[:, QD] = busb[:, QD] + lam * (bust[:, QD] - busb[:, QD])
    gent[:, PG] = genb[:, PG] + lam * (gent[:, PG] - genb[:, PG])

    # update data matrices with solution
    bust, gent, brancht = pfsoln(baseMVAt, bust, gent, brancht, Ybus, Yf, Yt,
                                 V, ref, pv, pq)

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

    # -----  output results  -----
    # convert back to original bus numbering & print results
    ppctarget["bus"], ppctarget["gen"], ppctarget[
        "branch"] = bust, gent, brancht
    if success:
        n = cpf_results["iterations"] + 1
        cpf_results["V_p"] = i2e_data(ppctarget, cpf_results["V_p"],
                                      full((nb, n), nan), "bus", 0)
        cpf_results["V_c"] = i2e_data(ppctarget, cpf_results["V_c"],
                                      full((nb, n), nan), "bus", 0)
    results = int2ext(ppctarget)
    results["cpf"] = cpf_results

    # zero out result fields of out-of-service gens & branches
    if len(results["order"]["gen"]["status"]["off"]) > 0:
        results["gen"][ix_(results["order"]["gen"]["status"]["off"],
                           [PG, QG])] = 0

    if len(results["order"]["branch"]["status"]["off"]) > 0:
        results["branch"][ix_(results["order"]["branch"]["status"]["off"],
                              [PF, QF, PT, QT])] = 0

    if fname:
        fd = None
        try:
            fd = open(fname, "a")
        except Exception as detail:
            stderr.write("Error opening %s: %s.\n" % (fname, detail))
        finally:
            if fd is not None:
                printpf(results, fd, ppopt)
                fd.close()
    else:
        printpf(results, stdout, ppopt)

    # save solved case
    if solvedcase:
        savecase(solvedcase, results)

    return results, success
    def admmopf_consfcn(self, x=None):
        if x is None:
            x = self.pb['x0']
        nx = len(x)
        nb = self.region['nb']
        ng = self.region['ng']
        iv = self.idx['var']
        bus = self.region['bus']
        gen = self.region['gen']
        branch = self.region['branch']
        Ybus = self.region['Ybus']
        Yf = self.region['Yf']
        Yt = self.region['Yt']
        baseMVA = self.region['baseMVA']

        ridx = self.idx['rbus']['int']
        # idx ranges
        iVa = iv['iVa']
        iVm = iv['iVm']
        iPg = iv['iPg']
        iQg = iv['iQg']
        # grab Pg and Qg
        gen[:, PG] = x[iPg]
        gen[:, QG] = x[iQg]
        # rebuid Sbus
        Sbus = makeSbus(1, bus, gen)
        # reconstruct V
        Va, Vm = x[iVa], x[iVm]
        V = Vm * exp(1j * Va)
        # evaluate power flow equations
        mis = V * conj(Ybus * V) - Sbus
        g = r_[mis.real, mis.imag]
        row = ridx + [i + nb for i in ridx]
        g = g[row]

        il = find((branch[:, RATE_A] != 0) & (branch[:, RATE_A] < 1e10))
        nl2 = len(il)

        Ybus2, Yf2, Yt2 = makeYbus(baseMVA, bus, branch)
        Yf = Yf2[il, :]
        Yt = Yt2[il, :]

        if nl2 > 0:
            flow_max = (branch[il, RATE_A] / baseMVA)**2
            flow_max[flow_max == 0] = Inf

            ## compute branch power flows
            ## complex power injected at "from" bus (p.u.)
            Sf = V[branch[il, F_BUS].astype(int)] * conj(Yf * V)
            ## complex power injected at "to" bus (p.u.)
            St = V[branch[il, T_BUS].astype(int)] * conj(Yt * V)
            h = r_[Sf.real**2 - flow_max,  ## branch P limits (from bus)
                   St.real**2 - flow_max]  ## branch P limits (to bus)

        else:
            h = array([])

        # ---- evaluate constraint gradients -------
        # compute partials of injected bus powers
        dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus, V)  # w.r.t. V
        neg_Cg = sparse((-ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng))

        # construct Jacobian of equality constraints (power flow) and transpose it
        dg = lil_matrix((2 * nb, nx))
        blank = sparse((nb, ng))
        dg = vstack([ \
			#P mismatch w.r.t Va, Vm, Pg, Qg

         hstack([dSbus_dVa.real, dSbus_dVm.real, neg_Cg, blank]),
         # Q mismatch w.r.t Va, Vm, Pg, Qg
         hstack([dSbus_dVa.imag, dSbus_dVm.imag, blank, neg_Cg])
         ], "csr")
        dg = dg[row, :]
        dg = dg.T

        if nl2 > 0:
            ## compute partials of Flows w.r.t. V                  ## power
            dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \
                             dSbr_dV(branch[il, :], Yf, Yt, V)
            dFf_dVa = dFf_dVa.real
            dFf_dVm = dFf_dVm.real
            dFt_dVa = dFt_dVa.real
            dFt_dVm = dFt_dVm.real
            Ff = Ff.real
            Ft = Ft.real

            ## squared magnitude of flow (of complex power or current, or real power)
            df_dVa, df_dVm, dt_dVa, dt_dVm = \
                         dAbr_dV(dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft)
            ## construct Jacobian of inequality constraints (branch limits)
            ## and transpose it.
            dh = lil_matrix((2 * nl2, nx))
            dh[:, r_[iVa, iVm].T] = vstack(
                [
                    hstack([df_dVa, df_dVm]),  ## "from" flow limit
                    hstack([dt_dVa, dt_dVm])  ## "to" flow limit
                ],
                "csr")
            dh = dh.T

        else:
            dh = None

        h = array([])
        dh = None

        return h, g, dh, dg
Beispiel #11
0
def _bfswpf(DLF, bus, gen, branch, baseMVA, Ybus, Sbus, Ibus, 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
    :param buses_ordered_bfs_nets: buses ordered according to breadth-first search

    :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)

    Ysh = _makeYsh_bfsw(bus, branch, baseMVA)

    # 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 + Ibus  # 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 + Ibus
            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 -
        F = _evaluate_Fx(Ybus, V, Sbus, pv, pq, Ibus=Ibus)
        # check tolerance
        converged = _check_for_convergence(F, tolerance_mva)

        if converged and verbose:
            print("\nFwd-back sweep power flow converged in "
                  "{0} iterations.\n".format(n_iter))

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

    return V, converged
Beispiel #12
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
Beispiel #13
0
def opf_consfcn(x, om, Ybus, Yf, Yt, ppopt, il=None, *args):
    """Evaluates nonlinear constraints and their Jacobian for OPF.

    Constraint evaluation function for AC optimal power flow, suitable
    for use with L{pips}. Computes constraint vectors and their gradients.

    @param x: optimization vector
    @param om: OPF model object
    @param Ybus: bus admittance matrix
    @param Yf: admittance matrix for "from" end of constrained branches
    @param Yt: admittance matrix for "to" end of constrained branches
    @param ppopt: PYPOWER options vector
    @param il: (optional) vector of branch indices corresponding to
    branches with flow limits (all others are assumed to be
    unconstrained). The default is C{range(nl)} (all branches).
    C{Yf} and C{Yt} contain only the rows corresponding to C{il}.

    @return: C{h} - vector of inequality constraint values (flow limits)
    limit^2 - flow^2, where the flow can be apparent power real power or
    current, depending on value of C{OPF_FLOW_LIM} in C{ppopt} (only for
    constrained lines). C{g} - vector of equality constraint values (power
    balances). C{dh} - (optional) inequality constraint gradients, column
    j is gradient of h(j). C{dg} - (optional) equality constraint gradients.

    @see: L{opf_costfcn}, L{opf_hessfcn}

    @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad
    Autonoma de Manizales)
    @author: Ray Zimmerman (PSERC Cornell)
    """
    ##----- initialize -----

    ## unpack data
    ppc = om.get_ppc()
    baseMVA, bus, gen, branch = \
        ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"]
    vv, _, _, _ = om.get_idx()

    ## problem dimensions
    nb = bus.shape[0]  ## number of buses
    nl = branch.shape[0]  ## number of branches
    ng = gen.shape[0]  ## number of dispatchable injections
    nxyz = len(x)  ## total number of control vars of all types

    ## set default constrained lines
    if il is None:
        il = arange(nl)  ## all lines have limits by default
    nl2 = len(il)  ## number of constrained lines

    ## grab Pg & Qg
    Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]]  ## active generation in p.u.
    Qg = x[vv["i1"]["Qg"]:vv["iN"]["Qg"]]  ## reactive generation in p.u.

    ## put Pg & Qg back in gen
    gen[:, PG] = Pg * baseMVA  ## active generation in MW
    gen[:, QG] = Qg * baseMVA  ## reactive generation in MVAr

    ## rebuild Sbus
    Sbus = makeSbus(baseMVA, bus, gen)  ## net injected power in p.u.

    ## ----- evaluate constraints -----
    ## reconstruct V
    Va = x[vv["i1"]["Va"]:vv["iN"]["Va"]]
    Vm = x[vv["i1"]["Vm"]:vv["iN"]["Vm"]]
    V = Vm * exp(1j * Va)

    ## evaluate power flow equations
    mis = V * conj(Ybus * V) - Sbus

    ##----- evaluate constraint function values -----
    ## first, the equality constraints (power flow)
    g = r_[mis.real,  ## active power mismatch for all buses
           mis.imag]  ## reactive power mismatch for all buses

    ## then, the inequality constraints (branch flow limits)
    if nl2 > 0:
        flow_max = (branch[il, RATE_A] / baseMVA)**2
        flow_max[flow_max == 0] = Inf
        if ppopt['OPF_FLOW_LIM'] == 2:  ## current magnitude limit, |I|
            If = Yf * V
            It = Yt * V
            h = r_[If * conj(If) - flow_max,  ## branch I limits (from bus)
                   It * conj(It) - flow_max].real  ## branch I limits (to bus)
        else:
            ## compute branch power flows
            ## complex power injected at "from" bus (p.u.)
            Sf = V[branch[il, F_BUS].astype(int)] * conj(Yf * V)
            ## complex power injected at "to" bus (p.u.)
            St = V[branch[il, T_BUS].astype(int)] * conj(Yt * V)
            if ppopt['OPF_FLOW_LIM'] == 1:  ## active power limit, P (Pan Wei)
                h = r_[Sf.real**2 - flow_max,  ## branch P limits (from bus)
                       St.real**2 - flow_max]  ## branch P limits (to bus)
            else:  ## apparent power limit, |S|
                h = r_[Sf * conj(Sf) - flow_max,  ## branch S limits (from bus)
                       St * conj(St) -
                       flow_max].real  ## branch S limits (to bus)
    else:
        h = zeros((0, 1))

    ##----- evaluate partials of constraints -----
    ## index ranges
    iVa = arange(vv["i1"]["Va"], vv["iN"]["Va"])
    iVm = arange(vv["i1"]["Vm"], vv["iN"]["Vm"])
    iPg = arange(vv["i1"]["Pg"], vv["iN"]["Pg"])
    iQg = arange(vv["i1"]["Qg"], vv["iN"]["Qg"])
    iVaVmPgQg = r_[iVa, iVm, iPg, iQg].T

    ## compute partials of injected bus powers
    dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus, V)  ## w.r.t. V
    ## Pbus w.r.t. Pg, Qbus w.r.t. Qg
    neg_Cg = sparse((-ones(ng), (gen[:, GEN_BUS], list(range(ng)))), (nb, ng))

    ## construct Jacobian of equality constraints (power flow) and transpose it
    dg = lil_matrix((2 * nb, nxyz))
    blank = sparse((nb, ng))
    dg[:, iVaVmPgQg] = vstack(
        [
            ## P mismatch w.r.t Va, Vm, Pg, Qg
            hstack([dSbus_dVa.real, dSbus_dVm.real, neg_Cg, blank]),
            ## Q mismatch w.r.t Va, Vm, Pg, Qg
            hstack([dSbus_dVa.imag, dSbus_dVm.imag, blank, neg_Cg])
        ],
        "csr")
    dg = dg.T

    if nl2 > 0:
        ## compute partials of Flows w.r.t. V
        if ppopt['OPF_FLOW_LIM'] == 2:  ## current
            dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \
                    dIbr_dV(branch[il, :], Yf, Yt, V)
        else:  ## power
            dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \
                    dSbr_dV(branch[il, :], Yf, Yt, V)
        if ppopt['OPF_FLOW_LIM'] == 1:  ## real part of flow (active power)
            dFf_dVa = dFf_dVa.real
            dFf_dVm = dFf_dVm.real
            dFt_dVa = dFt_dVa.real
            dFt_dVm = dFt_dVm.real
            Ff = Ff.real
            Ft = Ft.real

        ## squared magnitude of flow (of complex power or current, or real power)
        df_dVa, df_dVm, dt_dVa, dt_dVm = \
                dAbr_dV(dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft)

        ## construct Jacobian of inequality constraints (branch limits)
        ## and transpose it.
        dh = lil_matrix((2 * nl2, nxyz))
        dh[:, r_[iVa, iVm].T] = vstack(
            [
                hstack([df_dVa, df_dVm]),  ## "from" flow limit
                hstack([dt_dVa, dt_dVm])  ## "to" flow limit
            ],
            "csr")
        dh = dh.T
    else:
        dh = None

    return h, g, dh, dg
Beispiel #14
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
def opf_consfcn(x, om, Ybus, Yf, Yt, ppopt, il=None, *args):
    """Evaluates nonlinear constraints and their Jacobian for OPF.

    Constraint evaluation function for AC optimal power flow, suitable
    for use with L{pips}. Computes constraint vectors and their gradients.

    @param x: optimization vector
    @param om: OPF model object
    @param Ybus: bus admittance matrix
    @param Yf: admittance matrix for "from" end of constrained branches
    @param Yt: admittance matrix for "to" end of constrained branches
    @param ppopt: PYPOWER options vector
    @param il: (optional) vector of branch indices corresponding to
    branches with flow limits (all others are assumed to be
    unconstrained). The default is C{range(nl)} (all branches).
    C{Yf} and C{Yt} contain only the rows corresponding to C{il}.

    @return: C{h} - vector of inequality constraint values (flow limits)
    limit^2 - flow^2, where the flow can be apparent power real power or
    current, depending on value of C{OPF_FLOW_LIM} in C{ppopt} (only for
    constrained lines). C{g} - vector of equality constraint values (power
    balances). C{dh} - (optional) inequality constraint gradients, column
    j is gradient of h(j). C{dg} - (optional) equality constraint gradients.

    @see: L{opf_costfcn}, L{opf_hessfcn}

    @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad
    Autonoma de Manizales)
    @author: Ray Zimmerman (PSERC Cornell)
    @author: Richard Lincoln
    """
    ##----- initialize -----

    ## unpack data
    ppc = om.get_ppc()
    baseMVA, bus, gen, branch = \
        ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"]
    vv, _, _, _ = om.get_idx()

    ## problem dimensions
    nb = bus.shape[0]          ## number of buses
    nl = branch.shape[0]       ## number of branches
    ng = gen.shape[0]          ## number of dispatchable injections
    nxyz = len(x)              ## total number of control vars of all types

    ## set default constrained lines
    if il is None:
        il = arange(nl)         ## all lines have limits by default
    nl2 = len(il)              ## number of constrained lines

    ## grab Pg & Qg
    Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]]  ## active generation in p.u.
    Qg = x[vv["i1"]["Qg"]:vv["iN"]["Qg"]]  ## reactive generation in p.u.

    ## put Pg & Qg back in gen
    gen[:, PG] = Pg * baseMVA  ## active generation in MW
    gen[:, QG] = Qg * baseMVA  ## reactive generation in MVAr

    ## rebuild Sbus
    Sbus = makeSbus(baseMVA, bus, gen) ## net injected power in p.u.

    ## ----- evaluate constraints -----
    ## reconstruct V
    Va = x[vv["i1"]["Va"]:vv["iN"]["Va"]]
    Vm = x[vv["i1"]["Vm"]:vv["iN"]["Vm"]]
    V = Vm * exp(1j * Va)

    ## evaluate power flow equations
    mis = V * conj(Ybus * V) - Sbus

    ##----- evaluate constraint function values -----
    ## first, the equality constraints (power flow)
    g = r_[ mis.real,            ## active power mismatch for all buses
            mis.imag ]           ## reactive power mismatch for all buses

    ## then, the inequality constraints (branch flow limits)
    if nl2 > 0:
        flow_max = (branch[il, RATE_A] / baseMVA)**2
        flow_max[flow_max == 0] = Inf
        if ppopt['OPF_FLOW_LIM'] == 2:       ## current magnitude limit, |I|
            If = Yf * V
            It = Yt * V
            h = r_[ If * conj(If) - flow_max,     ## branch I limits (from bus)
                    It * conj(It) - flow_max ].real    ## branch I limits (to bus)
        else:
            ## compute branch power flows
            ## complex power injected at "from" bus (p.u.)
            Sf = V[ branch[il, F_BUS].astype(int) ] * conj(Yf * V)
            ## complex power injected at "to" bus (p.u.)
            St = V[ branch[il, T_BUS].astype(int) ] * conj(Yt * V)
            if ppopt['OPF_FLOW_LIM'] == 1:   ## active power limit, P (Pan Wei)
                h = r_[ Sf.real**2 - flow_max,   ## branch P limits (from bus)
                        St.real**2 - flow_max ]  ## branch P limits (to bus)
            else:                ## apparent power limit, |S|
                h = r_[ Sf * conj(Sf) - flow_max, ## branch S limits (from bus)
                        St * conj(St) - flow_max ].real  ## branch S limits (to bus)
    else:
        h = zeros((0,1))

    ##----- evaluate partials of constraints -----
    ## index ranges
    iVa = arange(vv["i1"]["Va"], vv["iN"]["Va"])
    iVm = arange(vv["i1"]["Vm"], vv["iN"]["Vm"])
    iPg = arange(vv["i1"]["Pg"], vv["iN"]["Pg"])
    iQg = arange(vv["i1"]["Qg"], vv["iN"]["Qg"])
    iVaVmPgQg = r_[iVa, iVm, iPg, iQg].T

    ## compute partials of injected bus powers
    dSbus_dVm, dSbus_dVa = dSbus_dV(Ybus, V)           ## w.r.t. V
    ## Pbus w.r.t. Pg, Qbus w.r.t. Qg
    neg_Cg = sparse((-ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng))

    ## construct Jacobian of equality constraints (power flow) and transpose it
    dg = lil_matrix((2 * nb, nxyz))
    blank = sparse((nb, ng))
    dg[:, iVaVmPgQg] = vstack([
            ## P mismatch w.r.t Va, Vm, Pg, Qg
            hstack([dSbus_dVa.real, dSbus_dVm.real, neg_Cg, blank]),
            ## Q mismatch w.r.t Va, Vm, Pg, Qg
            hstack([dSbus_dVa.imag, dSbus_dVm.imag, blank, neg_Cg])
        ], "csr")
    dg = dg.T

    if nl2 > 0:
        ## compute partials of Flows w.r.t. V
        if ppopt['OPF_FLOW_LIM'] == 2:     ## current
            dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \
                    dIbr_dV(branch[il, :], Yf, Yt, V)
        else:                  ## power
            dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft = \
                    dSbr_dV(branch[il, :], Yf, Yt, V)
        if ppopt['OPF_FLOW_LIM'] == 1:     ## real part of flow (active power)
            dFf_dVa = dFf_dVa.real
            dFf_dVm = dFf_dVm.real
            dFt_dVa = dFt_dVa.real
            dFt_dVm = dFt_dVm.real
            Ff = Ff.real
            Ft = Ft.real

        ## squared magnitude of flow (of complex power or current, or real power)
        df_dVa, df_dVm, dt_dVa, dt_dVm = \
                dAbr_dV(dFf_dVa, dFf_dVm, dFt_dVa, dFt_dVm, Ff, Ft)

        ## construct Jacobian of inequality constraints (branch limits)
        ## and transpose it.
        dh = lil_matrix((2 * nl2, nxyz))
        dh[:, r_[iVa, iVm].T] = vstack([
                hstack([df_dVa, df_dVm]),    ## "from" flow limit
                hstack([dt_dVa, dt_dVm])     ## "to" flow limit
            ], "csr")
        dh = dh.T
    else:
        dh = None

    return h, g, dh, dg
Beispiel #16
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
Beispiel #17
0
def runpf(casedata=None, ppopt=None, fname='', solvedcase=''):
    """Runs a power flow.

    Runs a power flow [full AC Newton's method by default] and optionally
    returns the solved values in the data matrices, a flag which is C{True} if
    the algorithm was successful in finding a solution, and the elapsed
    time in seconds. All input arguments are optional. If C{casename} is
    provided it specifies the name of the input data file or dict
    containing the power flow data. The default value is 'case9'.

    If the ppopt is provided it overrides the default PYPOWER options
    vector and can be used to specify the solution algorithm and output
    options among other things. If the 3rd argument is given the pretty
    printed output will be appended to the file whose name is given in
    C{fname}. If C{solvedcase} is specified the solved case will be written
    to a case file in PYPOWER format with the specified name. If C{solvedcase}
    ends with '.mat' it saves the case as a MAT-file otherwise it saves it
    as a Python-file.

    If the C{ENFORCE_Q_LIMS} options is set to C{True} [default is false] then
    if any generator reactive power limit is violated after running the AC
    power flow, the corresponding bus is converted to a PQ bus, with Qg at
    the limit, and the case is re-run. The voltage magnitude at the bus
    will deviate from the specified value in order to satisfy the reactive
    power limit. If the reference bus is converted to PQ, the first
    remaining PV bus will be used as the slack bus for the next iteration.
    This may result in the real power output at this generator being
    slightly off from the specified values.

    Enforcing of generator Q limits inspired by contributions from Mu Lin,
    Lincoln University, New Zealand (1/14/05).

    @author: Ray Zimmerman (PSERC Cornell)
    """
    ## default arguments
    if casedata is None:
        casedata = join(dirname(__file__), 'case9')
    ppopt = ppoption(ppopt)

    ## options
    verbose = ppopt["VERBOSE"]
    qlim = ppopt["ENFORCE_Q_LIMS"]  ## enforce Q limits on gens?
    dc = ppopt["PF_DC"]  ## use DC formulation?

    ## read data
    ppc = loadcase(casedata)

    ## add zero columns to branch for flows if needed
    if ppc["branch"].shape[1] < QT:
        ppc["branch"] = c_[ppc["branch"],
                           zeros((ppc["branch"].shape[0],
                                  QT - ppc["branch"].shape[1] + 1))]

    ## convert to internal indexing
    ppc = ext2int(ppc)
    baseMVA, bus, gen, branch = \
        ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["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 verbose > 0:
        v = ppver('all')
        stdout.write('PYPOWER Version %s, %s' % (v["Version"], v["Date"]))

    if dc:  # DC formulation
        if verbose:
            stdout.write(' -- 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).real - 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
    else:  ## AC formulation
        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)
            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:
                stderr.write('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],
                                                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

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

    ##-----  output results  -----
    ## convert back to original bus numbering & print results
    ppc["bus"], ppc["gen"], ppc["branch"] = bus, gen, branch
    results = int2ext(ppc)

    ## zero out result fields of out-of-service gens & branches
    if len(results["order"]["gen"]["status"]["off"]) > 0:
        results["gen"][ix_(results["order"]["gen"]["status"]["off"],
                           [PG, QG])] = 0

    if len(results["order"]["branch"]["status"]["off"]) > 0:
        results["branch"][ix_(results["order"]["branch"]["status"]["off"],
                              [PF, QF, PT, QT])] = 0

    if fname:
        fd = None
        try:
            fd = open(fname, "a")
        except Exception as detail:
            stderr.write("Error opening %s: %s.\n" % (fname, detail))
        finally:
            if fd is not None:
                printpf(results, fd, ppopt)
                fd.close()
    else:
        printpf(results, stdout, ppopt)

    ## save solved case
    if solvedcase:
        savecase(solvedcase, results)

    return results, success
Beispiel #18
0
def runpf_fast(Ybus,
               Yf,
               Yt,
               ref,
               pv,
               pq,
               on,
               ppc,
               ppopt=None,
               fname='',
               solvedcase=''):
    """Runs a power flow.

    Runs a power flow [full AC Newton's method by default] and optionally
    returns the solved values in the data matrices, a flag which is C{True} if
    the algorithm was successful in finding a solution, and the elapsed
    time in seconds. All input arguments are optional. If C{casename} is
    provided it specifies the name of the input data file or dict
    containing the power flow data. The default value is 'case9'.

    If the ppopt is provided it overrides the default PYPOWER options
    vector and can be used to specify the solution algorithm and output
    options among other things. If the 3rd argument is given the pretty
    printed output will be appended to the file whose name is given in
    C{fname}. If C{solvedcase} is specified the solved case will be written
    to a case file in PYPOWER format with the specified name. If C{solvedcase}
    ends with '.mat' it saves the case as a MAT-file otherwise it saves it
    as a Python-file.

    If the C{ENFORCE_Q_LIMS} options is set to C{True} [default is false] then
    if any generator reactive power limit is violated after running the AC
    power flow, the corresponding bus is converted to a PQ bus, with Qg at
    the limit, and the case is re-run. The voltage magnitude at the bus
    will deviate from the specified value in order to satisfy the reactive
    power limit. If the reference bus is converted to PQ, the first
    remaining PV bus will be used as the slack bus for the next iteration.
    This may result in the real power output at this generator being
    slightly off from the specified values.

    Enforcing of generator Q limits inspired by contributions from Mu Lin,
    Lincoln University, New Zealand (1/14/05).

    @author: Ray Zimmerman (PSERC Cornell)
    """
    ## default arguments

    ## options

    ## read data
    #ppc = loadcase(casedata)

    ## convert to internal indexing

    ppc["branch"][:, [0, 1]] -= 1
    ppc["bus"][:, 0] -= 1
    ppc["gen"][:, 0] -= 1

    baseMVA, bus, gen, branch = \
        ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"]

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

    #
    # generator info
    #print(gen[:, GEN_STATUS])
    #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()

    V0 = bus[:, VM] * exp(1j * 0.017453292519943295 * bus[:, VA])
    V0[gbus] = gen[on, VG] / abs(V0[gbus]) * V0[gbus]

    ## 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

    V, success, i = newtonpf_fast(Ybus, Sbus, V0, ref, pv, pq, ppopt)

    ## update data matrices with solution
    #bus, gen, branch = pfsoln(baseMVA, bus, gen, branch, Ybus, Yf, Yt, V, ref, pv, pq)
    bus[:, VM] = abs(V)
    bus[:, VA] = angle(V) * 180 / pi

    #UNTIL HERE
    ppc["et"] = time() - t0
    ppc["success"] = success

    ##-----  output results  -----
    ## convert back to original bus numbering & print results
    ppc["bus"], ppc["gen"], ppc["branch"] = bus, gen, branch

    ppc["branch"][:, [0, 1]] += 1
    ppc["bus"][:, 0] += 1
    ppc["gen"][:, 0] += 1

    return ppc, success, i
Beispiel #19
0
def _run_bfswpf(ppci, 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 ppci: matpower-style case data
    :param options: pf options
    :return: results (pypower style), success (flag about PF convergence)
    """
    time_start = time()  # starting pf calculation timing

    baseMVA, bus, gen, branch, ref, pv, pq, \
    on, gbus, V0 = _get_pf_variables_from_ppci(ppci)

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

    numba, makeYbus = _import_numba_extensions_if_flag_is_true(numba)

    nobus = bus.shape[0]
    nobranch = branch.shape[0]

    # generate Sbus
    Sbus = makeSbus(baseMVA, bus, gen)
    # generate results for original bus ordering
    # Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch)
    ppci, Ybus, Yf, Yt = _get_Y_bus(ppci, options, makeYbus, baseMVA, bus,
                                    branch)

    # creating network graph from list of branches
    bus_from = branch[:, F_BUS].real.astype(int)
    bus_to = branch[:, T_BUS].real.astype(int)
    G = csr_matrix((np.ones(nobranch), (bus_from, bus_to)),
                   shape=(nobus, nobus))
    # create spanning trees using breadth-first-search
    # TODO add efficiency warning if a network is heavy-meshed
    G_trees = []
    for refbus in ref:
        G_trees.append(csgraph.breadth_first_tree(G, refbus, directed=False))

        # depth-first-search bus ordering and generating Direct Load Flow matrix DLF = BCBV * BIBC
        ppci, DLF, buses_ordered_bfs_nets = _get_bibc_bcbv(
            ppci, options, bus, branch, G)

    # if there are trafos with phase-shift calculate Ybus without phase-shift for bfswpf
    any_trafo_shift = (branch[:, SHIFT] != 0).any()
    if any_trafo_shift:
        branch_noshift = branch.copy()
        branch_noshift[:, SHIFT] = 0
        Ybus_noshift, Yf_noshift, _ = makeYbus(baseMVA, bus, branch_noshift)
    else:
        Ybus_noshift = Ybus.copy()

    # get current injections for constant-current loads
    Ibus = _get_ibus(ppci)

    # #-----  run the power flow  -----
    V_final, success = _bfswpf(DLF, bus, gen, branch, baseMVA, Ybus_noshift,
                               Sbus, Ibus, V0, ref, pv, pq,
                               buses_ordered_bfs_nets, enforce_q_lims,
                               tolerance_kva, max_iteration, **kwargs)

    # if phase-shifting trafos are present adjust final state vector angles accordingly
    if calculate_voltage_angles and any_trafo_shift:
        brch_shift_mask = branch[:, SHIFT] != 0
        trafos_shift = dict(
            list(
                zip(
                    list(
                        zip(branch[brch_shift_mask, F_BUS].real.astype(int),
                            branch[brch_shift_mask, T_BUS].real.astype(int))),
                    branch[brch_shift_mask, SHIFT].real)))
        for trafo_ind, shift_degree in iteritems(trafos_shift):
            neti = 0
            # if multiple reference nodes, find in which network trafo is located
            if len(ref) > 0:
                for refbusi in range(len(ref)):
                    if trafo_ind[0] in buses_ordered_bfs_nets[refbusi]:
                        neti = refbusi
                        break
            G_tree = G_trees[neti]
            buses_ordered_bfs = buses_ordered_bfs_nets[neti]
            if (np.argwhere(buses_ordered_bfs == trafo_ind[0]) <
                    np.argwhere(buses_ordered_bfs == trafo_ind[1])):
                lv_bus = trafo_ind[1]
                shift_degree *= -1
            else:
                lv_bus = trafo_ind[0]

            buses_shifted_from_root = csgraph.breadth_first_order(
                G_tree, lv_bus, directed=True, return_predecessors=False)
            V_final[buses_shifted_from_root] *= np.exp(1j * np.pi / 180 *
                                                       shift_degree)

    # #----- 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)
    # bus, gen, branch = pfsoln_bfsw(baseMVA, bus, gen, branch, V_final, ref, pv, pq, BIBC, ysh_f,ysh_t,Iinj, Sbus)

    ppci["success"] = success

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

    return ppci, success
Beispiel #20
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