示例#1
0
def IwamotoNR(Ybus, Sbus, V0, Ibus, pv, pq, tol, max_it=15, robust=False):
    """
    Solves the power flow using a full Newton's method with the Iwamoto optimal step factor.
    Args:
        Ybus: Admittance matrix
        Sbus: Array of nodal power injections
        V0: Array of nodal voltages (initial solution)
        Ibus: Array of nodal current injections
        pv: Array with the indices of the PV buses
        pq: Array with the indices of the PQ buses
        tol: Tolerance
        max_it: Maximum number of iterations
        robust: Boolean variable for the use of the Iwamoto optimal step factor.
    Returns:
        Voltage solution, converged?, error, calculated power injections

    @author: Ray Zimmerman (PSERC Cornell)
    @Author: Santiago Penate Vera
    """
    start = time.time()

    # initialize
    converged = 0
    iter_ = 0
    V = V0
    Va = np.angle(V)
    Vm = np.abs(V)
    dVa = np.zeros_like(Va)
    dVm = np.zeros_like(Vm)

    # set up indexing for updating V
    pvpq = np.r_[pv, pq]
    npv = len(pv)
    npq = len(pq)

    # j1:j2 - V angle of pv buses
    j1 = 0
    j2 = npv + npq
    # j2:j3 - V mag of pq buses
    j3 = j2 + npq

    if (npq + npv) > 0:

        # generate lookup pvpq -> index pvpq (used in createJ)
        pvpq_lookup = np.zeros(np.max(Ybus.indices) + 1, dtype=int)
        pvpq_lookup[pvpq] = np.arange(len(pvpq))
        createJ = get_fastest_jacobian_function(pvpq, pq)

        # evaluate F(x0)
        Scalc = V * np.conj(Ybus * V - Ibus)
        mis = Scalc - Sbus  # compute the mismatch
        f = np.r_[mis[pvpq].real, mis[pq].imag]

        # check tolerance
        norm_f = np.linalg.norm(f, np.Inf)

        if norm_f < tol:
            converged = 1

        # do Newton iterations
        while not converged and iter_ < max_it:
            # update iteration counter
            iter_ += 1

            # evaluate Jacobian
            # J = Jacobian(Ybus, V, Ibus, pq, pvpq)
            J = _create_J_with_numba(Ybus, V, pvpq, pq, createJ, pvpq_lookup, npv, npq)

            # compute update step
            try:
                dx = linear_solver(J, f)
            except:
                print(J)
                converged = False
                iter_ = max_it + 1  # exit condition

            # reassign the solution vector
            dVa[pvpq] = dx[j1:j2]
            dVm[pq] = dx[j2:j3]
            dV = dVm * np.exp(1j * dVa)  # voltage mismatch

            # update voltage
            if robust:
                # if dV contains zeros will crash the second Jacobian derivative
                if not (dV == 0.0).any():
                    # calculate the optimal multiplier for enhanced convergence
                    mu_ = mu(Ybus, Ibus, J, f, dV, dx, pvpq, pq, npv, npq)
                else:
                    mu_ = 1.0
            else:
                mu_ = 1.0

            Vm -= mu_ * dVm
            Va -= mu_ * dVa

            V = Vm * np.exp(1j * Va)

            Vm = np.abs(V)  # update Vm and Va again in case
            Va = np.angle(V)  # we wrapped around with a negative Vm

            # evaluate F(x)
            Scalc = V * np.conj(Ybus * V - Ibus)
            mis = Scalc - Sbus  # complex power mismatch
            f = np.r_[mis[pvpq].real, mis[pq].imag]  # concatenate again

            # check for convergence
            norm_f = np.linalg.norm(f, np.Inf)

            if norm_f < tol:
                converged = 1
    else:
        norm_f = 0
        converged = True
        Scalc = Sbus

    end = time.time()
    elapsed = end - start

    return V, converged, norm_f, Scalc, iter_, elapsed
示例#2
0
def levenberg_marquardt_pf(Ybus, Sbus, V0, Ibus, pv, pq, tol, max_it=50):
    """
    Solves the power flow problem by the Levenberg-Marquardt power flow algorithm.
    It is usually better than Newton-Raphson, but it takes an order of magnitude more time to converge.
    Args:
        Ybus: Admittance matrix
        Sbus: Array of nodal power injections
        V0: Array of nodal voltages (initial solution)
        Ibus: Array of nodal current injections
        pv: Array with the indices of the PV buses
        pq: Array with the indices of the PQ buses
        tol: Tolerance
        max_it: Maximum number of iterations
    Returns:
        Voltage solution, converged?, error, calculated power injections

    @Author: Santiago Peñate Vera
    """
    start = time.time()

    # initialize
    V = V0
    Va = np.angle(V)
    Vm = np.abs(V)
    dVa = np.zeros_like(Va)
    dVm = np.zeros_like(Vm)

    # set up indexing for updating V
    pvpq = np.r_[pv, pq]
    npv = len(pv)
    npq = len(pq)

    # j1:j2 - V angle of pv and pq buses
    j1 = 0
    j2 = npv + npq
    # j2:j3 - V mag of pq buses
    j3 = j2 + npq

    if (npq + npv) > 0:
        normF = 100000
        update_jacobian = True
        converged = False
        iter_ = 0
        nu = 2.0
        lbmda = 0
        f_prev = 1e9  # very large number
        nn = 2 * npq + npv
        ii = np.linspace(0, nn-1, nn)
        Idn = sparse((np.ones(nn), (ii, ii)), shape=(nn, nn))  # csc_matrix identity

        # generate lookup pvpq -> index pvpq (used in createJ)
        pvpq_lookup = np.zeros(np.max(Ybus.indices) + 1, dtype=int)
        pvpq_lookup[pvpq] = np.arange(len(pvpq))
        createJ = get_fastest_jacobian_function(pvpq, pq)

        while not converged and iter_ < max_it:

            # evaluate Jacobian
            if update_jacobian:
                H = _create_J_with_numba(Ybus, V, pvpq, pq, createJ, pvpq_lookup, npv, npq)
                # H = Jacobian(Ybus, V, Ibus, pq, pvpq)

            # evaluate the solution error F(x0)
            Scalc = V * np.conj(Ybus * V - Ibus)
            mis = Scalc - Sbus  # compute the mismatch
            dz = np.r_[mis[pvpq].real, mis[pq].imag]  # mismatch in the Jacobian order

            # system matrix
            # H1 = H^t
            H1 = H.transpose().tocsr()

            # H2 = H1·H
            H2 = H1.dot(H)

            # set first value of lmbda
            if iter_ == 0:
                lbmda = 1e-3 * H2.diagonal().max()

            # compute system matrix A = H^T·H - lambda·I
            A = H2 + lbmda * Idn

            # right hand side
            # H^t·dz
            rhs = H1.dot(dz)

            # Solve the increment
            dx = linear_solver(A, rhs)

            # objective function to minimize
            f = 0.5 * dz.dot(dz)

            # decision function
            val = dx.dot(lbmda * dx + rhs)
            if val > 0.0:
                rho = (f_prev - f) / (0.5 * val)
            else:
                rho = -1.0

            # lambda update
            if rho >= 0:
                update_jacobian = True
                lbmda *= max([1.0 / 3.0, 1 - (2 * rho - 1) ** 3])
                nu = 2.0

                # reassign the solution vector
                dVa[pvpq] = dx[j1:j2]
                dVm[pq] = dx[j2:j3]

                # update Vm and Va again in case we wrapped around with a negative Vm
                Vm -= dVm
                Va -= dVa
                V = Vm * np.exp(1.0j * Va)

            else:
                update_jacobian = False
                lbmda *= nu
                nu *= 2.0

            # check convergence
            Scalc = V * np.conj(Ybus.dot(V))
            ds = Sbus - Scalc
            e = np.r_[ds[pvpq].real, ds[pq].imag]
            normF = 0.5 * np.dot(e, e)
            converged = normF < tol
            f_prev = f

            # update iteration counter
            iter_ += 1
    else:
        normF = 0
        converged = True
        Scalc = Sbus  # V * np.conj(Ybus * V - Ibus)
        iter_ = 0

    end = time.time()
    elapsed = end - start

    return V, converged, normF, Scalc, iter_, elapsed
示例#3
0
def corrector(Ybus,
              Ibus,
              Sbus,
              V0,
              pv,
              pq,
              lam0,
              Sxfr,
              Vprv,
              lamprv,
              z,
              step,
              parametrization,
              tol,
              max_it,
              pvpq_lookup,
              verbose,
              mu_0=1.0,
              acceleration_parameter=0.5):
    """
    Solves the corrector step of a continuation power flow using a full Newton method
    with selected parametrization scheme.

    solves for bus voltages and lambda given the full system admittance
    matrix (for all buses), the complex bus power injection vector (for
    all buses), the initial vector of complex bus voltages, and column
    vectors with the lists of bus indices for the swing bus, PV buses, and
    PQ buses, respectively. The bus voltage vector contains the set point
    for generator (including ref bus) buses, and the reference angle of the
    swing bus, as well as an initial guess for remaining magnitudes and
    angles.

     Uses default options if this parameter is not given. Returns the
     final complex voltages, a flag which indicates whether it converged or not,
     the number of iterations performed, and the final lambda.

    :param Ybus: Admittance matrix (CSC sparse)
    :param Ibus: Bus current injections
    :param Sbus: Bus power injections
    :param V0:  Bus initial voltages
    :param pv: list of pv nodes
    :param pq: list of pq nodes
    :param lam0: initial value of lambda (loading parameter)
    :param Sxfr: [delP+j*delQ] transfer/loading vector for all buses
    :param Vprv: final complex V corrector solution from previous continuation step
    :param lamprv: final lambda corrector solution from previous continuation step
    :param z: normalized predictor for all buses
    :param step: continuation step size
    :param parametrization:
    :param tol: Tolerance (p.u.)
    :param max_it: max iterations
    :param verbose: print information?
    :return: Voltage, converged, iterations, lambda, power error, calculated power
    """

    # initialize
    i = 0
    V = V0
    Va = np.angle(V)
    Vm = np.abs(V)
    lam = lam0  # set lam to initial lam0
    dVa = np.zeros_like(Va)
    dVm = np.zeros_like(Vm)
    dlam = 0

    # set up indexing for updating V
    npv = len(pv)
    npq = len(pq)
    pvpq = np.r_[pv, pq]
    nj = npv + npq * 2

    # j1:j2 - V angle of pv and pq buses
    j1 = 0
    j2 = npv + npq
    # j2:j3 - V mag of pq buses
    j3 = j2 + npq

    # evaluate F(x0, lam0), including Sxfr transfer/loading
    Scalc = V * np.conj(Ybus * V)
    mismatch = Scalc - Sbus - lam * Sxfr
    # F = np.r_[mismatch[pvpq].real, mismatch[pq].imag]

    # evaluate P(x0, lambda0)
    P = cpf_p(parametrization, step, z, V, lam, Vprv, lamprv, pv, pq, pvpq)

    # augment F(x,lambda) with P(x,lambda)
    F = np.r_[mismatch[pvpq].real, mismatch[pq].imag, P]

    # check tolerance
    normF = np.linalg.norm(F, np.Inf)
    converged = normF < tol
    if verbose:
        print('\nConverged!\n')

    dF_dlam = -np.r_[Sxfr[pvpq].real, Sxfr[pq].imag]

    # do Newton iterations
    while not converged and i < max_it:

        # update iteration counter
        i += 1

        # evaluate Jacobian
        J = _create_J_with_numba(Ybus, V, pvpq, pq, pvpq_lookup, npv, npq)

        dP_dV, dP_dlam = cpf_p_jac(parametrization, z, V, lam, Vprv, lamprv,
                                   pv, pq, pvpq)

        # augment J with real/imag - Sxfr and z^T
        '''
        J = [   J   dF_dlam 
              dP_dV dP_dlam ]
        '''
        J = vstack(
            [hstack([J, dF_dlam.reshape(nj, 1)]),
             hstack([dP_dV, dP_dlam])],
            format="csc")

        # compute update step
        dx = spsolve(J, F)
        dVa[pvpq] = dx[j1:j2]
        dVm[pq] = dx[j2:j3]
        dlam = dx[j3]

        # set the restoration values
        prev_Vm = Vm.copy()
        prev_Va = Va.copy()
        prev_lam = lam

        # set the values and correct with an adaptive mu if needed
        mu = mu_0  # ideally 1.0
        back_track_condition = True
        l_iter = 0
        normF_new = 0.0
        while back_track_condition and l_iter < max_it and mu > tol:

            # restore the previous values if we are backtracking (the first iteration is the normal NR procedure)
            if l_iter > 0:
                Va = prev_Va.copy()
                Vm = prev_Vm.copy()
                lam = prev_lam

            # update the variables from the solution
            Va -= mu * dVa
            Vm -= mu * dVm
            lam -= mu * dlam

            # update Vm and Va again in case we wrapped around with a negative Vm
            V = Vm * np.exp(1j * Va)

            # evaluate F(x, lam)
            Scalc = V * np.conj(Ybus * V)
            mismatch = Scalc - Sbus - lam * Sxfr

            # evaluate the parametrization function P(x, lambda)
            P = cpf_p(parametrization, step, z, V, lam, Vprv, lamprv, pv, pq,
                      pvpq)

            # compose the mismatch vector
            F = np.r_[mismatch[pvpq].real, mismatch[pq].imag, P]

            # check for convergence
            normF_new = np.linalg.norm(F, np.Inf)

            back_track_condition = normF_new > normF
            mu *= acceleration_parameter
            l_iter += 1

            if verbose:
                print('\n#3d        #10.3e', i, normF)

        if l_iter > 1 and back_track_condition:
            # this means that not even the backtracking was able to correct the solution so, restore and end
            Va = prev_Va.copy()
            Vm = prev_Vm.copy()
            V = Vm * np.exp(1.0j * Va)

            return V, converged, i, lam, normF, Scalc
        else:
            normF = normF_new

        converged = normF < tol
        if verbose:
            print('\nNewton'
                  's method corrector converged in ', i, ' iterations.\n')

    if verbose:
        if not converged:
            print('\nNewton method corrector did not converge in  ', i,
                  ' iterations.\n')

    return V, converged, i, lam, normF, Scalc
示例#4
0
def NR_LS(Ybus, Sbus, V0, Ibus, pv, pq, tol, max_it=15, acceleration_parameter=0.05, error_registry=None):
    """
    Solves the power flow using a full Newton's method with backtrack correction.
    @Author: Santiago Peñate Vera
    :param Ybus: Admittance matrix
    :param Sbus: Array of nodal power injections
    :param V0: Array of nodal voltages (initial solution)
    :param Ibus: Array of nodal current injections
    :param pv: Array with the indices of the PV buses
    :param pq: Array with the indices of the PQ buses
    :param tol: Tolerance
    :param max_it: Maximum number of iterations
    :param acceleration_parameter: parameter used to correct the "bad" iterations, should be be between 1e-3 ~ 0.5
    :param error_registry: list to store the error for plotting
    :return: Voltage solution, converged?, error, calculated power injections
    """
    start = time.time()

    # initialize
    back_track_counter = 0
    back_track_iterations = 0
    converged = 0
    iter_ = 0
    V = V0
    Va = np.angle(V)
    Vm = np.abs(V)
    dVa = np.zeros_like(Va)
    dVm = np.zeros_like(Vm)

    # set up indexing for updating V
    pvpq = np.r_[pv, pq]
    npv = len(pv)
    npq = len(pq)

    # j1:j2 - V angle of pv and pq buses
    j1 = 0
    j2 = npv + npq
    # j2:j3 - V mag of pq buses
    j3 = j2 + npq

    if (npq + npv) > 0:

        # generate lookup pvpq -> index pvpq (used in createJ)
        pvpq_lookup = np.zeros(np.max(Ybus.indices) + 1, dtype=int)
        pvpq_lookup[pvpq] = np.arange(len(pvpq))
        createJ = get_fastest_jacobian_function(pvpq, pq)

        # evaluate F(x0)
        Scalc = V * np.conj(Ybus * V - Ibus)
        dS = Scalc - Sbus  # compute the mismatch
        f = np.r_[dS[pvpq].real, dS[pq].imag]

        # check tolerance
        norm_f = 0.5 * f.dot(f)

        if error_registry is not None:
            error_registry.append(norm_f)

        if norm_f < tol:
            converged = 1

        # do Newton iterations
        while not converged and iter_ < max_it:
            # update iteration counter
            iter_ += 1

            # evaluate Jacobian
            # J = Jacobian(Ybus, V, Ibus, pq, pvpq)
            J = _create_J_with_numba(Ybus, V, pvpq, pq, createJ, pvpq_lookup, npv, npq)

            # compute update step
            dx = linear_solver(J, f)

            # reassign the solution vector
            dVa[pvpq] = dx[j1:j2]
            dVm[pq] = dx[j2:j3]

            # update voltage the Newton way (mu=1)
            mu_ = 1.0
            Vm -= mu_ * dVm
            Va -= mu_ * dVa
            Vnew = Vm * np.exp(1.0j * Va)

            # compute the mismatch function f(x_new)
            Scalc = Vnew * np.conj(Ybus * Vnew - Ibus)
            dS = Scalc - Sbus  # complex power mismatch
            f_new = np.r_[dS[pvpq].real, dS[pq].imag]  # concatenate to form the mismatch function
            norm_f_new = 0.5 * f_new.dot(f_new)

            if error_registry is not None:
                error_registry.append(norm_f_new)

            cond = norm_f_new > norm_f  # condition to back track (no improvement at all)

            if not cond:
                back_track_counter += 1

            l_iter = 0
            while not cond and l_iter < 10 and mu_ > 0.01:
                # line search back
                # update voltage with a closer value to the last value in the Jacobian direction
                mu_ *= acceleration_parameter
                Vm -= mu_ * dVm
                Va -= mu_ * dVa
                Vnew = Vm * np.exp(1.0j * Va)

                # compute the mismatch function f(x_new)
                Scalc = Vnew * np.conj(Ybus * Vnew - Ibus)
                dS = Scalc - Sbus  # complex power mismatch
                f_new = np.r_[dS[pvpq].real, dS[pq].imag]  # concatenate to form the mismatch function

                norm_f_new = 0.5 * f_new.dot(f_new)

                cond = norm_f_new > norm_f

                if error_registry is not None:
                    error_registry.append(norm_f_new)

                l_iter += 1
                back_track_iterations += 1

            # update calculation variables
            V = Vnew
            f = f_new

            # check for convergence
            if l_iter == 0:
                # no correction loop executed, hence compute the error fresh
                norm_f = 0.5 * f_new.dot(f_new)
            else:
                # pick the latest computer error in the correction loop
                norm_f = norm_f_new

            if error_registry is not None:
                error_registry.append(norm_f)

            if norm_f < tol:
                converged = 1

    else:
        norm_f = 0
        converged = True
        Scalc = Sbus

    end = time.time()
    elapsed = end - start

    return V, converged, norm_f, Scalc, iter_, elapsed
示例#5
0
def predictor(V, Ibus, lam, Ybus, Sxfr, pv, pq, step, z, Vprv, lamprv,
              parametrization: CpfParametrization, pvpq_lookup):
    """
    Computes a prediction (approximation) to the next solution of the
    continuation power flow using a normalized tangent predictor.
    :param V: complex bus voltage vector at current solution
    :param Ibus:
    :param lam: scalar lambda value at current solution
    :param Ybus: complex bus admittance matrix
    :param Sxfr: complex vector of scheduled transfers (difference between bus injections in base and target cases)
    :param pv: vector of indices of PV buses
    :param pq: vector of indices of PQ buses
    :param step: continuation step length
    :param z: normalized tangent prediction vector from previous step
    :param Vprv: complex bus voltage vector at previous solution
    :param lamprv: scalar lambda value at previous solution
    :param parametrization: Value of cpf parametrization option.
    :return: V0 : predicted complex bus voltage vector
             LAM0 : predicted lambda continuation parameter
             Z : the normalized tangent prediction vector
    """

    # sizes
    nb = len(V)
    npv = len(pv)
    npq = len(pq)
    pvpq = np.r_[pv, pq]
    nj = npv + npq * 2

    # compute Jacobian for the power flow equations
    J = _create_J_with_numba(Ybus, V, pvpq, pq, pvpq_lookup, npv, npq)

    dF_dlam = -np.r_[Sxfr[pvpq].real, Sxfr[pq].imag]

    dP_dV, dP_dlam = cpf_p_jac(parametrization, z, V, lam, Vprv, lamprv, pv,
                               pq, pvpq)

    # linear operator for computing the tangent predictor
    '''
    J2 = [   J   dF_dlam
           dP_dV dP_dlam ]
    '''
    J2 = vstack(
        [hstack([J, dF_dlam.reshape(nj, 1)]),
         hstack([dP_dV, dP_dlam])],
        format="csc")

    Va_prev = np.angle(V)
    Vm_prev = np.abs(V)

    # compute normalized tangent predictor
    s = np.zeros(npv + 2 * npq + 1)

    # increase in the direction of lambda
    s[npv + 2 * npq] = 1

    # tangent vector
    z[np.r_[pvpq, nb + pq, 2 * nb]] = spsolve(J2, s)

    # normalize_string tangent predictor  (dividing by the euclidean norm)
    z /= np.linalg.norm(z)

    Va0 = Va_prev
    Vm0 = Vm_prev
    # lam0 = lam

    # prediction for next step
    Va0[pvpq] = Va_prev[pvpq] + step * z[pvpq]
    Vm0[pq] = Vm_prev[pq] + step * z[pq + nb]
    lam0 = lam + step * z[2 * nb]
    V0 = Vm0 * np.exp(1j * Va0)

    return V0, lam0, z
def corrector(Ybus, Ibus, Sbus, V0, pv, pq, lam0, Sxfr, Vprv, lamprv, z, step,
              parametrization, tol, max_it, pvpq_lookup, verbose):
    """
    Solves the corrector step of a continuation power flow using a full Newton method
    with selected parametrization scheme.

    solves for bus voltages and lambda given the full system admittance
    matrix (for all buses), the complex bus power injection vector (for
    all buses), the initial vector of complex bus voltages, and column
    vectors with the lists of bus indices for the swing bus, PV buses, and
    PQ buses, respectively. The bus voltage vector contains the set point
    for generator (including ref bus) buses, and the reference angle of the
    swing bus, as well as an initial guess for remaining magnitudes and
    angles.

     Uses default options if this parameter is not given. Returns the
     final complex voltages, a flag which indicates whether it converged or not,
     the number of iterations performed, and the final lambda.

    :param Ybus: Admittance matrix (CSC sparse)
    :param Ibus: Bus current injections
    :param Sbus: Bus power injections
    :param V0:  Bus initial voltages
    :param pv: list of pv nodes
    :param pq: list of pq nodes
    :param lam0: initial value of lambda (loading parameter)
    :param Sxfr: [delP+j*delQ] transfer/loading vector for all buses
    :param Vprv: final complex V corrector solution from previous continuation step
    :param lamprv: final lambda corrector solution from previous continuation step
    :param z: normalized predictor for all buses
    :param step: continuation step size
    :param parametrization:
    :param tol: Tolerance (p.u.)
    :param max_it: max iterations
    :param verbose: print information?
    :return: Voltage, converged, iterations, lambda, power error, calculated power
    """

    # initialize
    converged = False
    i = 0
    V = V0
    Va = angle(V)
    Vm = np.abs(V)
    lam = lam0  # set lam to initial lam0

    # set up indexing for updating V
    npv = len(pv)
    npq = len(pq)
    pvpq = r_[pv, pq]
    nj = npv + npq * 2

    # j1:j2 - V angle of pv and pq buses
    j1 = 0
    j2 = npv + npq
    # j2:j3 - V mag of pq buses
    j3 = j2 + npq

    # evaluate F(x0, lam0), including Sxfr transfer/loading
    Scalc = V * np.conj(Ybus * V)
    mismatch = Scalc - Sbus - lam * Sxfr
    # F = r_[mismatch[pvpq].real, mismatch[pq].imag]

    # evaluate P(x0, lambda0)
    P = cpf_p(parametrization, step, z, V, lam, Vprv, lamprv, pv, pq, pvpq)

    # augment F(x,lambda) with P(x,lambda)
    F = r_[mismatch[pvpq].real, mismatch[pq].imag, P]

    # check tolerance
    normF = linalg.norm(F, Inf)
    converged = normF < tol
    if verbose:
        print('\nConverged!\n')

    dF_dlam = -r_[Sxfr[pvpq].real, Sxfr[pq].imag]

    # do Newton iterations
    while not converged and i < max_it:

        # update iteration counter
        i += 1

        # evaluate Jacobian
        J = _create_J_with_numba(Ybus, V, pvpq, pq, pvpq_lookup, npv, npq)

        dP_dV, dP_dlam = cpf_p_jac(parametrization, z, V, lam, Vprv, lamprv,
                                   pv, pq, pvpq)

        # augment J with real/imag - Sxfr and z^T
        '''
        J = [   J   dF_dlam 
              dP_dV dP_dlam ]
        '''
        J = vstack(
            [hstack([J, dF_dlam.reshape(nj, 1)]),
             hstack([dP_dV, dP_dlam])],
            format="csc")

        # compute update step
        dx = spsolve(J, F)

        # update voltage
        if npv:
            Va[pvpq] -= dx[j1:j2]
        if npq:
            Vm[pq] -= dx[j2:j3]

        # update lambda
        lam -= dx[j3]

        # update Vm and Va again in case we wrapped around with a negative Vm
        V = Vm * exp(1j * Va)
        Vm = np.abs(V)
        Va = angle(V)

        # evaluate F(x, lam)
        Scalc = V * conj(Ybus * V)
        mismatch = Scalc - Sbus - lam * Sxfr

        # evaluate the parametrization function P(x, lambda)
        P = cpf_p(parametrization, step, z, V, lam, Vprv, lamprv, pv, pq, pvpq)

        # compose the mismatch vector
        F = r_[mismatch[pvpq].real, mismatch[pq].imag, P]

        # check for convergence
        normF = linalg.norm(F, Inf)
        if verbose:
            print('\n#3d        #10.3e', i, normF)

        converged = normF < tol
        if verbose:
            print('\nNewton'
                  's method corrector converged in ', i, ' iterations.\n')

    if verbose:
        if not converged:
            print('\nNewton method corrector did not converge in  ', i,
                  ' iterations.\n')

    return V, converged, i, lam, normF, Scalc
示例#7
0
def levenberg_marquardt_pf(
        Ybus,
        Sbus_,
        V0,
        Ibus,
        pv_,
        pq_,
        Qmin,
        Qmax,
        tol,
        max_it=50,
        control_q=ReactivePowerControlMode.NoControl
) -> NumericPowerFlowResults:
    """
    Solves the power flow problem by the Levenberg-Marquardt power flow algorithm.
    It is usually better than Newton-Raphson, but it takes an order of magnitude more time to converge.
    Args:
        Ybus: Admittance matrix
        Sbus_: Array of nodal power injections
        V0: Array of nodal voltages (initial solution)
        Ibus: Array of nodal current injections
        pv_: Array with the indices of the PV buses
        pq_: Array with the indices of the PQ buses
        Qmin:
        Qmax:
        tol: Tolerance
        max_it: Maximum number of iterations
        control_q: Type of reactive power control
    Returns:
        Voltage solution, converged?, error, calculated power injections

    @Author: Santiago Peñate Vera
    """
    start = time.time()

    # initialize
    Sbus = Sbus_.copy()
    V = V0
    Va = np.angle(V)
    Vm = np.abs(V)
    dVa = np.zeros_like(Va)
    dVm = np.zeros_like(Vm)

    # set up indexing for updating V
    pq = pq_.copy()
    pv = pv_.copy()
    pvpq = np.r_[pv, pq]
    npv = len(pv)
    npq = len(pq)
    npvpq = npq + npv

    if npvpq > 0:
        normF = 100000
        update_jacobian = True
        converged = False
        iter_ = 0
        nu = 2.0
        lbmda = 0
        f_prev = 1e9  # very large number
        nn = 2 * npq + npv
        Idn = sp.diags(np.ones(nn))  # csc_matrix identity

        # generate lookup pvpq -> index pvpq (used in createJ)
        pvpq_lookup = np.zeros(np.max(Ybus.indices) + 1, dtype=int)
        pvpq_lookup[pvpq] = np.arange(len(pvpq))

        while not converged and iter_ < max_it:

            # evaluate Jacobian
            if update_jacobian:
                H = _create_J_with_numba(Ybus, V, pvpq, pq, pvpq_lookup, npv,
                                         npq)
                # H = Jacobian(Ybus, V, Ibus, pq, pvpq)

            # evaluate the solution error F(x0)
            Scalc = V * np.conj(Ybus * V - Ibus)
            mis = Scalc - Sbus  # compute the mismatch
            dz = np.r_[mis[pvpq].real,
                       mis[pq].imag]  # mismatch in the Jacobian order

            # system matrix
            # H1 = H^t
            H1 = H.transpose().tocsr()

            # H2 = H1·H
            H2 = H1.dot(H)

            # set first value of lmbda
            if iter_ == 0:
                lbmda = 1e-3 * H2.diagonal().max()

            # compute system matrix A = H^T·H - lambda·I
            A = H2 + lbmda * Idn

            # right hand side
            # H^t·dz
            rhs = H1.dot(dz)

            # Solve the increment
            dx = linear_solver(A, rhs)

            # objective function to minimize
            f = 0.5 * dz.dot(dz)

            # decision function
            val = dx.dot(lbmda * dx + rhs)
            if val > 0.0:
                rho = (f_prev - f) / (0.5 * val)
            else:
                rho = -1.0

            # lambda update
            if rho >= 0:
                update_jacobian = True
                lbmda *= max([1.0 / 3.0, 1 - (2 * rho - 1)**3])
                nu = 2.0

                # reassign the solution vector
                dVa[pvpq] = dx[:npvpq]
                dVm[pq] = dx[npvpq:]

                # update Vm and Va again in case we wrapped around with a negative Vm
                Vm -= dVm
                Va -= dVa
                V = Vm * np.exp(1.0j * Va)

            else:
                update_jacobian = False
                lbmda *= nu
                nu *= 2.0

            # check convergence
            Scalc = V * np.conj(Ybus.dot(V))
            ds = Sbus - Scalc
            e = np.r_[ds[pvpq].real, ds[pq].imag]
            normF = 0.5 * np.dot(e, e)

            # review reactive power limits
            # it is only worth checking Q limits with a low error
            # since with higher errors, the Q values may be far from realistic
            # finally, the Q control only makes sense if there are pv nodes
            if control_q != ReactivePowerControlMode.NoControl and normF < 1e-2 and npv > 0:

                # check and adjust the reactive power
                # this function passes pv buses to pq when the limits are violated,
                # but not pq to pv because that is unstable
                n_changes, Scalc, Sbus, pv, pq, pvpq = control_q_inside_method(
                    Scalc, Sbus, pv, pq, pvpq, Qmin, Qmax)

                if n_changes > 0:
                    # adjust internal variables to the new pq|pv values
                    npv = len(pv)
                    npq = len(pq)
                    npvpq = npv + npq
                    pvpq_lookup = np.zeros(np.max(Ybus.indices) + 1, dtype=int)
                    pvpq_lookup[pvpq] = np.arange(npvpq)

                    nn = 2 * npq + npv
                    ii = np.linspace(0, nn - 1, nn)
                    Idn = sparse((np.ones(nn), (ii, ii)),
                                 shape=(nn, nn))  # csc_matrix identity

                    # recompute the error based on the new Sbus
                    ds = Sbus - Scalc
                    e = np.r_[ds[pvpq].real, ds[pq].imag]
                    normF = 0.5 * np.dot(e, e)

            converged = normF < tol
            f_prev = f

            # update iteration counter
            iter_ += 1
    else:
        normF = 0
        converged = True
        Scalc = Sbus  # V * np.conj(Ybus * V - Ibus)
        iter_ = 0

    end = time.time()
    elapsed = end - start

    return NumericPowerFlowResults(V, converged, normF, Scalc, None, None,
                                   None, iter_, elapsed)
示例#8
0
def IwamotoNR(Ybus,
              Sbus_,
              V0,
              Ibus,
              pv_,
              pq_,
              Qmin,
              Qmax,
              tol,
              max_it=15,
              control_q=ReactivePowerControlMode.NoControl,
              robust=False) -> NumericPowerFlowResults:
    """
    Solves the power flow using a full Newton's method with the Iwamoto optimal step factor.
    Args:
        Ybus: Admittance matrix
        Sbus_: Array of nodal power injections
        V0: Array of nodal voltages (initial solution)
        Ibus: Array of nodal current injections
        pv_: Array with the indices of the PV buses
        pq_: Array with the indices of the PQ buses
        tol: Tolerance
        max_it: Maximum number of iterations
        robust: use of the Iwamoto optimal step factor?.
    Returns:
        Voltage solution, converged?, error, calculated power injections

    @Author: Santiago Penate Vera
    """
    start = time.time()

    # initialize
    Sbus = Sbus_.copy()
    converged = 0
    iter_ = 0
    V = V0
    Va = np.angle(V)
    Vm = np.abs(V)
    dVa = np.zeros_like(Va)
    dVm = np.zeros_like(Vm)

    # set up indexing for updating V
    pq = pq_.copy()
    pv = pv_.copy()
    pvpq = np.r_[pv, pq]
    npv = len(pv)
    npq = len(pq)
    npvpq = npv + npq

    if npvpq > 0:

        # generate lookup pvpq -> index pvpq (used in createJ)
        pvpq_lookup = np.zeros(np.max(Ybus.indices) + 1, dtype=int)
        pvpq_lookup[pvpq] = np.arange(len(pvpq))

        # evaluate F(x0)
        Scalc = V * np.conj(Ybus * V - Ibus)
        mis = Scalc - Sbus  # compute the mismatch
        f = np.r_[mis[pvpq].real, mis[pq].imag]

        # check tolerance
        norm_f = np.linalg.norm(f, np.Inf)
        converged = norm_f < tol

        # do Newton iterations
        while not converged and iter_ < max_it:
            # update iteration counter
            iter_ += 1

            # evaluate Jacobian
            # J = Jacobian(Ybus, V, Ibus, pq, pvpq)
            J = _create_J_with_numba(Ybus, V, pvpq, pq, pvpq_lookup, npv, npq)

            # compute update step
            try:
                dx = linear_solver(J, f)
            except:
                print(J)
                converged = False
                iter_ = max_it + 1  # exit condition
                end = time.time()
                elapsed = end - start
                return NumericPowerFlowResults(V, converged, norm_f, Scalc,
                                               None, None, None, iter_,
                                               elapsed)

            # assign the solution vector
            dVa[pvpq] = dx[:npvpq]
            dVm[pq] = dx[npvpq:]
            dV = dVm * np.exp(1j * dVa)  # voltage mismatch

            # update voltage
            if robust:
                # if dV contains zeros will crash the second Jacobian derivative
                if not (dV == 0.0).any():
                    # calculate the optimal multiplier for enhanced convergence
                    mu_ = mu(Ybus, Ibus, J, pvpq_lookup, f, dV, dx, pvpq, pq,
                             npv, npq)
                else:
                    mu_ = 1.0
            else:
                mu_ = 1.0

            Vm -= mu_ * dVm
            Va -= mu_ * dVa
            V = Vm * np.exp(1j * Va)

            Vm = np.abs(V)  # update Vm and Va again in case
            Va = np.angle(V)  # we wrapped around with a negative Vm

            # evaluate F(x)
            Scalc = V * np.conj(Ybus * V - Ibus)
            mis = Scalc - Sbus  # complex power mismatch
            f = np.r_[mis[pvpq].real, mis[pq].imag]  # concatenate again

            # check for convergence
            norm_f = np.linalg.norm(f, np.Inf)

            # review reactive power limits
            # it is only worth checking Q limits with a low error
            # since with higher errors, the Q values may be far from realistic
            # finally, the Q control only makes sense if there are pv nodes
            if control_q != ReactivePowerControlMode.NoControl and norm_f < 1e-2 and npv > 0:

                # check and adjust the reactive power
                # this function passes pv buses to pq when the limits are violated,
                # but not pq to pv because that is unstable
                n_changes, Scalc, Sbus, pv, pq, pvpq = control_q_inside_method(
                    Scalc, Sbus, pv, pq, pvpq, Qmin, Qmax)

                if n_changes > 0:
                    # adjust internal variables to the new pq|pv values
                    npv = len(pv)
                    npq = len(pq)
                    npvpq = npv + npq
                    pvpq_lookup = np.zeros(np.max(Ybus.indices) + 1, dtype=int)
                    pvpq_lookup[pvpq] = np.arange(npvpq)

                    # recompute the error based on the new Sbus
                    dS = Scalc - Sbus  # complex power mismatch
                    f = np.r_[dS[pvpq].real, dS[
                        pq].imag]  # concatenate to form the mismatch function
                    norm_f = np.linalg.norm(f, np.inf)

            # check convergence
            converged = norm_f < tol

    else:
        norm_f = 0
        converged = True
        Scalc = Sbus

    end = time.time()
    elapsed = end - start

    return NumericPowerFlowResults(V, converged, norm_f, Scalc, None, None,
                                   None, iter_, elapsed)
示例#9
0
def NR_LS(
        Ybus,
        Sbus_,
        V0,
        Ibus,
        pv_,
        pq_,
        Qmin,
        Qmax,
        tol,
        max_it=15,
        mu_0=1.0,
        acceleration_parameter=0.05,
        error_registry=None,
        control_q=ReactivePowerControlMode.NoControl
) -> NumericPowerFlowResults:
    """
    Solves the power flow using a full Newton's method with backtrack correction.
    @Author: Santiago Peñate Vera
    :param Ybus: Admittance matrix
    :param Sbus: Array of nodal power injections
    :param V0: Array of nodal voltages (initial solution)
    :param Ibus: Array of nodal current injections
    :param pv_: Array with the indices of the PV buses
    :param pq_: Array with the indices of the PQ buses
    :param Qmin: array of lower reactive power limits per bus
    :param Qmax: array of upper reactive power limits per bus
    :param tol: Tolerance
    :param max_it: Maximum number of iterations
    :param mu_0: initial acceleration value
    :param acceleration_parameter: parameter used to correct the "bad" iterations, should be be between 1e-3 ~ 0.5
    :param error_registry: list to store the error for plotting
    :param control_q: Control reactive power
    :return: Voltage solution, converged?, error, calculated power injections
    """
    start = time.time()

    # initialize
    iter_ = 0
    Sbus = Sbus_.copy()
    V = V0
    Va = np.angle(V)
    Vm = np.abs(V)
    dVa = np.zeros_like(Va)
    dVm = np.zeros_like(Vm)

    # set up indexing for updating V
    pq = pq_.copy()
    pv = pv_.copy()
    pvpq = np.r_[pv, pq]
    npv = len(pv)
    npq = len(pq)
    npvpq = npv + npq

    # j1 = 0
    # j2 = npv + npq  # j1:j2 - V angle of pv and pq buses
    # j3 = j2 + npq  # j2:j3 - V mag of pq buses

    if npvpq > 0:

        # generate lookup pvpq -> index pvpq (used in createJ)
        pvpq_lookup = np.zeros(np.max(Ybus.indices) + 1, dtype=int)
        pvpq_lookup[pvpq] = np.arange(npvpq)

        # evaluate F(x0)
        Scalc = V * np.conj(Ybus * V - Ibus)
        dS = Scalc - Sbus  # compute the mismatch
        f = np.r_[dS[pvpq].real, dS[pq].imag]
        norm_f = np.linalg.norm(f, np.inf)
        converged = norm_f < tol

        if error_registry is not None:
            error_registry.append(norm_f)

        # to be able to compare
        Ybus.sort_indices()

        # do Newton iterations
        while not converged and iter_ < max_it:
            # update iteration counter
            iter_ += 1

            # evaluate Jacobian
            # J = Jacobian(Ybus, V, Ibus, pq, pvpq)
            J = _create_J_with_numba(Ybus, V, pvpq, pq, pvpq_lookup, npv, npq)

            # compute update step
            dx = linear_solver(J, f)

            # reassign the solution vector
            dVa[pvpq] = dx[:npvpq]
            dVm[pq] = dx[npvpq:]

            # set the restoration values
            prev_Vm = Vm.copy()
            prev_Va = Va.copy()

            # set the values and correct with an adaptive mu if needed
            mu = mu_0  # ideally 1.0
            back_track_condition = True
            l_iter = 0
            norm_f_new = 0.0
            while back_track_condition and l_iter < max_it and mu > tol:

                # restore the previous values if we are backtracking (the first iteration is the normal NR procedure)
                if l_iter > 0:
                    Va = prev_Va.copy()
                    Vm = prev_Vm.copy()

                # update voltage the Newton way
                Vm -= mu * dVm
                Va -= mu * dVa
                V = Vm * np.exp(1.0j * Va)

                # compute the mismatch function f(x_new)
                Scalc = V * np.conj(Ybus * V - Ibus)
                dS = Scalc - Sbus  # complex power mismatch
                f = np.r_[
                    dS[pvpq].real,
                    dS[pq].imag]  # concatenate to form the mismatch function
                norm_f_new = np.linalg.norm(f, np.inf)

                back_track_condition = norm_f_new > norm_f
                mu *= acceleration_parameter
                l_iter += 1

            if l_iter > 1 and back_track_condition:
                # this means that not even the backtracking was able to correct the solution so, restore and end
                Va = prev_Va.copy()
                Vm = prev_Vm.copy()
                V = Vm * np.exp(1.0j * Va)

                end = time.time()
                elapsed = end - start
                return NumericPowerFlowResults(V, converged, norm_f_new, Scalc,
                                               None, None, None, iter_,
                                               elapsed)
            else:
                norm_f = norm_f_new

            # review reactive power limits
            # it is only worth checking Q limits with a low error
            # since with higher errors, the Q values may be far from realistic
            # finally, the Q control only makes sense if there are pv nodes
            if control_q != ReactivePowerControlMode.NoControl and norm_f < 1e-2 and npv > 0:

                # check and adjust the reactive power
                # this function passes pv buses to pq when the limits are violated,
                # but not pq to pv because that is unstable
                n_changes, Scalc, Sbus, pv, pq, pvpq = control_q_inside_method(
                    Scalc, Sbus, pv, pq, pvpq, Qmin, Qmax)

                if n_changes > 0:

                    # adjust internal variables to the new pq|pv values
                    npv = len(pv)
                    npq = len(pq)
                    npvpq = npv + npq
                    pvpq_lookup = np.zeros(np.max(Ybus.indices) + 1, dtype=int)
                    pvpq_lookup[pvpq] = np.arange(npvpq)

                    # recompute the error based on the new Sbus
                    dS = Scalc - Sbus  # complex power mismatch
                    f = np.r_[dS[pvpq].real, dS[
                        pq].imag]  # concatenate to form the mismatch function
                    norm_f = np.linalg.norm(f, np.inf)

            if error_registry is not None:
                error_registry.append(norm_f)

            converged = norm_f < tol

    else:
        norm_f = 0
        converged = True
        Scalc = Sbus

    end = time.time()
    elapsed = end - start

    return NumericPowerFlowResults(V, converged, norm_f, Scalc, None, None,
                                   None, iter_, elapsed)