def Jacobian(Ybus, V, Ibus, pq, pvpq): """ Computes the system Jacobian matrix Args: Ybus: Admittance matrix V: Array of nodal voltages Ibus: Array of nodal current injections pq: Array with the indices of the PQ buses pvpq: Array with the indices of the PV and PQ buses Returns: The system Jacobian matrix """ # dS_dVm, dS_dVa = dSbus_dV(Ybus, V, Ibus) # compute the derivatives I = Ybus * V - Ibus diagV = diags(V) diagI = diags(I) diagVnorm = diags(V / np.abs(V)) dS_dVm = diagV * conj(Ybus * diagVnorm) + conj(diagI) * diagVnorm dS_dVa = 1j * diagV * conj(diagI - Ybus * diagV) J11 = dS_dVa[array([pvpq]).T, pvpq].real J12 = dS_dVm[array([pvpq]).T, pq].real J21 = dS_dVa[array([pq]).T, pvpq].imag J22 = dS_dVm[array([pq]).T, pq].imag J = vstack_sp([hstack_sp([J11, J12]), hstack_sp([J21, J22])], format="csr") return J
def Jrect(G, B, V, pvpq, pqpv, pq, pv): e = V.real f = V.imag ediag = diags(e) fdiag = diags(f) J1 = ediag * G + fdiag * B + diags(G * e - B * f) J2 = fdiag * G - ediag * B + diags(G * f + B * e) J3 = fdiag * G - ediag * B - diags(G * f + B * e) J4 = -ediag * G - fdiag * B + diags(G * e - B * f) J5 = diags(2 * e).tocsc() J6 = diags(2 * f).tocsc() J = vstack_sp([ hstack_sp([J1[np.ix_(pvpq, pvpq)], J2[np.ix_(pvpq, pqpv)]]), hstack_sp([J3[np.ix_(pq, pvpq)], J4[np.ix_(pq, pqpv)]]), hstack_sp([J5[np.ix_(pv, pvpq)], J6[np.ix_(pv, pqpv)]]) ], format="csc") return J
def Jacobian_I(Ybus, V, pq, pvpq): """ Computes the system Jacobian matrix Args: Ybus: Admittance matrix V: Array of nodal voltages pq: Array with the indices of the PQ buses pvpq: Array with the indices of the PV and PQ buses Returns: The system Jacobian matrix in current equations """ dI_dVm = Ybus * diags(V / np.abs(V)) dI_dVa = 1j * (Ybus * diags(V)) J11 = dI_dVa[array([pvpq]).T, pvpq].real J12 = dI_dVm[array([pvpq]).T, pq].real J21 = dI_dVa[array([pq]).T, pvpq].imag J22 = dI_dVm[array([pq]).T, pq].imag J = vstack_sp([hstack_sp([J11, J12]), hstack_sp([J21, J22])], format="csr") return J
from scipy.sparse import hstack as hstack_sp, vstack as vstack_sp circuit = MultiCircuit() b1 = Bus(name='B1') b2 = Bus(name='B2') l2 = Load(P=1, Q=1) br1 = Branch(b1, b2, r=0.1, x=1) circuit.add_bus(b1) circuit.add_bus(b2) circuit.add_load(b2, l2) circuit.add_branch(br1) islands = circuit.compile_snapshot().compute() npv = len(islands[0].pv) npq = len(islands[0].pq) pvpq = np.r_[islands[0].pv, islands[0].pq] J = Jacobian(Ybus=islands[0].Ybus, V=islands[0].Vbus, Ibus=islands[0].Ibus, pq=islands[0].pq, pvpq=pvpq) ek = np.zeros((1, npv + npq + npq + 1)) ek[0, -1] = 1 K = np.zeros((npv + npq + npq, 1)) J2 = vstack_sp([hstack_sp([J, K]), ek], format="csr") pass
def ASD1(Ybus, S0, V0, I0, pv, pq, vd, tol, max_it=15, gamma=0.5): """ Alternate Search Directions Power Flow As defined in the paper: Unified formulation of a family of iterative solvers for power system analysis by Domenico Borzacchiello et. Al. :param Ybus: Admittance matrix :param S0: Power injections :param V0: Initial Voltage Vector :param I0: Current injections @V=1.0 p.u. :param pv: Array of PV bus indices :param pq: Array of PQ bus indices :param vd: Array of Slack bus indices :param tol: Tolerance :param max_it: Maximum number of iterations :param gamma: Reactive power acceleration factor :return: V, converged, normF, Scalc, iterations, elapsed """ start = time.time() # initialize V = V0.copy() Vset_pv = np.abs(V0[pv]) Scalc = S0.copy() Scalc[pv] = Scalc[pv].real + 0j normF = 1e20 iterations = 0 converged = False n = len(V0) npq = len(pq) npv = len(pv) pqpv = np.r_[pq, pv] npqpv = len(pqpv) # kron if npv: Y11 = Ybus[pq, :][:, pq] Y12 = Ybus[pq, :][:, pv] Y21 = Ybus[pv, :][:, pq] Y22 = Ybus[pv, :][:, pv] Ykron = Y22 + Y21 * (spsolve(Y11, Y12)) Yred = vstack_sp((hstack_sp((Y11, Y12)), hstack_sp((Y21, Y22)))) else: Yred = Ybus[pq, :][:, pq] # reduced system Sred = S0[pqpv] I0_red = I0[pqpv] Yslack = -Ybus[np.ix_(pqpv, vd)] # yes, it is the negative of this Vslack = V0[vd] # compute alpha Vabs = np.ones(npqpv) alpha = sp.diags(np.conj(Sred) / (Vabs * Vabs)) # compute Y-alpha Y_alpha = (Yred - alpha) # compute beta as a vector B = Y_alpha.diagonal() beta = diags(B) Y_beta = Yred - beta # get the first voltage approximation V0, or simply V(l Ivd = Yslack * Vslack # slack currents V_l = spsolve(Y_alpha, Ivd) # slack voltages influence V_l = V0[pqpv] while not converged and iterations < max_it: iterations += 1 # Global step rhs_global = np.conj(Sred) / np.conj(V_l) - alpha * V_l + I0_red + Ivd V_l12 = spsolve(Y_alpha, rhs_global) # PV correction if npv: V_pv = V_l12[npq:] V_pv_abs = np.abs(V_pv) dV_l12 = (Vset_pv - V_pv_abs) * V_pv / V_pv_abs dI_l12 = Ykron * dV_l12 dQ_l12 = (V_pv * np.conj(dI_l12)).imag # correct the reactive power Sred[npq:] = Sred[npq:].real + 1j * ( Sred[npq:].imag + gamma * dQ_l12) # assign the reactive power V_l12[npq:] += dV_l12 # correct the voltage # local step A = (Y_beta * V_l12 - I0_red - Ivd) / B Sigma = -np.conj(Sred) / (A * np.conj(A) * B) U = (-1 - np.sqrt(1 - 4 * (Sigma.imag * Sigma.imag + Sigma.real)) ) / 2.0 + 1j * Sigma.imag V_l = U * A # Assign the reduced solution V[pq] = V_l[:npq] V[pv] = V_l[npq:] # compute the calculated power injection and the error of the voltage solution Scalc = V * np.conj(Ybus * V - I0) mis = Scalc - S0 # complex power mismatch mismatch = np.r_[mis[pv].real, mis[pq].real, mis[pq].imag] # concatenate again normF = np.linalg.norm(mismatch, np.Inf) converged = normF < tol print(normF) end = time.time() elapsed = end - start return V, converged, normF, Scalc, iterations, elapsed