def estimate(self, v_start=None, delta_start=None, calculate_voltage_angles=True): """ The function estimate is the main function of the module. It takes up to three input arguments: v_start, delta_start and calculate_voltage_angles. The first two are the initial state variables for the estimation process. Usually they can be initialized in a "flat-start" condition: All voltages being 1.0 pu and all voltage angles being 0 degrees. In this case, the parameters can be left at their default values (None). If the estimation is applied continuously, using the results from the last estimation as the starting condition for the current estimation can decrease the amount of iterations needed to estimate the current state. The third parameter defines whether all voltage angles are calculated absolutely, including phase shifts from transformers. If only the relative differences between buses are required, this parameter can be set to False. Returned is a boolean value, which is true after a successful estimation and false otherwise. The resulting complex voltage will be written into the pandapower network. The result fields are found res_bus_est of the pandapower network. INPUT: **net** - The net within this line should be created **v_start** (np.array, shape=(1,), optional) - Vector with initial values for all voltage magnitudes in p.u. (sorted by bus index) **delta_start** (np.array, shape=(1,), optional) - Vector with initial values for all voltage angles in degrees (sorted by bus index) OPTIONAL: **calculate_voltage_angles** - (bool) - Take into account absolute voltage angles and phase shifts in transformers Default is True. OUTPUT: **successful** (boolean) - True if the estimation process was successful Optional estimation variables: The bus power injections can be accessed with *se.s_node_powers* and the estimated values corresponding to the (noisy) measurement values with *se.hx*. (*hx* denotes h(x)) EXAMPLE: success = estimate(np.array([1.0, 1.0, 1.0]), np.array([0.0, 0.0, 0.0])) """ if self.net is None: raise UserWarning("Component was not initialized with a network.") # add initial values for V and delta # node voltages # V<delta if v_start is None: v_start = np.ones(self.net.bus.shape[0]) if delta_start is None: delta_start = np.zeros(self.net.bus.shape[0]) # initialize the ppc bus with the initial values given vm_backup, va_backup = self.net.res_bus.vm_pu.copy( ), self.net.res_bus.va_degree.copy() self.net.res_bus.vm_pu = v_start self.net.res_bus.vm_pu[self.net.bus.index[self.net.bus.in_service == False]] = np.nan self.net.res_bus.va_degree = delta_start # select elements in service and convert pandapower ppc to ppc self.net._options = {} _add_ppc_options(self.net, check_connectivity=False, init="results", trafo_model="t", copy_constraints_to_ppc=False, mode="pf", enforce_q_lims=False, calculate_voltage_angles=calculate_voltage_angles, r_switch=0.0, recycle=dict(_is_elements=False, ppc=False, Ybus=False)) self.net["_is_elements"] = _select_is_elements(self.net) ppc, _ = _pd2ppc(self.net) mapping_table = self.net["_pd2ppc_lookups"]["bus"] br_cols = ppc["branch"].shape[1] bs_cols = ppc["bus"].shape[1] self.net.res_bus.vm_pu = vm_backup self.net.res_bus.va_degree = va_backup # add 6 columns to ppc[bus] for Vm, Vm std dev, P, P std dev, Q, Q std dev bus_append = np.full((ppc["bus"].shape[0], 6), np.nan, dtype=ppc["bus"].dtype) v_measurements = self.net.measurement[ (self.net.measurement.type == "v") & (self.net.measurement.element_type == "bus")] if len(v_measurements): bus_positions = mapping_table[v_measurements.bus.values.astype( int)] bus_append[bus_positions, 0] = v_measurements.value.values bus_append[bus_positions, 1] = v_measurements.std_dev.values p_measurements = self.net.measurement[ (self.net.measurement.type == "p") & (self.net.measurement.element_type == "bus")] if len(p_measurements): bus_positions = mapping_table[p_measurements.bus.values.astype( int)] bus_append[bus_positions, 2] = p_measurements.value.values * 1e3 / self.s_ref bus_append[bus_positions, 3] = p_measurements.std_dev.values * 1e3 / self.s_ref q_measurements = self.net.measurement[ (self.net.measurement.type == "q") & (self.net.measurement.element_type == "bus")] if len(q_measurements): bus_positions = mapping_table[q_measurements.bus.values.astype( int)] bus_append[bus_positions, 4] = q_measurements.value.values * 1e3 / self.s_ref bus_append[bus_positions, 5] = q_measurements.std_dev.values * 1e3 / self.s_ref # add virtual measurements for artificial buses, which were created because # of an open line switch. p/q are 0. and std dev is 1. (small value) new_in_line_buses = np.setdiff1d(np.arange(ppc["bus"].shape[0]), mapping_table[mapping_table >= 0]) bus_append[new_in_line_buses, 2] = 0. bus_append[new_in_line_buses, 3] = 1. bus_append[new_in_line_buses, 4] = 0. bus_append[new_in_line_buses, 5] = 1. # add 12 columns to mpc[branch] for Im_from, Im_from std dev, Im_to, Im_to std dev, # P_from, P_from std dev, P_to, P_to std dev, Q_from,Q_from std dev, Q_to, Q_to std dev branch_append = np.full((ppc["branch"].shape[0], 12), np.nan, dtype=ppc["branch"].dtype) i_measurements = self.net.measurement[ (self.net.measurement.type == "i") & (self.net.measurement.element_type == "line")] if len(i_measurements): meas_from = i_measurements[(i_measurements.bus.values.astype( int) == self.net.line.from_bus[i_measurements.element]).values] meas_to = i_measurements[(i_measurements.bus.values.astype( int) == self.net.line.to_bus[i_measurements.element]).values] ix_from = meas_from.element.values.astype(int) ix_to = meas_to.element.values.astype(int) i_a_to_pu_from = (self.net.bus.vn_kv[meas_from.bus] * 1e3 / self.s_ref).values i_a_to_pu_to = (self.net.bus.vn_kv[meas_to.bus] * 1e3 / self.s_ref).values branch_append[ix_from, 0] = meas_from.value.values * i_a_to_pu_from branch_append[ix_from, 1] = meas_from.std_dev.values * i_a_to_pu_from branch_append[ix_to, 2] = meas_to.value.values * i_a_to_pu_to branch_append[ix_to, 3] = meas_to.std_dev.values * i_a_to_pu_to p_measurements = self.net.measurement[ (self.net.measurement.type == "p") & (self.net.measurement.element_type == "line")] if len(p_measurements): meas_from = p_measurements[(p_measurements.bus.values.astype( int) == self.net.line.from_bus[p_measurements.element]).values] meas_to = p_measurements[(p_measurements.bus.values.astype( int) == self.net.line.to_bus[p_measurements.element]).values] ix_from = meas_from.element.values.astype(int) ix_to = meas_to.element.values.astype(int) branch_append[ix_from, 4] = meas_from.value.values * 1e3 / self.s_ref branch_append[ix_from, 5] = meas_from.std_dev.values * 1e3 / self.s_ref branch_append[ix_to, 6] = meas_to.value.values * 1e3 / self.s_ref branch_append[ix_to, 7] = meas_to.std_dev.values * 1e3 / self.s_ref q_measurements = self.net.measurement[ (self.net.measurement.type == "q") & (self.net.measurement.element_type == "line")] if len(q_measurements): meas_from = q_measurements[(q_measurements.bus.values.astype( int) == self.net.line.from_bus[q_measurements.element]).values] meas_to = q_measurements[(q_measurements.bus.values.astype( int) == self.net.line.to_bus[q_measurements.element]).values] ix_from = meas_from.element.values.astype(int) ix_to = meas_to.element.values.astype(int) branch_append[ix_from, 8] = meas_from.value.values * 1e3 / self.s_ref branch_append[ix_from, 9] = meas_from.std_dev.values * 1e3 / self.s_ref branch_append[ix_to, 10] = meas_to.value.values * 1e3 / self.s_ref branch_append[ix_to, 11] = meas_to.std_dev.values * 1e3 / self.s_ref i_tr_measurements = self.net.measurement[ (self.net.measurement.type == "i") & (self.net.measurement.element_type == "transformer")] if len(i_tr_measurements): meas_from = i_tr_measurements[( i_tr_measurements.bus.values.astype(int) == self.net.trafo.hv_bus[i_tr_measurements.element]).values] meas_to = i_tr_measurements[( i_tr_measurements.bus.values.astype(int) == self.net.trafo.lv_bus[i_tr_measurements.element]).values] ix_from = meas_from.element.values.astype(int) ix_to = meas_to.element.values.astype(int) i_a_to_pu_from = (self.net.bus.vn_kv[meas_from.bus] * 1e3 / self.s_ref).values i_a_to_pu_to = (self.net.bus.vn_kv[meas_to.bus] * 1e3 / self.s_ref).values branch_append[ix_from, 0] = meas_from.value.values * i_a_to_pu_from branch_append[ix_from, 1] = meas_from.std_dev.values * i_a_to_pu_from branch_append[ix_to, 2] = meas_to.value.values * i_a_to_pu_to branch_append[ix_to, 3] = meas_to.std_dev.values * i_a_to_pu_to p_tr_measurements = self.net.measurement[ (self.net.measurement.type == "p") & (self.net.measurement.element_type == "transformer")] if len(p_tr_measurements): meas_from = p_tr_measurements[( p_tr_measurements.bus.values.astype(int) == self.net.trafo.hv_bus[p_tr_measurements.element]).values] meas_to = p_tr_measurements[( p_tr_measurements.bus.values.astype(int) == self.net.trafo.lv_bus[p_tr_measurements.element]).values] ix_from = len(self.net.line) + meas_from.element.values.astype(int) ix_to = len(self.net.line) + meas_to.element.values.astype(int) branch_append[ix_from, 4] = meas_from.value.values * 1e3 / self.s_ref branch_append[ix_from, 5] = meas_from.std_dev.values * 1e3 / self.s_ref branch_append[ix_to, 6] = meas_to.value.values * 1e3 / self.s_ref branch_append[ix_to, 7] = meas_to.std_dev.values * 1e3 / self.s_ref q_tr_measurements = self.net.measurement[ (self.net.measurement.type == "q") & (self.net.measurement.element_type == "transformer")] if len(q_tr_measurements): meas_from = q_tr_measurements[( q_tr_measurements.bus.values.astype(int) == self.net.trafo.hv_bus[q_tr_measurements.element]).values] meas_to = q_tr_measurements[( q_tr_measurements.bus.values.astype(int) == self.net.trafo.lv_bus[q_tr_measurements.element]).values] ix_from = len(self.net.line) + meas_from.element.values.astype(int) ix_to = len(self.net.line) + meas_to.element.values.astype(int) branch_append[ix_from, 8] = meas_from.value.values * 1e3 / self.s_ref branch_append[ix_from, 9] = meas_from.std_dev.values * 1e3 / self.s_ref branch_append[ix_to, 10] = meas_to.value.values * 1e3 / self.s_ref branch_append[ix_to, 11] = meas_to.std_dev.values * 1e3 / self.s_ref ppc["bus"] = np.hstack((ppc["bus"], bus_append)) ppc["branch"] = np.hstack((ppc["branch"], branch_append)) with warnings.catch_warnings(): warnings.simplefilter("ignore") ppc_i = ext2int(ppc) p_bus_not_nan = ~np.isnan(ppc_i["bus"][:, bs_cols + 2]) p_line_f_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 4]) p_line_t_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 6]) q_bus_not_nan = ~np.isnan(ppc_i["bus"][:, bs_cols + 4]) q_line_f_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 8]) q_line_t_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 10]) v_bus_not_nan = ~np.isnan(ppc_i["bus"][:, bs_cols + 0]) i_line_f_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 0]) i_line_t_not_nan = ~np.isnan(ppc_i["branch"][:, br_cols + 2]) # piece together our measurement vector z z = np.concatenate( (ppc_i["bus"][p_bus_not_nan, bs_cols + 2], ppc_i["branch"][p_line_f_not_nan, br_cols + 4], ppc_i["branch"][p_line_t_not_nan, br_cols + 6], ppc_i["bus"][q_bus_not_nan, bs_cols + 4], ppc_i["branch"][q_line_f_not_nan, br_cols + 8], ppc_i["branch"][q_line_t_not_nan, br_cols + 10], ppc_i["bus"][v_bus_not_nan, bs_cols + 0], ppc_i["branch"][i_line_f_not_nan, br_cols + 0], ppc_i["branch"][i_line_t_not_nan, br_cols + 2])).real.astype(np.float64) # number of nodes n_active = len(np.where(ppc_i["bus"][:, 1] != 4)[0]) slack_buses = np.where(ppc_i["bus"][:, 1] == 3)[0] # Check if observability criterion is fulfilled and the state estimation is possible if len(z) < 2 * n_active - 1: self.logger.error("System is not observable (cancelling)") self.logger.error( "Measurements available: %d. Measurements required: %d" % (len(z), 2 * n_active - 1)) return False # Set the starting values for all active buses v_m = ppc_i["bus"][:, 7] delta = ppc_i["bus"][:, 8] * np.pi / 180 # convert to rad delta_masked = np.ma.array(delta, mask=False) delta_masked.mask[slack_buses] = True non_slack_buses = np.arange(len(delta))[~delta_masked.mask] # Matrix calculation object sem = wls_matrix_ops(ppc_i, slack_buses, non_slack_buses, self.s_ref, bs_cols, br_cols) # state vector E = np.concatenate((delta_masked.compressed(), v_m)) # Covariance matrix R r_cov = np.concatenate( (ppc_i["bus"][p_bus_not_nan, bs_cols + 3], ppc_i["branch"][p_line_f_not_nan, br_cols + 5], ppc_i["branch"][p_line_t_not_nan, br_cols + 7], ppc_i["bus"][q_bus_not_nan, bs_cols + 5], ppc_i["branch"][q_line_f_not_nan, br_cols + 9], ppc_i["branch"][q_line_t_not_nan, br_cols + 11], ppc_i["bus"][v_bus_not_nan, bs_cols + 1], ppc_i["branch"][i_line_f_not_nan, br_cols + 1], ppc_i["branch"][i_line_t_not_nan, br_cols + 3])).real.astype(np.float64) r_inv = csr_matrix(np.linalg.inv(np.diagflat(r_cov)**2)) current_error = 100 current_iterations = 0 while current_error > self.tolerance and current_iterations < self.max_iterations: self.logger.debug(" Starting iteration %d" % (1 + current_iterations)) try: # create h(x) for the current iteration h_x = sem.create_hx(v_m, delta) # Residual r r = csr_matrix(z - h_x).T # Jacobian matrix H H = csr_matrix(sem.create_jacobian(v_m, delta)) # if not np.linalg.cond(H) < 1 / sys.float_info.epsilon: # self.logger.error("Error in matrix H") # Gain matrix G_m # G_m = H^t * R^-1 * H G_m = H.T * (r_inv * H) # State Vector difference d_E # d_E = G_m^-1 * (H' * R^-1 * r) d_E = spsolve(G_m, H.T * (r_inv * r)) E += d_E # Update V/delta delta[non_slack_buses] = E[:len(non_slack_buses)] v_m = np.squeeze(E[len(non_slack_buses):]) current_iterations += 1 current_error = np.max(np.abs(d_E)) self.logger.debug("Current error: %.4f" % current_error) except np.linalg.linalg.LinAlgError: self.logger.error( "A problem appeared while using the linear algebra methods." "Check and change the measurement set.") return False # Print output for results if current_error <= self.tolerance: successful = True self.logger.info( "WLS State Estimation successful (%d iterations)" % current_iterations) else: successful = False self.logger.info( "WLS State Estimation not successful (%d/%d iterations" % (current_iterations, self.max_iterations)) # write voltage into ppc ppc_i["bus"][:, 7] = v_m ppc_i["bus"][:, 8] = delta * 180 / np.pi # convert to degree # calculate bus powers v_cpx = v_m * np.exp(1j * delta) bus_powers_conj = np.zeros(len(v_cpx), dtype=np.complex128) for i in range(len(v_cpx)): bus_powers_conj[i] = np.dot(sem.Y_bus[i, :], v_cpx) * np.conjugate( v_cpx[i]) ppc_i["bus"][:, 2] = bus_powers_conj.real # saved in per unit ppc_i["bus"][:, 3] = -bus_powers_conj.imag # saved in per unit # convert to pandapower indices with warnings.catch_warnings(): warnings.simplefilter("ignore") ppc = int2ext(ppc_i) _set_buses_out_of_service(ppc) # Store results, overwrite old results self.net.res_bus_est = pd.DataFrame( columns=["vm_pu", "va_degree", "p_kw", "q_kvar"], index=self.net.bus.index) self.net.res_line_est = pd.DataFrame(columns=[ "p_from_kw", "q_from_kvar", "p_to_kw", "q_to_kvar", "pl_kw", "ql_kvar", "i_from_ka", "i_to_ka", "i_ka", "loading_percent" ], index=self.net.line.index) bus_idx = mapping_table[self.net["bus"].index.values] self.net["res_bus_est"]["vm_pu"] = ppc["bus"][bus_idx][:, 7] self.net["res_bus_est"]["va_degree"] = ppc["bus"][bus_idx][:, 8] self.net.res_bus_est.p_kw = -get_values( ppc["bus"][:, 2], self.net.bus.index, mapping_table) * self.s_ref / 1e3 self.net.res_bus_est.q_kvar = -get_values( ppc["bus"][:, 3], self.net.bus.index, mapping_table) * self.s_ref / 1e3 self.net.res_line_est = calculate_line_results(self.net, use_res_bus_est=True) # Store some variables required for Chi^2 and r_N_max test: self.R_inv = r_inv.toarray() self.Gm = G_m.toarray() self.r = r.toarray() self.H = H.toarray() self.Ht = self.H.T self.hx = h_x self.V = v_m self.delta = delta return successful
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
def opf(*args): """Solves an optimal power flow. Returns a C{results} dict. The data for the problem can be specified in one of three ways: 1. a string (ppc) containing the file name of a PYPOWER case which defines the data matrices baseMVA, bus, gen, branch, and gencost (areas is not used at all, it is only included for backward compatibility of the API). 2. a dict (ppc) containing the data matrices as fields. 3. the individual data matrices themselves. The optional user parameters for user constraints (C{A, l, u}), user costs (C{N, fparm, H, Cw}), user variable initializer (C{z0}), and user variable limits (C{zl, zu}) can also be specified as fields in a case dict, either passed in directly or defined in a case file referenced by name. When specified, C{A, l, u} represent additional linear constraints on the optimization variables, C{l <= A*[x z] <= u}. If the user specifies an C{A} matrix that has more columns than the number of "C{x}" (OPF) variables, then there are extra linearly constrained "C{z}" variables. For an explanation of the formulation used and instructions for forming the C{A} matrix, see the MATPOWER manual. A generalized cost on all variables can be applied if input arguments C{N}, C{fparm}, C{H} and C{Cw} are specified. First, a linear transformation of the optimization variables is defined by means of C{r = N * [x z]}. Then, to each element of C{r} a function is applied as encoded in the C{fparm} matrix (see MATPOWER manual). If the resulting vector is named C{w}, then C{H} and C{Cw} define a quadratic cost on w: C{(1/2)*w'*H*w + Cw * w}. C{H} and C{N} should be sparse matrices and C{H} should also be symmetric. The optional C{ppopt} vector specifies PYPOWER options. If the OPF algorithm is not explicitly set in the options PYPOWER will use the default solver, based on a primal-dual interior point method. For the AC OPF this is C{OPF_ALG = 560}. For the DC OPF, the default is C{OPF_ALG_DC = 200}. See L{ppoption} for more details on the available OPF solvers and other OPF options and their default values. The solved case is returned in a single results dict (described below). Also returned are the final objective function value (C{f}) and a flag which is C{True} if the algorithm was successful in finding a solution (success). Additional optional return values are an algorithm specific return status (C{info}), elapsed time in seconds (C{et}), the constraint vector (C{g}), the Jacobian matrix (C{jac}), and the vector of variables (C{xr}) as well as the constraint multipliers (C{pimul}). The single results dict is a PYPOWER case struct (ppc) with the usual baseMVA, bus, branch, gen, gencost fields, along with the following additional fields: - C{order} see 'help ext2int' for details of this field - C{et} elapsed time in seconds for solving OPF - C{success} 1 if solver converged successfully, 0 otherwise - C{om} OPF model object, see 'help opf_model' - C{x} final value of optimization variables (internal order) - C{f} final objective function value - C{mu} shadow prices on ... - C{var} - C{l} lower bounds on variables - C{u} upper bounds on variables - C{nln} - C{l} lower bounds on nonlinear constraints - C{u} upper bounds on nonlinear constraints - C{lin} - C{l} lower bounds on linear constraints - C{u} upper bounds on linear constraints - C{g} (optional) constraint values - C{dg} (optional) constraint 1st derivatives - C{df} (optional) obj fun 1st derivatives (not yet implemented) - C{d2f} (optional) obj fun 2nd derivatives (not yet implemented) - C{raw} raw solver output in form returned by MINOS, and more - C{xr} final value of optimization variables - C{pimul} constraint multipliers - C{info} solver specific termination code - C{output} solver specific output information - C{alg} algorithm code of solver used - C{var} - C{val} optimization variable values, by named block - C{Va} voltage angles - C{Vm} voltage magnitudes (AC only) - C{Pg} real power injections - C{Qg} reactive power injections (AC only) - C{y} constrained cost variable (only if have pwl costs) - (other) any user defined variable blocks - C{mu} variable bound shadow prices, by named block - C{l} lower bound shadow prices - C{Va}, C{Vm}, C{Pg}, C{Qg}, C{y}, (other) - C{u} upper bound shadow prices - C{Va}, C{Vm}, C{Pg}, C{Qg}, C{y}, (other) - C{nln} (AC only) - C{mu} shadow prices on nonlinear constraints, by named block - C{l} lower bounds - C{Pmis} real power mismatch equations - C{Qmis} reactive power mismatch equations - C{Sf} flow limits at "from" end of branches - C{St} flow limits at "to" end of branches - C{u} upper bounds - C{Pmis}, C{Qmis}, C{Sf}, C{St} - C{lin} - C{mu} shadow prices on linear constraints, by named block - C{l} lower bounds - C{Pmis} real power mistmatch equations (DC only) - C{Pf} flow limits at "from" end of branches (DC only) - C{Pt} flow limits at "to" end of branches (DC only) - C{PQh} upper portion of gen PQ-capability curve(AC only) - C{PQl} lower portion of gen PQ-capability curve(AC only) - C{vl} constant power factor constraint for loads - C{ycon} basin constraints for CCV for pwl costs - (other) any user defined constraint blocks - C{u} upper bounds - C{Pmis}, C{Pf}, C{Pf}, C{PQh}, C{PQl}, C{vl}, C{ycon}, - (other) - C{cost} user defined cost values, by named block @see: L{runopf}, L{dcopf}, L{uopf}, L{caseformat} @author: Ray Zimmerman (PSERC Cornell) @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Richard Lincoln """ ##----- initialization ----- t0 = time() ## start timer ## process input arguments ppc, ppopt = opf_args2(*args) ## add zero columns to bus, gen, branch for multipliers, etc if needed nb = shape(ppc['bus'])[0] ## number of buses nl = shape(ppc['branch'])[0] ## number of branches ng = shape(ppc['gen'])[0] ## number of dispatchable injections if shape(ppc['bus'])[1] < MU_VMIN + 1: ppc['bus'] = c_[ppc['bus'], zeros((nb, MU_VMIN + 1 - shape(ppc['bus'])[1]))] if shape(ppc['gen'])[1] < MU_QMIN + 1: ppc['gen'] = c_[ppc['gen'], zeros((ng, MU_QMIN + 1 - shape(ppc['gen'])[1]))] if shape(ppc['branch'])[1] < MU_ANGMAX + 1: ppc['branch'] = c_[ppc['branch'], zeros( (nl, MU_ANGMAX + 1 - shape(ppc['branch'])[1]))] ##----- convert to internal numbering, remove out-of-service stuff ----- ppc = ext2int(ppc) ##----- construct OPF model object ----- om = opf_setup(ppc, ppopt) ##----- execute the OPF ----- results, success, raw = opf_execute(om, ppopt) ##----- revert to original ordering, including out-of-service stuff ----- results = int2ext(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, MU_PMAX, MU_PMIN])] = 0 if len(results['order']['branch']['status']['off']) > 0: results['branch'][ix_( results['order']['branch']['status']['off'], [PF, QF, PT, QT, MU_SF, MU_ST, MU_ANGMIN, MU_ANGMAX])] = 0 ##----- finish preparing output ----- et = time() - t0 ## compute elapsed time results['et'] = et results['success'] = success results['raw'] = raw return results
def solveropfnlp_2(ppc, solver="ipopt"): if solver == "ipopt": opt = SolverFactory("ipopt", executable="/home/iso/PycharmProjects/opfLC_python3/Python3/py_solvers/ipopt-linux64/ipopt") if solver == "bonmin": opt = SolverFactory("bonmin", executable="/home/iso/PycharmProjects/opfLC_python3/Python3/py_solvers/bonmin-linux64/bonmin") if solver == "knitro": opt = SolverFactory("knitro", executable="D:/ICT/Artelys/Knitro 10.2.1/knitroampl/knitroampl") ppc = ext2int(ppc) # convert to continuous indexing starting from 0 # Gather information about the system # ============================================================= baseMVA, bus, gen, branch = \ ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"] nb = bus.shape[0] # number of buses ng = gen.shape[0] # number of generators nl = branch.shape[0] # number of lines # generator buses gb = tolist(np.array(gen[:, GEN_BUS]).astype(int)) sb = find((bus[:, BUS_TYPE] == REF)) # slack bus index fr = branch[:, F_BUS].astype(int) # from bus indices to = branch[:, T_BUS].astype(int) # to bus indices tr = branch[:, TAP] # transformation ratios tr[find(tr == 0)] = 1 # set to 1 transformation ratios that are 0 r = branch[:, BR_R] # branch resistances x = branch[:, BR_X] # branch reactances b = branch[:, BR_B] # branch susceptances start_time = time.clock() # Admittance matrix computation # ============================================================= y = makeYbus(baseMVA, bus, branch)[0] # admittance matrix yk = 1./(r+x*1j) # branch admittance yft = yk + 0.5j*b # branch admittance + susceptance gk = yk.real # branch resistance yk = yk/tr # include /tr in yk # Optimization # ============================================================= branch[find(branch[:, RATE_A] == 0), RATE_A] = 9999 # set undefined Sflow limit to 9999 Smax = branch[:, RATE_A] / baseMVA # Max. Sflow # Power demand parameters Pd = bus[:, PD] / baseMVA Qd = bus[:, QD] / baseMVA # Max and min Pg and Qg Pg_max = zeros(nb) Pg_max[gb] = gen[:, PMAX] / baseMVA Pg_min = zeros(nb) Pg_min[gb] = gen[:, PMIN] / baseMVA Qg_max = zeros(nb) Qg_max[gb] = gen[:, QMAX] / baseMVA Qg_min = zeros(nb) Qg_min[gb] = gen[:, QMIN] / baseMVA # Vmax and Vmin vectors Vmax = bus[:, VMAX] Vmin = bus[:, VMIN] vm = bus[:, VM] va = bus[:, VA]*pi/180 # create a new optimization model model = ConcreteModel() # Define sets # ------------ model.bus = Set(ordered=True, initialize=range(nb)) # Set of all buses model.gen = Set(ordered=True, initialize=gb) # Set of buses with generation model.line = Set(ordered=True, initialize=range(nl)) # Set of all lines # Define variables # ----------------- # Voltage magnitudes vector (vm) model.vm = Var(model.bus) # Voltage angles vector (va) model.va = Var(model.bus) # Reactive power generation, synchronous machines(SM) (Qg) model.Qg = Var(model.gen) Qg0 = zeros(nb) Qg0[gb] = gen[:, QG]/baseMVA # Active power generation, synchronous machines(SM) (Pg) model.Pg = Var(model.gen) Pg0 = zeros(nb) Pg0[gb] = gen[:, PG] / baseMVA # Active and reactive power from at all branches model.Pf = Var(model.line) model.Qf = Var(model.line) # Active and reactive power to at all branches model.Pt = Var(model.line) model.Qt = Var(model.line) # Warm start the problem # ------------------------ for i in range(nb): model.vm[i] = vm[i] model.va[i] = va[i] if i in gb: model.Pg[i] = Pg0[i] model.Qg[i] = Qg0[i] for i in range(nl): model.Pf[i] = vm[fr[i]] ** 2 * abs(yft[i]) / (tr[i] ** 2) * np.cos(-ang(yft[i])) -\ vm[fr[i]] * vm[to[i]] * abs(yk[i]) * np.cos(va[fr[i]] - va[to[i]] - ang(yk[i])) model.Qf[i] = vm[fr[i]] ** 2 * abs(yft[i]) / (tr[i] ** 2) * np.sin(-ang(yft[i])) -\ vm[fr[i]] * vm[to[i]] * abs(yk[i]) * np.sin(va[fr[i]] - va[to[i]] - ang(yk[i])) model.Pt[i] = vm[to[i]] ** 2 * abs(yft[i]) * np.cos(-ang(yft[i])) -\ vm[to[i]] * vm[fr[i]] * abs(yk[i]) * np.cos(va[to[i]] - va[fr[i]] - ang(yk[i])) model.Qt[i] = vm[to[i]] ** 2 * abs(yft[i]) * np.sin(-ang(yft[i])) -\ vm[to[i]] * vm[fr[i]] * abs(yk[i]) * np.sin(va[to[i]] - va[fr[i]] - ang(yk[i])) # Define constraints # ---------------------------- # Equalities: # ------------ # Active power flow equalities def powerflowact(model, i): if i in gb: return model.Pg[i]-Pd[i] == sum(model.vm[i]*model.vm[j]*abs(y[i, j]) * cos(model.va[i] - model.va[j] - ang(y[i, j])) for j in range(nb)) else: return sum(model.vm[i]*model.vm[j]*abs(y[i, j]) * cos(model.va[i] - model.va[j] - ang(y[i, j])) for j in range(nb)) == -Pd[i] model.const1 = Constraint(model.bus, rule=powerflowact) # Reactive power flow equalities def powerflowreact(model, i): if i in gb: return model.Qg[i]-Qd[i] == sum(model.vm[i]*model.vm[j]*abs(y[i, j]) * sin(model.va[i] - model.va[j] - ang(y[i, j])) for j in range(nb)) else: return sum(model.vm[i]*model.vm[j]*abs(y[i, j]) * sin(model.va[i] - model.va[j] - ang(y[i, j])) for j in range(nb)) == -Qd[i] model.const2 = Constraint(model.bus, rule=powerflowreact) # Active power from def pfrom(model, i): return model.Pf[i] == model.vm[fr[i]] ** 2 * abs(yft[i]) / (tr[i] ** 2) * np.cos(-ang(yft[i])) - \ model.vm[fr[i]] * model.vm[to[i]] * abs(yk[i]) * \ cos(model.va[fr[i]] - model.va[to[i]] - ang(yk[i])) model.const3 = Constraint(model.line, rule=pfrom) # Reactive power from def qfrom(model, i): return model.Qf[i] == model.vm[fr[i]] ** 2 * abs(yft[i]) / (tr[i] ** 2) * np.sin(-ang(yft[i])) - \ model.vm[fr[i]] * model.vm[to[i]] * abs(yk[i]) * \ sin(model.va[fr[i]] - model.va[to[i]] - ang(yk[i])) model.const4 = Constraint(model.line, rule=qfrom) # Active power to def pto(model, i): return model.Pt[i] == model.vm[to[i]] ** 2 * abs(yft[i]) * np.cos(-ang(yft[i])) - \ model.vm[to[i]] * model.vm[fr[i]] * abs(yk[i]) * \ cos(model.va[to[i]] - model.va[fr[i]] - ang(yk[i])) model.const5 = Constraint(model.line, rule=pto) # Reactive power to def qto(model, i): return model.Qt[i] == model.vm[to[i]] ** 2 * abs(yft[i]) * np.sin(-ang(yft[i])) - \ model.vm[to[i]] * model.vm[fr[i]] * abs(yk[i]) * \ sin(model.va[to[i]] - model.va[fr[i]] - ang(yk[i])) model.const6 = Constraint(model.line, rule=qto) # Slack bus phase angle model.const7 = Constraint(expr=model.va[sb[0]] == 0) # Inequalities: # ---------------- # Active power generator limits Pg_min <= Pg <= Pg_max def genplimits(model, i): return Pg_min[i] <= model.Pg[i] <= Pg_max[i] model.const8 = Constraint(model.gen, rule=genplimits) # Reactive power generator limits Qg_min <= Qg <= Qg_max def genqlimits(model, i): return Qg_min[i] <= model.Qg[i] <= Qg_max[i] model.const9 = Constraint(model.gen, rule=genqlimits) # Voltage constraints ( Vmin <= V <= Vmax ) def vlimits(model, i): return Vmin[i] <= model.vm[i] <= Vmax[i] model.const10 = Constraint(model.bus, rule=vlimits) # Sfrom line limit def sfrommax(model, i): return model.Pf[i]**2 + model.Qf[i]**2 <= Smax[i]**2 model.const11 = Constraint(model.line, rule=sfrommax) # Sto line limit def stomax(model, i): return model.Pt[i]**2 + model.Qt[i]**2 <= Smax[i]**2 model.const12 = Constraint(model.line, rule=stomax) # Set objective function # ------------------------ def obj_fun(model): return sum(gk[i] * ((model.vm[fr[i]] / tr[i])**2 + model.vm[to[i]]**2 - 2/tr[i] * model.vm[fr[i]] * model.vm[to[i]] * cos(model.va[fr[i]] - model.va[to[i]])) for i in range(nl)) model.obj = Objective(rule=obj_fun, sense=minimize) mt = time.clock() - start_time # Modeling time # Execute solve command with the selected solver # ------------------------------------------------ start_time = time.clock() results = opt.solve(model, tee=True) et = time.clock() - start_time # Elapsed time print(results) # Update the case info with the optimized variables # ================================================== for i in range(nb): bus[i, VM] = model.vm[i].value # Bus voltage magnitudes bus[i, VA] = model.va[i].value*180/pi # Bus voltage angles # Include Pf - Qf - Pt - Qt in the branch matrix branchsol = zeros((nl, 17)) branchsol[:, :-4] = branch for i in range(nl): branchsol[i, PF] = model.Pf[i].value * baseMVA branchsol[i, QF] = model.Qf[i].value * baseMVA branchsol[i, PT] = model.Pt[i].value * baseMVA branchsol[i, QT] = model.Qt[i].value * baseMVA # Update gen matrix variables for i in range(ng): gen[i, PG] = model.Pg[gb[i]].value * baseMVA gen[i, QG] = model.Qg[gb[i]].value * baseMVA gen[i, VG] = bus[gb[i], VM] # Convert to external (original) numbering and save case results ppc = int2ext(ppc) ppc['bus'][:, 1:] = bus[:, 1:] branchsol[:, 0:2] = ppc['branch'][:, 0:2] ppc['branch'] = branchsol ppc['gen'][:, 1:] = gen[:, 1:] ppc['obj'] = value(obj_fun(model)) ppc['ploss'] = value(obj_fun(model)) * baseMVA ppc['et'] = et ppc['mt'] = mt ppc['success'] = 1 # ppc solved case is returned return ppc
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
def t_ext2int2ext(quiet=False): """Tests C{ext2int} and C{int2ext}. @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln """ t_begin(85, quiet) ##----- ppc = e2i_data/i2e_data(ppc) ----- t = 'ppc = e2i_data(ppc) : ' ppce = loadcase(t_case_ext()) ppci = loadcase(t_case_int()) ppc = e2i_data(ppce) t_is(ppc['bus'], ppci['bus'], 12, [t, 'bus']) t_is(ppc['branch'], ppci['branch'], 12, [t, 'branch']) t_is(ppc['gen'], ppci['gen'], 12, [t, 'gen']) t_is(ppc['gencost'], ppci['gencost'], 12, [t, 'gencost']) t_is(ppc['areas'], ppci['areas'], 12, [t, 'areas']) t_is(ppc['A'], ppci['A'], 12, [t, 'A']) t_is(ppc['N'], ppci['N'], 12, [t, 'N']) t = 'ppc = e2i_data(ppc) - repeat : ' ppc = e2i_data(ppc) t_is(ppc['bus'], ppci['bus'], 12, [t, 'bus']) t_is(ppc['branch'], ppci['branch'], 12, [t, 'branch']) t_is(ppc['gen'], ppci['gen'], 12, [t, 'gen']) t_is(ppc['gencost'], ppci['gencost'], 12, [t, 'gencost']) t_is(ppc['areas'], ppci['areas'], 12, [t, 'areas']) t_is(ppc['A'], ppci['A'], 12, [t, 'A']) t_is(ppc['N'], ppci['N'], 12, [t, 'N']) t = 'ppc = i2e_data(ppc) : ' ppc = i2e_data(ppc) t_is(ppc['bus'], ppce['bus'], 12, [t, 'bus']) t_is(ppc['branch'], ppce['branch'], 12, [t, 'branch']) t_is(ppc['gen'], ppce['gen'], 12, [t, 'gen']) t_is(ppc['gencost'], ppce['gencost'], 12, [t, 'gencost']) t_is(ppc['areas'], ppce['areas'], 12, [t, 'areas']) t_is(ppc['A'], ppce['A'], 12, [t, 'A']) t_is(ppc['N'], ppce['N'], 12, [t, 'N']) ##----- val = e2i_data/i2e_data(ppc, val, ...) ----- t = 'val = e2i_data(ppc, val, \'bus\')' ppc = e2i_data(ppce) got = e2i_data(ppc, ppce['xbus'], 'bus') ex = ppce['xbus'] ex = delete(ex, 5, 0) t_is(got, ex, 12, t) t = 'val = i2e_data(ppc, val, oldval, \'bus\')' tmp = ones(ppce['xbus'].shape) tmp[5, :] = ppce['xbus'][5, :] got = i2e_data(ppc, ex, tmp, 'bus') t_is(got, ppce['xbus'], 12, t) t = 'val = e2i_data(ppc, val, \'bus\', 1)' got = e2i_data(ppc, ppce['xbus'], 'bus', 1) ex = ppce['xbus'] ex = delete(ex, 5, 1) t_is(got, ex, 12, t) t = 'val = i2e_data(ppc, val, oldval, \'bus\', 1)' tmp = ones(ppce['xbus'].shape) tmp[:, 5] = ppce['xbus'][:, 5] got = i2e_data(ppc, ex, tmp, 'bus', 1) t_is(got, ppce['xbus'], 12, t) t = 'val = e2i_data(ppc, val, \'gen\')' got = e2i_data(ppc, ppce['xgen'], 'gen') ex = ppce['xgen'][[3, 1, 0], :] t_is(got, ex, 12, t) t = 'val = i2e_data(ppc, val, oldval, \'gen\')' tmp = ones(ppce['xgen'].shape) tmp[2, :] = ppce['xgen'][2, :] got = i2e_data(ppc, ex, tmp, 'gen') t_is(got, ppce['xgen'], 12, t) t = 'val = e2i_data(ppc, val, \'gen\', 1)' got = e2i_data(ppc, ppce['xgen'], 'gen', 1) ex = ppce['xgen'][:, [3, 1, 0]] t_is(got, ex, 12, t) t = 'val = i2e_data(ppc, val, oldval, \'gen\', 1)' tmp = ones(ppce['xgen'].shape) tmp[:, 2] = ppce['xgen'][:, 2] got = i2e_data(ppc, ex, tmp, 'gen', 1) t_is(got, ppce['xgen'], 12, t) t = 'val = e2i_data(ppc, val, \'branch\')' got = e2i_data(ppc, ppce['xbranch'], 'branch') ex = ppce['xbranch'] ex = delete(ex, 6, 0) t_is(got, ex, 12, t) t = 'val = i2e_data(ppc, val, oldval, \'branch\')' tmp = ones(ppce['xbranch'].shape) tmp[6, :] = ppce['xbranch'][6, :] got = i2e_data(ppc, ex, tmp, 'branch') t_is(got, ppce['xbranch'], 12, t) t = 'val = e2i_data(ppc, val, \'branch\', 1)' got = e2i_data(ppc, ppce['xbranch'], 'branch', 1) ex = ppce['xbranch'] ex = delete(ex, 6, 1) t_is(got, ex, 12, t) t = 'val = i2e_data(ppc, val, oldval, \'branch\', 1)' tmp = ones(ppce['xbranch'].shape) tmp[:, 6] = ppce['xbranch'][:, 6] got = i2e_data(ppc, ex, tmp, 'branch', 1) t_is(got, ppce['xbranch'], 12, t) t = 'val = e2i_data(ppc, val, {\'branch\', \'gen\', \'bus\'})' got = e2i_data(ppc, ppce['xrows'], ['branch', 'gen', 'bus']) ex = r_[ppce['xbranch'][list(range(6)) + list(range(7, 10)), :4], ppce['xgen'][[3, 1, 0], :], ppce['xbus'][list(range(5)) + list(range(6, 10)), :4], -1 * ones((2, 4))] t_is(got, ex, 12, t) t = 'val = i2e_data(ppc, val, oldval, {\'branch\', \'gen\', \'bus\'})' tmp1 = ones(ppce['xbranch'][:, :4].shape) tmp1[6, :4] = ppce['xbranch'][6, :4] tmp2 = ones(ppce['xgen'].shape) tmp2[2, :] = ppce['xgen'][2, :] tmp3 = ones(ppce['xbus'][:, :4].shape) tmp3[5, :4] = ppce['xbus'][5, :4] tmp = r_[tmp1, tmp2, tmp3] got = i2e_data(ppc, ex, tmp, ['branch', 'gen', 'bus']) t_is(got, ppce['xrows'], 12, t) t = 'val = e2i_data(ppc, val, {\'branch\', \'gen\', \'bus\'}, 1)' got = e2i_data(ppc, ppce['xcols'], ['branch', 'gen', 'bus'], 1) ex = r_[ppce['xbranch'][list(range(6)) + list(range(7, 10)), :4], ppce['xgen'][[3, 1, 0], :], ppce['xbus'][list(range(5)) + list(range(6, 10)), :4], -1 * ones((2, 4))].T t_is(got, ex, 12, t) t = 'val = i2e_data(ppc, val, oldval, {\'branch\', \'gen\', \'bus\'}, 1)' tmp1 = ones(ppce['xbranch'][:, :4].shape) tmp1[6, :4] = ppce['xbranch'][6, :4] tmp2 = ones(ppce['xgen'].shape) tmp2[2, :] = ppce['xgen'][2, :] tmp3 = ones(ppce['xbus'][:, :4].shape) tmp3[5, :4] = ppce['xbus'][5, :4] tmp = r_[tmp1, tmp2, tmp3].T got = i2e_data(ppc, ex, tmp, ['branch', 'gen', 'bus'], 1) t_is(got, ppce['xcols'], 12, t) ##----- ppc = e2i_field/i2e_field(ppc, field, ...) ----- t = 'ppc = e2i_field(ppc, field, \'bus\')' ppc = e2i_field(ppce) ex = ppce['xbus'] ex = delete(ex, 5, 0) got = e2i_field(ppc, 'xbus', 'bus') t_is(got['xbus'], ex, 12, t) t = 'ppc = i2e_field(ppc, field, \'bus\')' got = i2e_field(got, 'xbus', ordering='bus') t_is(got['xbus'], ppce['xbus'], 12, t) t = 'ppc = e2i_field(ppc, field, \'bus\', 1)' ex = ppce['xbus'] ex = delete(ex, 5, 1) got = e2i_field(ppc, 'xbus', 'bus', 1) t_is(got['xbus'], ex, 12, t) t = 'ppc = i2e_field(ppc, field, \'bus\', 1)' got = i2e_field(got, 'xbus', ordering='bus', dim=1) t_is(got['xbus'], ppce['xbus'], 12, t) t = 'ppc = e2i_field(ppc, field, \'gen\')' ex = ppce['xgen'][[3, 1, 0], :] got = e2i_field(ppc, 'xgen', 'gen') t_is(got['xgen'], ex, 12, t) t = 'ppc = i2e_field(ppc, field, \'gen\')' got = i2e_field(got, 'xgen', ordering='gen') t_is(got['xgen'], ppce['xgen'], 12, t) t = 'ppc = e2i_field(ppc, field, \'gen\', 1)' ex = ppce['xgen'][:, [3, 1, 0]] got = e2i_field(ppc, 'xgen', 'gen', 1) t_is(got['xgen'], ex, 12, t) t = 'ppc = i2e_field(ppc, field, \'gen\', 1)' got = i2e_field(got, 'xgen', ordering='gen', dim=1) t_is(got['xgen'], ppce['xgen'], 12, t) t = 'ppc = e2i_field(ppc, field, \'branch\')' ex = ppce['xbranch'] ex = delete(ex, 6, 0) got = e2i_field(ppc, 'xbranch', 'branch') t_is(got['xbranch'], ex, 12, t) t = 'ppc = i2e_field(ppc, field, \'branch\')' got = i2e_field(got, 'xbranch', ordering='branch') t_is(got['xbranch'], ppce['xbranch'], 12, t) t = 'ppc = e2i_field(ppc, field, \'branch\', 1)' ex = ppce['xbranch'] ex = delete(ex, 6, 1) got = e2i_field(ppc, 'xbranch', 'branch', 1) t_is(got['xbranch'], ex, 12, t) t = 'ppc = i2e_field(ppc, field, \'branch\', 1)' got = i2e_field(got, 'xbranch', ordering='branch', dim=1) t_is(got['xbranch'], ppce['xbranch'], 12, t) t = 'ppc = e2i_field(ppc, field, {\'branch\', \'gen\', \'bus\'})' ex = r_[ppce['xbranch'][list(range(6)) + list(range(7, 10)), :4], ppce['xgen'][[3, 1, 0], :], ppce['xbus'][list(range(5)) + list(range(6, 10)), :4], -1 * ones((2, 4))] got = e2i_field(ppc, 'xrows', ['branch', 'gen', 'bus']) t_is(got['xrows'], ex, 12, t) t = 'ppc = i2e_field(ppc, field, {\'branch\', \'gen\', \'bus\'})' got = i2e_field(got, 'xrows', ordering=['branch', 'gen', 'bus']) t_is(got['xrows'], ppce['xrows'], 12, t) t = 'ppc = e2i_field(ppc, field, {\'branch\', \'gen\', \'bus\'}, 1)' ex = r_[ppce['xbranch'][list(range(6)) + list(range(7, 10)), :4], ppce['xgen'][[3, 1, 0], :], ppce['xbus'][list(range(5)) + list(range(6, 10)), :4], -1 * ones((2, 4))].T got = e2i_field(ppc, 'xcols', ['branch', 'gen', 'bus'], 1) t_is(got['xcols'], ex, 12, t) t = 'ppc = i2e_field(ppc, field, {\'branch\', \'gen\', \'bus\'})' got = i2e_field(got, 'xcols', ordering=['branch', 'gen', 'bus'], dim=1) t_is(got['xcols'], ppce['xcols'], 12, t) t = 'ppc = e2i_field(ppc, {\'field1\', \'field2\'}, ordering)' ex = ppce['x']['more'][[3, 1, 0], :] got = e2i_field(ppc, ['x', 'more'], 'gen') t_is(got['x']['more'], ex, 12, t) t = 'ppc = i2e_field(ppc, {\'field1\', \'field2\'}, ordering)' got = i2e_field(got, ['x', 'more'], ordering='gen') t_is(got['x']['more'], ppce['x']['more'], 12, t) t = 'ppc = e2i_field(ppc, {\'field1\', \'field2\'}, ordering, 1)' ex = ppce['x']['more'][:, [3, 1, 0]] got = e2i_field(ppc, ['x', 'more'], 'gen', 1) t_is(got['x']['more'], ex, 12, t) t = 'ppc = i2e_field(ppc, {\'field1\', \'field2\'}, ordering, 1)' got = i2e_field(got, ['x', 'more'], ordering='gen', dim=1) t_is(got['x']['more'], ppce['x']['more'], 12, t) ##----- more ppc = ext2int/int2ext(ppc) ----- t = 'ppc = ext2int(ppc) - bus/gen/branch only : ' ppce = loadcase(t_case_ext()) ppci = loadcase(t_case_int()) del ppce['gencost'] del ppce['areas'] del ppce['A'] del ppce['N'] del ppci['gencost'] del ppci['areas'] del ppci['A'] del ppci['N'] ppc = ext2int(ppce) t_is(ppc['bus'], ppci['bus'], 12, [t, 'bus']) t_is(ppc['branch'], ppci['branch'], 12, [t, 'branch']) t_is(ppc['gen'], ppci['gen'], 12, [t, 'gen']) t = 'ppc = ext2int(ppc) - no areas/A : ' ppce = loadcase(t_case_ext()) ppci = loadcase(t_case_int()) del ppce['areas'] del ppce['A'] del ppci['areas'] del ppci['A'] ppc = ext2int(ppce) t_is(ppc['bus'], ppci['bus'], 12, [t, 'bus']) t_is(ppc['branch'], ppci['branch'], 12, [t, 'branch']) t_is(ppc['gen'], ppci['gen'], 12, [t, 'gen']) t_is(ppc['gencost'], ppci['gencost'], 12, [t, 'gencost']) t_is(ppc['N'], ppci['N'], 12, [t, 'N']) t = 'ppc = ext2int(ppc) - Qg cost, no N : ' ppce = loadcase(t_case_ext()) ppci = loadcase(t_case_int()) del ppce['N'] del ppci['N'] ppce['gencost'] = c_[ppce['gencost'], ppce['gencost']] ppci['gencost'] = c_[ppci['gencost'], ppci['gencost']] ppc = ext2int(ppce) t_is(ppc['bus'], ppci['bus'], 12, [t, 'bus']) t_is(ppc['branch'], ppci['branch'], 12, [t, 'branch']) t_is(ppc['gen'], ppci['gen'], 12, [t, 'gen']) t_is(ppc['gencost'], ppci['gencost'], 12, [t, 'gencost']) t_is(ppc['areas'], ppci['areas'], 12, [t, 'areas']) t_is(ppc['A'], ppci['A'], 12, [t, 'A']) t = 'ppc = ext2int(ppc) - A, N are DC sized : ' ppce = loadcase(t_case_ext()) ppci = loadcase(t_case_int()) eVmQgcols = list(range(10, 20)) + list(range(24, 28)) iVmQgcols = list(range(9, 18)) + list(range(21, 24)) ppce['A'] = delete(ppce['A'], eVmQgcols, 1) ppce['N'] = delete(ppce['N'], eVmQgcols, 1) ppci['A'] = delete(ppci['A'], iVmQgcols, 1) ppci['N'] = delete(ppci['N'], iVmQgcols, 1) ppc = ext2int(ppce) t_is(ppc['bus'], ppci['bus'], 12, [t, 'bus']) t_is(ppc['branch'], ppci['branch'], 12, [t, 'branch']) t_is(ppc['gen'], ppci['gen'], 12, [t, 'gen']) t_is(ppc['gencost'], ppci['gencost'], 12, [t, 'gencost']) t_is(ppc['areas'], ppci['areas'], 12, [t, 'areas']) t_is(ppc['A'], ppci['A'], 12, [t, 'A']) t_is(ppc['N'], ppci['N'], 12, [t, 'N']) t = 'ppc = int2ext(ppc) - A, N are DC sized : ' ppc = int2ext(ppc) t_is(ppc['bus'], ppce['bus'], 12, [t, 'bus']) t_is(ppc['branch'], ppce['branch'], 12, [t, 'branch']) t_is(ppc['gen'], ppce['gen'], 12, [t, 'gen']) t_is(ppc['gencost'], ppce['gencost'], 12, [t, 'gencost']) t_is(ppc['areas'], ppce['areas'], 12, [t, 'areas']) t_is(ppc['A'], ppce['A'], 12, [t, 'A']) t_is(ppc['N'], ppce['N'], 12, [t, 'N']) t_end()
def opf(*args): """Solves an optimal power flow. Returns a C{results} dict. The data for the problem can be specified in one of three ways: 1. a string (ppc) containing the file name of a PYPOWER case which defines the data matrices baseMVA, bus, gen, branch, and gencost (areas is not used at all, it is only included for backward compatibility of the API). 2. a dict (ppc) containing the data matrices as fields. 3. the individual data matrices themselves. The optional user parameters for user constraints (C{A, l, u}), user costs (C{N, fparm, H, Cw}), user variable initializer (C{z0}), and user variable limits (C{zl, zu}) can also be specified as fields in a case dict, either passed in directly or defined in a case file referenced by name. When specified, C{A, l, u} represent additional linear constraints on the optimization variables, C{l <= A*[x z] <= u}. If the user specifies an C{A} matrix that has more columns than the number of "C{x}" (OPF) variables, then there are extra linearly constrained "C{z}" variables. For an explanation of the formulation used and instructions for forming the C{A} matrix, see the MATPOWER manual. A generalized cost on all variables can be applied if input arguments C{N}, C{fparm}, C{H} and C{Cw} are specified. First, a linear transformation of the optimization variables is defined by means of C{r = N * [x z]}. Then, to each element of C{r} a function is applied as encoded in the C{fparm} matrix (see MATPOWER manual). If the resulting vector is named C{w}, then C{H} and C{Cw} define a quadratic cost on w: C{(1/2)*w'*H*w + Cw * w}. C{H} and C{N} should be sparse matrices and C{H} should also be symmetric. The optional C{ppopt} vector specifies PYPOWER options. If the OPF algorithm is not explicitly set in the options PYPOWER will use the default solver, based on a primal-dual interior point method. For the AC OPF this is C{OPF_ALG = 560}. For the DC OPF, the default is C{OPF_ALG_DC = 200}. See L{ppoption} for more details on the available OPF solvers and other OPF options and their default values. The solved case is returned in a single results dict (described below). Also returned are the final objective function value (C{f}) and a flag which is C{True} if the algorithm was successful in finding a solution (success). Additional optional return values are an algorithm specific return status (C{info}), elapsed time in seconds (C{et}), the constraint vector (C{g}), the Jacobian matrix (C{jac}), and the vector of variables (C{xr}) as well as the constraint multipliers (C{pimul}). The single results dict is a PYPOWER case struct (ppc) with the usual baseMVA, bus, branch, gen, gencost fields, along with the following additional fields: - C{order} see 'help ext2int' for details of this field - C{et} elapsed time in seconds for solving OPF - C{success} 1 if solver converged successfully, 0 otherwise - C{om} OPF model object, see 'help opf_model' - C{x} final value of optimization variables (internal order) - C{f} final objective function value - C{mu} shadow prices on ... - C{var} - C{l} lower bounds on variables - C{u} upper bounds on variables - C{nln} - C{l} lower bounds on nonlinear constraints - C{u} upper bounds on nonlinear constraints - C{lin} - C{l} lower bounds on linear constraints - C{u} upper bounds on linear constraints - C{g} (optional) constraint values - C{dg} (optional) constraint 1st derivatives - C{df} (optional) obj fun 1st derivatives (not yet implemented) - C{d2f} (optional) obj fun 2nd derivatives (not yet implemented) - C{raw} raw solver output in form returned by MINOS, and more - C{xr} final value of optimization variables - C{pimul} constraint multipliers - C{info} solver specific termination code - C{output} solver specific output information - C{alg} algorithm code of solver used - C{var} - C{val} optimization variable values, by named block - C{Va} voltage angles - C{Vm} voltage magnitudes (AC only) - C{Pg} real power injections - C{Qg} reactive power injections (AC only) - C{y} constrained cost variable (only if have pwl costs) - (other) any user defined variable blocks - C{mu} variable bound shadow prices, by named block - C{l} lower bound shadow prices - C{Va}, C{Vm}, C{Pg}, C{Qg}, C{y}, (other) - C{u} upper bound shadow prices - C{Va}, C{Vm}, C{Pg}, C{Qg}, C{y}, (other) - C{nln} (AC only) - C{mu} shadow prices on nonlinear constraints, by named block - C{l} lower bounds - C{Pmis} real power mismatch equations - C{Qmis} reactive power mismatch equations - C{Sf} flow limits at "from" end of branches - C{St} flow limits at "to" end of branches - C{u} upper bounds - C{Pmis}, C{Qmis}, C{Sf}, C{St} - C{lin} - C{mu} shadow prices on linear constraints, by named block - C{l} lower bounds - C{Pmis} real power mistmatch equations (DC only) - C{Pf} flow limits at "from" end of branches (DC only) - C{Pt} flow limits at "to" end of branches (DC only) - C{PQh} upper portion of gen PQ-capability curve(AC only) - C{PQl} lower portion of gen PQ-capability curve(AC only) - C{vl} constant power factor constraint for loads - C{ycon} basin constraints for CCV for pwl costs - (other) any user defined constraint blocks - C{u} upper bounds - C{Pmis}, C{Pf}, C{Pf}, C{PQh}, C{PQl}, C{vl}, C{ycon}, - (other) - C{cost} user defined cost values, by named block @see: L{runopf}, L{dcopf}, L{uopf}, L{caseformat} @author: Ray Zimmerman (PSERC Cornell) @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Richard Lincoln """ ##----- initialization ----- t0 = time() ## start timer ## process input arguments ppc, ppopt = opf_args2(*args) ## add zero columns to bus, gen, branch for multipliers, etc if needed nb = shape(ppc['bus'])[0] ## number of buses nl = shape(ppc['branch'])[0] ## number of branches ng = shape(ppc['gen'])[0] ## number of dispatchable injections if shape(ppc['bus'])[1] < MU_VMIN + 1: ppc['bus'] = c_[ppc['bus'], zeros((nb, MU_VMIN + 1 - shape(ppc['bus'])[1]))] if shape(ppc['gen'])[1] < MU_QMIN + 1: ppc['gen'] = c_[ppc['gen'], zeros((ng, MU_QMIN + 1 - shape(ppc['gen'])[1]))] if shape(ppc['branch'])[1] < MU_ANGMAX + 1: ppc['branch'] = c_[ppc['branch'], zeros((nl, MU_ANGMAX + 1 - shape(ppc['branch'])[1]))] ##----- convert to internal numbering, remove out-of-service stuff ----- ppc = ext2int(ppc) ##----- construct OPF model object ----- om = opf_setup(ppc, ppopt) ##----- execute the OPF ----- results, success, raw = opf_execute(om, ppopt) ##----- revert to original ordering, including out-of-service stuff ----- results = int2ext(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, MU_PMAX, MU_PMIN]) ] = 0 if len(results['order']['branch']['status']['off']) > 0: results['branch'][ ix_(results['order']['branch']['status']['off'], [PF, QF, PT, QT, MU_SF, MU_ST, MU_ANGMIN, MU_ANGMAX]) ] = 0 ##----- finish preparing output ----- et = time() - t0 ## compute elapsed time results['et'] = et results['success'] = success results['raw'] = raw return results
def i2e_data(ppc, val, oldval, ordering, dim=0): """Converts data from internal to external bus numbering. For a case dict using internal indexing, this function can be used to convert other data structures as well by passing in 3 or 4 extra parameters in addition to the case dict. If the value passed in the 2nd argument C{val} is a column vector, it will be converted according to the ordering specified by the 4th argument (C{ordering}, described below). If C{val} is an n-dimensional matrix, then the optional 5th argument (C{dim}, default = 0) can be used to specify which dimension to reorder. The 3rd argument (C{oldval}) is used to initialize the return value before converting C{val} to external indexing. In particular, any data corresponding to off-line gens or branches or isolated buses or any connected gens or branches will be taken from C{oldval}, with C[val} supplying the rest of the returned data. The C{ordering} argument is used to indicate whether the data corresponds to bus-, gen- or branch-ordered data. It can be one of the following three strings: 'bus', 'gen' or 'branch'. For data structures with multiple blocks of data, ordered by bus, gen or branch, they can be converted with a single call by specifying C[ordering} as a list of strings. Any extra elements, rows, columns, etc. beyond those indicated in C{ordering}, are not disturbed. Examples: A_ext = i2e_data(ppc, A_int, A_orig, ['bus','bus','gen','gen'], 1) Converts an A matrix for user-supplied OPF constraints from internal to external ordering, where the columns of the A matrix correspond to bus voltage angles, then voltage magnitudes, then generator real power injections and finally generator reactive power injections. gencost_ext = i2e_data(ppc, gencost_int, gencost_orig, ['gen','gen'], 0) Converts a C{gencost} matrix that has both real and reactive power costs (in rows 1--ng and ng+1--2*ng, respectively). @see: L{e2i_data}, L{i2e_field}, L{int2ext}. """ from pypower.int2ext import int2ext if 'order' not in ppc: sys.stderr.write('i2e_data: ppc does not have the \'order\' field ' 'required for conversion back to external numbering.\n') return o = ppc["order"] if o['state'] != 'i': sys.stderr.write('i2e_data: ppc does not appear to be in internal ' 'order\n') return if isinstance(ordering, str): ## single set if ordering == 'gen': v = get_reorder(val, o[ordering]["i2e"], dim) else: v = val val = set_reorder(oldval, v, o[ordering]["status"]["on"], dim) else: ## multiple sets be = 0 ## base, external indexing bi = 0 ## base, internal indexing new_v = [] for ordr in ordering: ne = o["ext"][ordr].shape[0] ni = ppc[ordr].shape[0] v = get_reorder(val, bi + arange(ni), dim) oldv = get_reorder(oldval, be + arange(ne), dim) new_v.append( int2ext(ppc, v, oldv, ordr, dim) ) be = be + ne bi = bi + ni ni = val.shape[dim] if ni > bi: ## the rest v = get_reorder(val, arange(bi, ni), dim) new_v.append(v) val = concatenate(new_v, dim) return val
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 i2e_data(ppc, val, oldval, ordering, dim=0): """Converts data from internal to external bus numbering. For a case dict using internal indexing, this function can be used to convert other data structures as well by passing in 3 or 4 extra parameters in addition to the case dict. If the value passed in the 2nd argument C{val} is a column vector, it will be converted according to the ordering specified by the 4th argument (C{ordering}, described below). If C{val} is an n-dimensional matrix, then the optional 5th argument (C{dim}, default = 0) can be used to specify which dimension to reorder. The 3rd argument (C{oldval}) is used to initialize the return value before converting C{val} to external indexing. In particular, any data corresponding to off-line gens or branches or isolated buses or any connected gens or branches will be taken from C{oldval}, with C[val} supplying the rest of the returned data. The C{ordering} argument is used to indicate whether the data corresponds to bus-, gen- or branch-ordered data. It can be one of the following three strings: 'bus', 'gen' or 'branch'. For data structures with multiple blocks of data, ordered by bus, gen or branch, they can be converted with a single call by specifying C[ordering} as a list of strings. Any extra elements, rows, columns, etc. beyond those indicated in C{ordering}, are not disturbed. Examples: A_ext = i2e_data(ppc, A_int, A_orig, ['bus','bus','gen','gen'], 1) Converts an A matrix for user-supplied OPF constraints from internal to external ordering, where the columns of the A matrix correspond to bus voltage angles, then voltage magnitudes, then generator real power injections and finally generator reactive power injections. gencost_ext = i2e_data(ppc, gencost_int, gencost_orig, ['gen','gen'], 0) Converts a C{gencost} matrix that has both real and reactive power costs (in rows 1--ng and ng+1--2*ng, respectively). @see: L{e2i_data}, L{i2e_field}, L{int2ext}. """ if 'order' not in ppc: sys.stderr.write('i2e_data: ppc does not have the \'order\' field ' 'required for conversion back to external numbering.\n') return o = ppc["order"] if o['state'] != 'i': sys.stderr.write('i2e_data: ppc does not appear to be in internal ' 'order\n') return if isinstance(ordering, str): ## single set if ordering == 'gen': v = get_reorder(val, o[ordering]["i2e"], dim) else: v = val val = set_reorder(oldval, v, o[ordering]["status"]["on"], dim) else: ## multiple sets be = 0 ## base, external indexing bi = 0 ## base, internal indexing new_v = [] for ordr in ordering: ne = o["ext"][ordr].shape[0] ni = ppc[ordr].shape[0] v = get_reorder(val, bi + arange(ni), dim) oldv = get_reorder(oldval, be + arange(ne), dim) new_v.append( int2ext(ppc, v, oldv, ordr, dim) ) be = be + ne bi = bi + ni ni = val.shape[dim] if ni > bi: ## the rest v = get_reorder(val, arange(bi, ni), dim) new_v.append(v) val = concatenate(new_v, dim) return val
def solveropfnlp_4(ppc, solver="ipopt"): if solver == "ipopt": opt = SolverFactory( "ipopt", executable= "/home/iso/PycharmProjects/opfLC_python3/Python3/py_solvers/ipopt-linux64/ipopt" ) if solver == "bonmin": opt = SolverFactory( "bonmin", executable= "/home/iso/PycharmProjects/opfLC_python3/Python3/py_solvers/bonmin-linux64/bonmin" ) if solver == "knitro": opt = SolverFactory( "knitro", executable="D:/ICT/Artelys/Knitro 10.2.1/knitroampl/knitroampl") ppc = ext2int(ppc) # convert to continuous indexing starting from 0 # Gather information about the system # ============================================================= baseMVA, bus, gen, branch = \ ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc["branch"] nb = bus.shape[0] # number of buses ng = gen.shape[0] # number of generators nl = branch.shape[0] # number of lines # generator buses gb = tolist(np.array(gen[:, GEN_BUS]).astype(int)) sb = find((bus[:, BUS_TYPE] == REF)) # slack bus index fr = branch[:, F_BUS].astype(int) # from bus indices to = branch[:, T_BUS].astype(int) # to bus indices tr0 = copy(branch[:, TAP]) # transformation ratios tr0[find(tr0 == 0)] = 1 # set to 1 transformation ratios that are 0 tp = find(branch[:, TAP] != 0) # lines with tap changers ntp = find(branch[:, TAP] == 0) # lines without tap changers # Tap changer settings dudtap = 0.01 # Voltage per unit variation with tap changes tapmax = 10 # Highest tap changer setting tapmin = -10 # Lowest tap changer setting # Shunt element options stepmax = 1 # maximum step of the shunt element Bs0 = bus[:, BS] / baseMVA # shunt elements susceptance sd = find(bus[:, BS] != 0) # buses with shunt devices r = branch[:, BR_R] # branch resistances x = branch[:, BR_X] # branch reactances b = branch[:, BR_B] # branch susceptances start_time = time.clock() # Admittance matrix computation # ============================================================= # Set tap ratios and shunt elements to neutral position branch[:, TAP] = 1 bus[:, BS] = 0 y = makeYbus(baseMVA, bus, branch)[0] # admittance matrix yk = 1. / (r + x * 1j) # branch admittance yft = yk + 0.5j * b # branch admittance + susceptance gk = yk.real # branch resistance # Optimization # ============================================================= branch[find(branch[:, RATE_A] == 0), RATE_A] = 9999 # set undefined Sflow limit to 9999 Smax = branch[:, RATE_A] / baseMVA # Max. Sflow # Power demand parameters Pd = bus[:, PD] / baseMVA Qd = bus[:, QD] / baseMVA # Max and min Pg and Qg Pg_max = zeros(nb) Pg_max[gb] = gen[:, PMAX] / baseMVA Pg_min = zeros(nb) Pg_min[gb] = gen[:, PMIN] / baseMVA Qg_max = zeros(nb) Qg_max[gb] = gen[:, QMAX] / baseMVA Qg_min = zeros(nb) Qg_min[gb] = gen[:, QMIN] / baseMVA # Vmax and Vmin vectors Vmax = bus[:, VMAX] Vmin = bus[:, VMIN] vm = bus[:, VM] va = bus[:, VA] * pi / 180 # create a new optimization model model = ConcreteModel() # Define sets # ------------ model.bus = Set(ordered=True, initialize=range(nb)) # Set of all buses model.gen = Set(ordered=True, initialize=gb) # Set of buses with generation model.line = Set(ordered=True, initialize=range(nl)) # Set of all lines model.taps = Set(ordered=True, initialize=tp) # Set of all lines with tap changers model.shunt = Set(ordered=True, initialize=sd) # Set of buses with shunt elements # Define variables # ----------------- # Voltage magnitudes vector (vm) model.vm = Var(model.bus) # Voltage angles vector (va) model.va = Var(model.bus) # Reactive power generation, synchronous machines(SM) (Qg) model.Qg = Var(model.gen) Qg0 = zeros(nb) Qg0[gb] = gen[:, QG] / baseMVA # Active power generation, synchronous machines(SM) (Pg) model.Pg = Var(model.gen) Pg0 = zeros(nb) Pg0[gb] = gen[:, PG] / baseMVA # Active and reactive power from at all branches model.Pf = Var(model.line) model.Qf = Var(model.line) # Active and reactive power to at all branches model.Pt = Var(model.line) model.Qt = Var(model.line) # Transformation ratios model.tr = Var(model.taps) # Tap changer positions + their bounds model.tap = Var(model.taps, bounds=(tapmin, tapmax)) # Shunt susceptance model.Bs = Var(model.shunt) # Shunt positions + their bounds model.s = Var(model.shunt, bounds=(0, stepmax)) # Warm start the problem # ------------------------ for i in range(nb): model.vm[i] = vm[i] model.va[i] = va[i] if i in gb: model.Pg[i] = Pg0[i] model.Qg[i] = Qg0[i] for i in range(nl): model.Pf[i] = vm[fr[i]] ** 2 * abs(yft[i]) / (tr0[i] ** 2) * np.cos(-ang(yft[i])) -\ vm[fr[i]] * vm[to[i]] * abs(yk[i]) / tr0[i] * np.cos(va[fr[i]] - va[to[i]] - ang(yk[i])) model.Qf[i] = vm[fr[i]] ** 2 * abs(yft[i]) / (tr0[i] ** 2) * np.sin(-ang(yft[i])) -\ vm[fr[i]] * vm[to[i]] * abs(yk[i]) / tr0[i] * np.sin(va[fr[i]] - va[to[i]] - ang(yk[i])) model.Pt[i] = vm[to[i]] ** 2 * abs(yft[i]) * np.cos(-ang(yft[i])) -\ vm[to[i]] * vm[fr[i]] * abs(yk[i]) / tr0[i] * np.cos(va[to[i]] - va[fr[i]] - ang(yk[i])) model.Qt[i] = vm[to[i]] ** 2 * abs(yft[i]) * np.sin(-ang(yft[i])) -\ vm[to[i]] * vm[fr[i]] * abs(yk[i]) / tr0[i] * np.sin(va[to[i]] - va[fr[i]] - ang(yk[i])) for i in tp: model.tr[i] = tr0[i] for i in sd: model.Bs[i] = Bs0[i] # Define constraints # ---------------------------- # Equalities: # ------------ # Active power flow equalities def powerflowact(model, i): bfrom_i = tp[find(fr[tp] == i)] # branches from bus i with transformer bto_i = tp[find(to[tp] == i)] # branches to bus i with transformer allbut_i = find(bus[:, BUS_I] != i) # Set of other buses if i in gb: return model.Pg[i]-Pd[i] == sum(model.vm[i] * model.vm[j] * abs(y[i, j]) * cos(model.va[i] - model.va[j] - ang(y[i, j])) for j in allbut_i) - \ sum(model.vm[i] * model.vm[to[j]] * abs(yk[j]) * cos(model.va[i] - model.va[to[j]] - ang(yk[j])) * (1 / model.tr[j] - 1) for j in bfrom_i) - \ sum(model.vm[i] * model.vm[fr[j]] * abs(yk[j]) * cos(model.va[i] - model.va[fr[j]] - ang(yk[j])) * (1 / model.tr[j] - 1) for j in bto_i) + \ model.vm[i] ** 2 * (sum(abs(yk[j]) * (1 / model.tr[j]**2 - 1) * np.cos(- ang(yk[j])) for j in bfrom_i) + real(y[i, i])) else: return sum(model.vm[i] * model.vm[j] * abs(y[i, j]) * cos(model.va[i] - model.va[j] - ang(y[i, j])) for j in allbut_i) - \ sum(model.vm[i] * model.vm[to[j]] * abs(yk[j]) * cos(model.va[i] - model.va[to[j]] - ang(yk[j])) * (1 / model.tr[j] - 1) for j in bfrom_i) - \ sum(model.vm[i] * model.vm[fr[j]] * abs(yk[j]) * cos(model.va[i] - model.va[fr[j]] - ang(yk[j])) * (1 / model.tr[j] - 1) for j in bto_i) + \ model.vm[i] ** 2 * (sum(abs(yk[j]) * (1 / model.tr[j]**2 - 1) * np.cos(- ang(yk[j])) for j in bfrom_i) + real(y[i, i])) == -Pd[i] model.const1 = Constraint(model.bus, rule=powerflowact) # Reactive power flow equalities def powerflowreact(model, i): bfrom_i = tp[find(fr[tp] == i)] # branches from bus i with transformer bto_i = tp[find(to[tp] == i)] # branches to bus i with transformer allbut_i = find(bus[:, BUS_I] != i) # Set of other buses sh = sd[find(sd == i)] # Detect shunt elements if i in gb: return model.Qg[i]-Qd[i] == \ sum(model.vm[i] * model.vm[j] * abs(y[i, j]) * sin(model.va[i] - model.va[j] - ang(y[i, j])) for j in allbut_i) - \ sum(model.vm[i] * model.vm[to[j]] * abs(yk[j]) * sin(model.va[i] - model.va[to[j]] - ang(yk[j])) * (1 / model.tr[j] - 1) for j in bfrom_i) - \ sum(model.vm[i] * model.vm[fr[j]] * abs(yk[j]) * sin(model.va[i] - model.va[fr[j]] - ang(yk[j])) * (1 / model.tr[j] - 1) for j in bto_i) + \ model.vm[i] ** 2 * (sum(abs(yk[j]) * (1 / model.tr[j] ** 2 - 1) * np.sin(- ang(yk[j])) for j in bfrom_i) - imag(y[i, i]) - sum(model.Bs[j] for j in sh)) else: return sum(model.vm[i] * model.vm[j] * abs(y[i, j]) * sin(model.va[i] - model.va[j] - ang(y[i, j])) for j in allbut_i) - \ sum(model.vm[i] * model.vm[to[j]] * abs(yk[j]) * sin(model.va[i] - model.va[to[j]] - ang(yk[j])) * (1 / model.tr[j] - 1) for j in bfrom_i) - \ sum(model.vm[i] * model.vm[fr[j]] * abs(yk[j]) * sin(model.va[i] - model.va[fr[j]] - ang(yk[j])) * (1 / model.tr[j] - 1) for j in bto_i) + \ model.vm[i] ** 2 * (sum(abs(yk[j]) * (1 / model.tr[j] ** 2 - 1) * np.sin(- ang(yk[j])) for j in bfrom_i) - imag(y[i, i]) - sum(model.Bs[j] for j in sh)) == - Qd[i] model.const2 = Constraint(model.bus, rule=powerflowreact) # Active power from def pfrom(model, i): if i in tp: return model.Pf[i] == model.vm[fr[i]] ** 2 * abs(yft[i]) / (model.tr[i] ** 2) * np.cos(-ang(yft[i])) - \ model.vm[fr[i]] * model.vm[to[i]] * abs(yk[i]) / model.tr[i] * \ cos(model.va[fr[i]] - model.va[to[i]] - ang(yk[i])) else: return model.Pf[i] == model.vm[fr[i]] ** 2 * abs(yft[i]) / tr0[i] ** 2 * np.cos(-ang(yft[i])) - \ model.vm[fr[i]] * model.vm[to[i]] * abs(yk[i]) / tr0[i] * \ cos(model.va[fr[i]] - model.va[to[i]] - ang(yk[i])) model.const3 = Constraint(model.line, rule=pfrom) # Reactive power from def qfrom(model, i): if i in tp: return model.Qf[i] == model.vm[fr[i]] ** 2 * abs(yft[i]) / (model.tr[i] ** 2) * np.sin(-ang(yft[i])) - \ model.vm[fr[i]] * model.vm[to[i]] * abs(yk[i]) / model.tr[i] * \ sin(model.va[fr[i]] - model.va[to[i]] - ang(yk[i])) else: return model.Qf[i] == model.vm[fr[i]] ** 2 * abs(yft[i]) / tr0[i] ** 2 * np.sin(-ang(yft[i])) - \ model.vm[fr[i]] * model.vm[to[i]] * abs(yk[i]) / tr0[i] * \ sin(model.va[fr[i]] - model.va[to[i]] - ang(yk[i])) model.const4 = Constraint(model.line, rule=qfrom) # Active power to def pto(model, i): if i in tp: return model.Pt[i] == model.vm[to[i]] ** 2 * abs(yft[i]) * np.cos(-ang(yft[i])) - \ model.vm[to[i]] * model.vm[fr[i]] * abs(yk[i]) / model.tr[i] * \ cos(model.va[to[i]] - model.va[fr[i]] - ang(yk[i])) else: return model.Pt[i] == model.vm[to[i]] ** 2 * abs(yft[i]) * np.cos(-ang(yft[i])) - \ model.vm[to[i]] * model.vm[fr[i]] * abs(yk[i]) / tr0[i] * \ cos(model.va[to[i]] - model.va[fr[i]] - ang(yk[i])) model.const5 = Constraint(model.line, rule=pto) # Reactive power to def qto(model, i): if i in tp: return model.Qt[i] == model.vm[to[i]] ** 2 * abs(yft[i]) * np.sin(-ang(yft[i])) - \ model.vm[to[i]] * model.vm[fr[i]] * abs(yk[i]) / model.tr[i] * \ sin(model.va[to[i]] - model.va[fr[i]] - ang(yk[i])) else: return model.Qt[i] == model.vm[to[i]] ** 2 * abs(yft[i]) * np.sin(-ang(yft[i])) - \ model.vm[to[i]] * model.vm[fr[i]] * abs(yk[i]) / tr0[i] * \ sin(model.va[to[i]] - model.va[fr[i]] - ang(yk[i])) model.const6 = Constraint(model.line, rule=qto) # Slack bus phase angle model.const7 = Constraint(expr=model.va[sb[0]] == 0) # Transformation ratio equalities def trfunc(model, i): return model.tr[i] == 1 + dudtap * model.tap[i] model.const8 = Constraint(model.taps, rule=trfunc) # Shunt susceptance equality def shuntfunc(model, i): return model.Bs[i] == model.s[i] / stepmax * Bs0[i] model.const9 = Constraint(model.shunt, rule=shuntfunc) # Inequalities: # ---------------- # Active power generator limits Pg_min <= Pg <= Pg_max def genplimits(model, i): return Pg_min[i] <= model.Pg[i] <= Pg_max[i] model.const10 = Constraint(model.gen, rule=genplimits) # Reactive power generator limits Qg_min <= Qg <= Qg_max def genqlimits(model, i): return Qg_min[i] <= model.Qg[i] <= Qg_max[i] model.const11 = Constraint(model.gen, rule=genqlimits) # Voltage constraints ( Vmin <= V <= Vmax ) def vlimits(model, i): return Vmin[i] <= model.vm[i] <= Vmax[i] model.const12 = Constraint(model.bus, rule=vlimits) # Sfrom line limit def sfrommax(model, i): return model.Pf[i]**2 + model.Qf[i]**2 <= Smax[i]**2 model.const13 = Constraint(model.line, rule=sfrommax) # Sto line limit def stomax(model, i): return model.Pt[i]**2 + model.Qt[i]**2 <= Smax[i]**2 model.const14 = Constraint(model.line, rule=stomax) # Set objective function # ------------------------ def obj_fun(model): return sum(gk[i] * ((model.vm[fr[i]] / model.tr[i])**2 + model.vm[to[i]]**2 - 2 / model.tr[i] * model.vm[fr[i]] * model.vm[to[i]] * cos(model.va[fr[i]] - model.va[to[i]])) for i in tp) + \ sum(gk[i] * ((model.vm[fr[i]] / tr0[i]) ** 2 + model.vm[to[i]] ** 2 - 2 / tr0[i] * model.vm[fr[i]] * model.vm[to[i]] * cos(model.va[fr[i]] - model.va[to[i]])) for i in ntp) model.obj = Objective(rule=obj_fun, sense=minimize) mt = time.clock() - start_time # Modeling time # Execute solve command with the selected solver # ------------------------------------------------ start_time = time.clock() results = opt.solve(model, tee=True) et = time.clock() - start_time # Elapsed time print(results) # Update the case info with the optimized variables and approximate the continuous variables to discrete values # ============================================================================================================== for i in range(nb): if i in sd: bus[i, BS] = round(model.s[i].value) * Bs0[i] * baseMVA bus[i, VM] = model.vm[i].value # Bus voltage magnitudes bus[i, VA] = model.va[i].value * 180 / pi # Bus voltage angles # Update transformation ratios for i in range(nl): if i in tp: branch[i, TAP] = 1 + dudtap * round(model.tap[i].value) # Update gen matrix variables for i in range(ng): gen[i, PG] = model.Pg[gb[i]].value * baseMVA gen[i, QG] = model.Qg[gb[i]].value * baseMVA gen[i, VG] = bus[gb[i], VM] # Convert to external (original) numbering and save case results ppc = int2ext(ppc) ppc['bus'][:, 1:] = bus[:, 1:] branch[:, 0:2] = ppc['branch'][:, 0:2] ppc['branch'] = branch ppc['gen'][:, 1:] = gen[:, 1:] # Execute a second optimization with only the discrete approximated values (requires solveropfnlp_2) sol = solveropfnlp_2(ppc) sol['mt'] = sol['mt'] + mt sol['et'] = sol['et'] + et sol['tap'] = zeros((tp.shape[0], 1)) for i in range(tp.shape[0]): sol['tap'][i] = round(model.tap[tp[i]].value) sol['shunt'] = zeros((sd.shape[0], 1)) for i in range(sd.shape[0]): sol['shunt'][i] = round(model.s[sd[i]].value) # ppc solved case is returned return sol