def get_Jacobian(self, sparse=False): """ Returns the grid Jacobian matrix. Arguments: **sparse** (bool, False): Return the matrix in CSR sparse format (True) or as full matrix (False) """ numerical_circuit = self.compile() islands = numerical_circuit.compute() i = 0 # Initial magnitudes pvpq = np.r_[islands[i].pv, islands[i].pq] J = Jacobian(Ybus=islands[i].Ybus, V=islands[i].Vbus, Ibus=islands[i].Ibus, pq=islands[i].pq, pvpq=pvpq) if sparse: return J else: return J.todense()
def get_Jacobian(self, sparse=False): """ Returns the grid Jacobian matrix. Arguments: **sparse** (bool, False): Return the matrix in CSR sparse format (True) or as full matrix (False) """ # Initial magnitudes pvpq = np.r_[self.numerical_circuit.pv, self.numerical_circuit.pq] J = Jacobian(Ybus=self.numerical_circuit.Ybus, V=self.numerical_circuit.Vbus, Ibus=self.numerical_circuit.Ibus, pq=self.numerical_circuit.pq, pvpq=pvpq) if sparse: return J else: return J.todense()
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 get_structure(self, structure_type) -> pd.DataFrame: """ Get a DataFrame with the input. Arguments: **structure_type** (str): 'Vbus', 'Sbus', 'Ibus', 'Ybus', 'Yshunt', 'Yseries' or 'Types' Returns: pandas DataFrame """ if structure_type == 'Vbus': df = pd.DataFrame(data=self.Vbus, columns=['Voltage (p.u.)'], index=self.bus_names) elif structure_type == 'Sbus': df = pd.DataFrame(data=self.Sbus, columns=['Power (p.u.)'], index=self.bus_names) elif structure_type == 'Ibus': df = pd.DataFrame(data=self.Ibus, columns=['Current (p.u.)'], index=self.bus_names) elif structure_type == 'Ybus': df = pd.DataFrame(data=self.Ybus.toarray(), columns=self.bus_names, index=self.bus_names) elif structure_type == 'Yf': df = pd.DataFrame(data=self.Yf.toarray(), columns=self.bus_names, index=self.branch_names) elif structure_type == 'Yt': df = pd.DataFrame(data=self.Yt.toarray(), columns=self.bus_names, index=self.branch_names) elif structure_type == 'Cf': df = pd.DataFrame(data=self.C_branch_bus_f.toarray(), columns=self.bus_names, index=self.branch_names) elif structure_type == 'Ct': df = pd.DataFrame(data=self.C_branch_bus_t.toarray(), columns=self.bus_names, index=self.branch_names) elif structure_type == 'Yshunt': df = pd.DataFrame(data=self.Yshunt, columns=['Shunt admittance (p.u.)'], index=self.bus_names) elif structure_type == 'Yseries': df = pd.DataFrame(data=self.Yseries.toarray(), columns=self.bus_names, index=self.bus_names) elif structure_type == "B'": df = pd.DataFrame(data=self.B1.toarray(), columns=self.bus_names, index=self.bus_names) elif structure_type == "B''": df = pd.DataFrame(data=self.B2.toarray(), columns=self.bus_names, index=self.bus_names) elif structure_type == 'Types': df = pd.DataFrame(data=self.bus_types, columns=['Bus types'], index=self.bus_names) elif structure_type == 'Qmin': df = pd.DataFrame(data=self.Qmin_bus, columns=['Qmin'], index=self.bus_names) elif structure_type == 'Qmax': df = pd.DataFrame(data=self.Qmax_bus, columns=['Qmax'], index=self.bus_names) elif structure_type == 'pq': df = pd.DataFrame(data=self.pq, columns=['pq'], index=self.bus_names[self.pq]) elif structure_type == 'pv': df = pd.DataFrame(data=self.pv, columns=['pv'], index=self.bus_names[self.pv]) elif structure_type == 'vd': df = pd.DataFrame(data=self.vd, columns=['vd'], index=self.bus_names[self.vd]) elif structure_type == 'pqpv': df = pd.DataFrame(data=self.pqpv, columns=['pqpv'], index=self.bus_names[self.pqpv]) elif structure_type == 'original_bus_idx': df = pd.DataFrame(data=self.original_bus_idx, columns=['original_bus_idx'], index=self.bus_names) elif structure_type == 'original_branch_idx': df = pd.DataFrame(data=self.original_branch_idx, columns=['original_branch_idx'], index=self.branch_names) elif structure_type == 'original_line_idx': df = pd.DataFrame(data=self.original_line_idx, columns=['original_line_idx'], index=self.line_names) elif structure_type == 'original_tr_idx': df = pd.DataFrame(data=self.original_tr_idx, columns=['original_tr_idx'], index=self.tr_names) elif structure_type == 'original_gen_idx': df = pd.DataFrame(data=self.original_gen_idx, columns=['original_gen_idx'], index=self.generator_names) elif structure_type == 'Jacobian': J = Jacobian(self.Ybus, self.Vbus, self.Ibus, self.pq, self.pqpv) """ 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 """ npq = len(self.pq) npv = len(self.pv) npqpv = npq + npv cols = ['dS/dVa'] * npqpv + ['dS/dVm'] * npq rows = cols df = pd.DataFrame(data=J.toarray(), columns=cols, index=rows) else: raise Exception('PF input: structure type not found') return df
def get_structure(self, structure_type): """ Get a DataFrame with the input. Arguments: **structure_type** (str): 'Vbus', 'Sbus', 'Ibus', 'Ybus', 'Yshunt', 'Yseries' or 'Types' Returns: pandas DataFrame """ if structure_type == 'Vbus': df = pd.DataFrame(data=self.Vbus, columns=['Voltage (p.u.)'], index=self.bus_names) elif structure_type == 'Sbus': df = pd.DataFrame(data=self.Sbus, columns=['Power (p.u.)'], index=self.bus_names) elif structure_type == 'Ibus': df = pd.DataFrame(data=self.Ibus, columns=['Current (p.u.)'], index=self.bus_names) elif structure_type == 'Ybus': df = pd.DataFrame(data=self.Ybus.toarray(), columns=self.bus_names, index=self.bus_names) elif structure_type == 'Yshunt': df = pd.DataFrame(data=self.Ysh, columns=['Shunt admittance (p.u.)'], index=self.bus_names) elif structure_type == 'Yseries': df = pd.DataFrame(data=self.Yseries.toarray(), columns=self.bus_names, index=self.bus_names) elif structure_type == "B'": df = pd.DataFrame(data=self.B1.toarray(), columns=self.bus_names, index=self.bus_names) elif structure_type == "B''": df = pd.DataFrame(data=self.B2.toarray(), columns=self.bus_names, index=self.bus_names) elif structure_type == 'Types': df = pd.DataFrame(data=self.types, columns=['Bus types'], index=self.bus_names) elif structure_type == 'Qmin': df = pd.DataFrame(data=self.Qmin, columns=['Qmin'], index=self.bus_names) elif structure_type == 'Qmax': df = pd.DataFrame(data=self.Qmax, columns=['Qmax'], index=self.bus_names) elif structure_type == 'Jacobian': J = Jacobian(self.Ybus, self.Vbus, self.Ibus, self.pq, self.pqpv) """ 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 """ npq = len(self.pq) npv = len(self.pv) npqpv = npq + npv cols = ['dS/dVa'] * npqpv + ['dS/dVm'] * npq rows = cols df = pd.DataFrame(data=J.toarray(), columns=cols, index=rows) else: raise Exception('PF input: structure type not found') return df
def predictor(V, Ibus, lam, Ybus, Sxfr, pv, pq, step, z, Vprv, lamprv, parametrization: VCParametrization): """ 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 """ """ % MATPOWER % Copyright (c) 1996-2015 by Power System Engineering Research Center (PSERC) % by Shrirang Abhyankar, Argonne National Laboratory % and Ray Zimmerman, PSERC Cornell % % $Id: cpf_predictor.m 2644 2015-03-11 19:34:22Z ray $ % % This file is part of MATPOWER. % Covered by the 3-clause BSD License (see LICENSE file for details). % See http://www.pserc.cornell.edu/matpower/ for more info. """ # sizes nb = len(V) npv = len(pv) npq = len(pq) pvpq = r_[pv, pq] nj = npv + npq * 2 # compute Jacobian for the power flow equations J = Jacobian(Ybus, V, Ibus, pq, pvpq) dF_dlam = -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[r_[pvpq, nb + pq, 2 * nb]] = spsolve(J2, s) # normalize_string tangent predictor (dividing by the euclidean norm) z /= 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[nb + pq] lam0 = lam + step * z[2 * nb] V0 = Vm0 * exp(1j * Va0) return V0, lam0, z
def corrector_new(Ybus, Ibus, Sbus, V0, pv, pq, lam0, Sxfr, Vprv, lamprv, z, step, parametrization, tol, max_it, verbose, max_it_internal=10): """ 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: :param max_it: :param verbose: :return: V, CONVERGED, I, LAM """ """ # CPF_CORRECTOR Solves the corrector step of a continuation power flow using a # full Newton method with selected parametrization scheme. # [V, CONVERGED, I, LAM] = CPF_CORRECTOR(YBUS, SBUS, V0, REF, PV, PQ, ... # LAM0, SXFR, VPRV, LPRV, Z, STEP, parametrization, MPOPT) # 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. MPOPT is a MATPOWER options struct which can be used to # set the termination tolerance, maximum number of iterations, and # output options (see MPOPTION for details). 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. # # The extra continuation inputs are LAM0 (initial predicted lambda), # SXFR ([delP+j*delQ] transfer/loading vector for all buses), VPRV # (final complex V corrector solution from previous continuation step), # LAMPRV (final lambda corrector solution from previous continuation step), # Z (normalized predictor for all buses), and STEP (continuation step size). # The extra continuation output is LAM (final corrector lambda). # # See also RUNCPF. # MATPOWER # Copyright (c) 1996-2015 by Power System Engineering Research Center (PSERC) # by Ray Zimmerman, PSERC Cornell, # Shrirang Abhyankar, Argonne National Laboratory, # and Alexander Flueck, IIT # # Modified by Alexander J. Flueck, Illinois Institute of Technology # 2001.02.22 - corrector.m (ver 1.0) based on newtonpf.m (MATPOWER 2.0) # # Modified by Shrirang Abhyankar, Argonne National Laboratory # (Updated to be compatible with MATPOWER version 4.1) # # $Id: cpf_corrector.m 2644 2015-03-11 19:34:22Z ray $ # # This file is part of MATPOWER. # Covered by the 3-clause BSD License (see LICENSE file for details). # See http://www.pserc.cornell.edu/matpower/ for more info. """ # initialize converged = False i = 0 V = V0 Va = angle(V) Vm = np.abs(V) dVa = np.zeros_like(Va) dVm = np.zeros_like(Vm) 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 nb = len(V) # number of buses j1 = 1 ''' # MATLAB code j2 = npv # j1:j2 - V angle of pv buses j3 = j2 + 1 j4 = j2 + npq # j3:j4 - V angle of pq buses j5 = j4 + 1 j6 = j4 + npq # j5:j6 - V mag of pq buses j7 = j6 + 1 j8 = j6 + 1 # j7:j8 - lambda ''' # j1:j2 - V angle of pv buses j1 = 0 j2 = npv # j3:j4 - V angle of pq buses j3 = j2 j4 = j2 + npq # j5:j6 - V mag of pq buses j5 = j4 j6 = j4 + npq j7 = j6 j8 = j6 + 1 # evaluate F(x0, lam0), including Sxfr transfer/loading mismatch = V * conj(Ybus * V) - 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 last_error = linalg.norm(F, Inf) error = 1e20 if last_error < tol: converged = True if verbose: print('\nConverged!\n') # do Newton iterations while not converged and i < max_it: # update iteration counter i += 1 # evaluate Jacobian J = Jacobian(Ybus, V, Ibus, pq, pvpq) dF_dlam = -r_[Sxfr[pvpq].real, Sxfr[pq].imag] 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) # reassign the solution vector if npv: dVa[pv] = dx[j1:j2] if npq: dVa[pq] = dx[j3:j4] dVm[pq] = dx[j5:j6] # update lambda lam += dx[j7:j8][0] # reset mu mu_ = 1.0 print('iter', i) it = 0 Vm = np.abs(V) Va = np.angle(V) while error >= last_error and it < max_it_internal: # update voltage the Newton way (mu=1) Vm_new = Vm + mu_ * dVm Va_new = Va + mu_ * dVa V_new = Vm_new * exp(1j * Va_new) print('\t', mu_, error, last_error) # evaluate F(x, lam) mismatch = V_new * conj(Ybus * V_new) - Sbus - lam * Sxfr # evaluate P(x, lambda) P = cpf_p(parametrization, step, z, V_new, lam, Vprv, lamprv, pv, pq, pvpq) # compose the mismatch vector F = r_[mismatch[pv].real, mismatch[pq].real, mismatch[pq].imag, P] # check for convergence error = linalg.norm(F, Inf) # modify mu mu_ *= 0.25 it += 1 V = V_new.copy() last_error = error if verbose: print('\n#3d #10.3e', i, error) if error < tol: converged = True 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, error
def run_jacobian_mode(self, time_indices) -> PtdfTimeSeriesResults: """ Run the PTDF with the illinois formulation :return: TimeSeriesResults instance """ # initialize the grid time series results, we will append the island results with another function nc = compile_opf_time_circuit( circuit=self.grid, apply_temperature=self.pf_options.apply_temperature_correction, branch_tolerance_mode=self.pf_options. branch_impedance_tolerance_mode) results = PtdfTimeSeriesResults( n=nc.nbus, m=nc.nbr, time_array=self.grid.time_profile[time_indices], bus_names=nc.bus_names, bus_types=nc.bus_types, branch_names=nc.branch_names) # if there are valid profiles... if self.grid.time_profile is not None: # run a power flow to get the initial branch power and compose the second branch power with the increment driver = PowerFlowDriver(grid=self.grid, options=self.pf_options) driver.run() # compile the islands islands = split_opf_time_circuit_into_islands(nc) # compose the power injections Sbus_0 = driver.results.Sbus Pbus_0 = Sbus_0.real V_0 = driver.results.voltage Pbr_0 = driver.results.Sbranch.real for island in islands: V = island.Vbus[0, :] Ibus = island.Ibus[:, 0] n = len(V) # set up indexing for updating V pvpq = np.r_[island.pv, island.pq] npv = len(island.pv) npq = len(island.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 # compute the Jacobian J = Jacobian(island.Ybus, V, Ibus, island.pq, pvpq) Jfact = factorized(J) dVa = np.zeros(n) dVm = np.zeros(n) # run the PTDF time series for k, t_idx in enumerate(time_indices): # dP = Pbus_0[island.original_bus_idx] - island.Sbus[:, t_idx].real # compute the power increment (f) dS = Sbus_0 - island.Sbus[:, t_idx] # dS = island.Sbus[:, t_idx] f = np.r_[dS[pvpq].real, dS[island.pq].imag] # solve the voltage increment dx = Jfact(f) # reassign the solution vector dVa[pvpq] = dx[j1:j2] dVm[island.pq] = dx[j2:j3] dV = dVm * np.exp(1j * dVa) V = V_0 - dV Vf = island.C_branch_bus_f * V If = np.conj(island.Yf * V) Sf = (Vf * If) * island.Sbase results.voltage[k, island.original_bus_idx] = np.abs(V) results.Sbranch[k, island.original_branch_idx] = Sf.real results.loading[ k, island.original_branch_idx] = Sf.real / ( nc.branch_rates[t_idx, island.original_branch_idx] + 1e-9) results.S[ k, island.original_bus_idx] = island.Sbus[:, t_idx].real progress = ((t_idx - self.start_ + 1) / (self.end_ - self.start_)) * 100 self.progress_signal.emit(progress) self.progress_text.emit('Simulating PTDF at ' + str(self.grid.time_profile[t_idx])) else: print('There are no profiles') self.progress_text.emit('There are no profiles') return results
def compute_ptdf(Ybus, Yf, Yt, Cf, Ct, V, Ibus, Sbus, pq, pv): """ :param Ybus: :param Yf: :param Yt: :param Cf: :param Ct: :param V: :param Ibus: :param Sbus: :param pq: :param pv: :return: """ n = len(V) # 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 # compute the Jacobian J = Jacobian(Ybus, V, Ibus, pq, pvpq) # compute the power increment (f) Scalc = V * np.conj(Ybus * V - Ibus) dS = Scalc - Sbus f = np.r_[dS[pvpq].real, dS[pq].imag] # solve the voltage increment dx = spsolve(J, f) # reassign the solution vector dVa = np.zeros(n) dVm = np.zeros(n) dVa[pvpq] = dx[j1:j2] dVm[pq] = dx[j2:j3] # compute branch derivatives If = Yf * V It = Yt * V E = V / np.abs(V) Vdiag = sp.diags(V) Vdiag_conj = sp.diags(np.conj(V)) Ediag = sp.diags(E) Ediag_conj = sp.diags(np.conj(E)) If_diag_conj = sp.diags(np.conj(If)) It_diag_conj = sp.diags(np.conj(It)) Yf_conj = Yf.copy() Yf_conj.data = np.conj(Yf_conj.data) Yt_conj = Yt.copy() Yt_conj.data = np.conj(Yt_conj.data) dSf_dVa = 1j * (If_diag_conj * Cf * Vdiag - sp.diags(Cf * V) * Yf_conj * Vdiag_conj) dSf_dVm = If_diag_conj * Cf * Ediag - sp.diags( Cf * V) * Yf_conj * Ediag_conj dSt_dVa = 1j * (It_diag_conj * Ct * Vdiag - sp.diags(Ct * V) * Yt_conj * Vdiag_conj) dSt_dVm = It_diag_conj * Ct * Ediag - sp.diags( Ct * V) * Yt_conj * Ediag_conj # compute the PTDF dVmf = Cf * dVm dVaf = Cf * dVa dPf_dVa = dSf_dVa.real dPf_dVm = dSf_dVm.real dVmt = Ct * dVm dVat = Ct * dVa dPt_dVa = dSt_dVa.real dPt_dVm = dSt_dVm.real PTDF = sp.diags(dVmf) * dPf_dVm + sp.diags(dVmt) * dPt_dVm + sp.diags( dVaf) * dPf_dVa + sp.diags(dVat) * dPt_dVa return PTDF