def optimal_power_flow_energy_reserve(*args): casedata = args[0] # Target power flow modelling beta = args[1] # The reserve level mpc = loadcase(casedata) # Import the power flow modelling ## convert to internal indexing mpc = ext2int(mpc) baseMVA, bus, gen, branch,gencost = mpc["baseMVA"], mpc["bus"], mpc["gen"], mpc["branch"],mpc["gencost"] # nb = shape(mpc['bus'])[0] ## number of buses nl = shape(mpc['branch'])[0] ## number of branches ng = shape(mpc['gen'])[0] ## number of dispatchable injections ## Formualte the stat = branch[:, BR_STATUS] ## ones at in-service branches b = stat / branch[:, BR_X] ## series susceptance tap = ones(nl) ## default tap ratio = 1 i = find(branch[:, TAP]) ## indices of non-zero tap ratios tap[i] = branch[i, TAP] ## assign non-zero tap ratios ## build connection matrix Cft = Cf - Ct for line and from - to buses f = branch[:, F_BUS] ## list of "from" buses t = branch[:, T_BUS] ## list of "to" buses i = r_[range(nl), range(nl)] ## double set of row indices ## connection matrix Cft = sparse((r_[ones(nl), -ones(nl)], (i, r_[f, t])), (nl, nb)) ## build Bf such that Bf * Va is the vector of real branch powers injected ## at each branch's "from" bus Bf = sparse((r_[b, -b], (i, r_[f, t])), shape=(nl, nb)) ## = spdiags(b, 0, nl, nl) * Cft ## build Bbus Bbus = Cft.T * Bf # The distribution factor Distribution_factor = sparse(Bf*inv(Bbus)) Cg = sparse((ones(ng), (gen[:, GEN_BUS], arange(ng))), (nb, ng)) # Sparse index generation method is different from the way of matlab Cd = sparse((ones(nb), (bus[:, BUS_I], arange(nb))), (nb, nb)) # Sparse index load Pd = sum(bus[:,PD]) # Total power demand # Formulate the problem lb = concatenate((gen[:,PMIN],zeros(ng))) # extend the ub = concatenate((gen[:,PMAX],gen[:,PMAX])) Aeq = sparse(concatenate((ones(ng),zeros(ng)))) beq = [Pd] Aineq = sparse(hstack([Distribution_factor * Cg,zeros((nl,ng))])) Aineq = vstack([Aineq, -Aineq]) # The ramp reserve requirement Aineq = vstack([Aineq, sparse((r_[ones(ng), ones(ng)], (r_[arange(ng), arange(ng)], r_[arange(ng), ng+arange(ng)])), (ng, 2*ng))]) Aineq = vstack([Aineq, sparse((r_[-ones(ng), ones(ng)], (r_[arange(ng), arange(ng)], r_[arange(ng), ng+arange(ng)])), (ng, 2*ng))]) bineq = concatenate((branch[:, RATE_A] + Distribution_factor * Cd * bus[:, PD], branch[:, RATE_A] - Distribution_factor * Cd * bus[:, PD])) bineq = concatenate((bineq, gen[:, PMAX])) bineq = concatenate((bineq, -gen[:, PMIN])) c = concatenate((gencost[:,5],zeros(ng))) Q = diag(concatenate((gencost[:,4],zeros(ng)))) (Pg,obj) = miqp_gurobi(c = c,Q = Q, Aeq = Aeq, beq = beq, A = Aineq, b = bineq, xmin = lb,xmax = ub) obj = obj + sum(gencost[:,6]) return Pg, obj
def draw_network(fignr=754): from matplotlib.pyplot import figure, show import networkx as nx casedata = get_topology() ppc = casedata ppopt = ppoption(PF_ALG=2) ppc = ext2int(ppc) figure(fignr) g = nx.Graph() i = ppc['bus'][:, BUS_I].astype(int) g.add_nodes_from(i, bgcolor='green') #nx.draw_networkx_nodes(g,pos=nx.spring_layout(g)) fr = ppc['branch'][:, F_BUS].astype(int) to = ppc['branch'][:, T_BUS].astype(int) g.add_edges_from(zip(fr, to), color='magenta') nx.draw(g, with_labels=True, node_size=1000,node_color='skyblue',width=0.5) show()
def draw_network(fignr=754): from matplotlib.pyplot import figure, show import networkx as nx casedata = get_topology() ppc = casedata ppopt = ppoption(PF_ALG=2) ppc = ext2int(ppc) figure(fignr) g = nx.Graph() i = ppc['bus'][:, BUS_I].astype(int) g.add_nodes_from(i, bgcolor='green') #nx.draw_networkx_nodes(g,pos=nx.spring_layout(g)) fr = ppc['branch'][:, F_BUS].astype(int) to = ppc['branch'][:, T_BUS].astype(int) g.add_edges_from(zip(fr, to), color='magenta') nx.draw(g, with_labels=True, node_size=1000, node_color='skyblue', width=0.5) show()
def run(mpc): """ Gurobi based optimal power flow modelling and solution :param mpc: The input case of optimal power flow :return: obtained solution """ # Data format from pypower.idx_brch import F_BUS, T_BUS, BR_R, BR_X, TAP, SHIFT, BR_STATUS, RATE_A from pypower.idx_cost import MODEL, NCOST, PW_LINEAR, COST, POLYNOMIAL from pypower.idx_bus import BUS_TYPE, REF, VA, VM, PD, GS, VMAX, VMIN, BUS_I, QD from pypower.idx_gen import GEN_BUS, VG, PG, QG, PMAX, PMIN, QMAX, QMIN from pypower.ext2int import ext2int mpc = ext2int(mpc) baseMVA, bus, gen, branch, gencost = mpc["baseMVA"], mpc["bus"], mpc[ "gen"], mpc["branch"], mpc["gencost"] nb = shape(mpc['bus'])[0] # number of buses nl = shape(mpc['branch'])[0] # number of branches ng = shape(mpc['gen'])[0] # number of dispatchable injections f = branch[:, F_BUS] ## list of "from" buses t = branch[:, T_BUS] ## list of "to" buses i = range(nl) ## double set of row indices # Connection matrix Cf = sparse((ones(nl), (i, f)), (nl, nb)) Ct = sparse((ones(nl), (i, t)), (nl, nb)) Cg = sparse((ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) Branch_R = branch[:, BR_R] Branch_X = branch[:, BR_X] Cf = Cf.T Ct = Ct.T # Obtain the boundary information Slmax = branch[:, RATE_A] / baseMVA Pij_l = -Slmax Qij_l = -Slmax Iij_l = zeros(nl) Vm_l = turn_to_power(bus[:, VMIN], 2) Pg_l = gen[:, PMIN] / baseMVA Qg_l = gen[:, QMIN] / baseMVA Pij_u = Slmax Qij_u = Slmax Iij_u = Slmax Vm_u = turn_to_power(bus[:, VMAX], 2) Pg_u = 2 * gen[:, PMAX] / baseMVA Qg_u = 2 * gen[:, QMAX] / baseMVA lx = concatenate([Pij_l, Qij_l, Iij_l, Vm_l, Pg_l, Qg_l]) ux = concatenate([Pij_u, Qij_u, Iij_u, Vm_u, Pg_u, Qg_u]) model = Model("OPF") # Define the decision variables x = {} nx = 3 * nl + nb + 2 * ng for i in range(nx): x[i] = model.addVar(lb=lx[i], ub=ux[i], vtype=GRB.CONTINUOUS) # Add system level constraints Aeq_p = hstack([ Ct - Cf, zeros((nb, nl)), -diag(Ct * Branch_R) * Ct, zeros((nb, nb)), Cg, zeros((nb, ng)) ]) beq_p = bus[:, PD] / baseMVA # Add constraints for each sub system Aeq_q = hstack([ zeros((nb, nl)), Ct - Cf, -diag(Ct * Branch_X) * Ct, zeros((nb, nb)), zeros((nb, ng)), Cg ]) beq_q = bus[:, QD] / baseMVA Aeq_KVL = hstack([ -2 * diags(Branch_R), -2 * diags(Branch_X), diags(turn_to_power(Branch_R, 2)) + diags(turn_to_power(Branch_X, 2)), Cf.T - Ct.T, zeros((nl, 2 * ng)) ]) beq_KVL = zeros(nl) Aeq = vstack([Aeq_p, Aeq_q, Aeq_KVL]) Aeq = Aeq.todense() beq = concatenate([beq_p, beq_q, beq_KVL]) neq = len(beq) for i in range(neq): expr = 0 for j in range(nx): expr += x[j] * Aeq[i, j] model.addConstr(lhs=expr, sense=GRB.EQUAL, rhs=beq[i]) for i in range(nl): model.addConstr(x[i] * x[i] + x[i + nl] * x[i + nl] <= x[i + 2 * nl] * x[f[i] + 3 * nl], name='"rc{0}"'.format(i)) obj = 0 for i in range(ng): obj += gencost[i, 4] * x[i + 3 * nl + nb] * x[ i + 3 * nl + nb] * baseMVA * baseMVA + gencost[i, 5] * x[ i + 3 * nl + nb] * baseMVA + gencost[i, 6] model.setObjective(obj) model.Params.OutputFlag = 0 model.Params.LogToConsole = 0 model.Params.DisplayInterval = 1 model.optimize() xx = [] for v in model.getVars(): xx.append(v.x) obj = obj.getValue() Pij = xx[0:nl] Qij = xx[nl + 0:2 * nl] Iij = xx[2 * nl:3 * nl] Vi = xx[3 * nl:3 * nl + nb] Pg = xx[3 * nl + nb:3 * nl + nb + ng] Qg = xx[3 * nl + nb + ng:3 * nl + nb + 2 * ng] # for i in range(nl): # branch indexing exchange # if branch[i, F_BUS] > branch[i, T_BUS]: # temp = branch[i, F_BUS] # branch[i, F_BUS] = branch[i, T_BUS] # branch[i, T_BUS] = temp f = branch[:, F_BUS] ## list of "from" buses t = branch[:, T_BUS] ## list of "to" buses # i = range(nl) ## double set of row indices area = ancestor_children_generation(f, t, range(nb)) Pi = Cg * Pg - bus[:, PD] / baseMVA Qi = Cg * Qg - bus[:, QD] / baseMVA for i in range(nb): # If the bus is the root bus, only the children information is required. if len(area[i]["Ai"]) == 0: print(i) expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Pij[area[i]["Cbranch"][0][j]] print(expr - Pi[i]) expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Qij[area[i]["Cbranch"][0][j]] print(expr - Qi[i]) elif len(area[i]["Cbranch"]) == 0: # This bus is the lead node print(i) print(Pij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_R[area[i]["Abranch"][0][0]] + Pi[i]) print(Qij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_X[area[i]["Abranch"][0][0]] + Qi[i]) print(Vi[int(area[i]["Ai"][0])] - Vi[i] - 2 * Branch_R[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] - 2 * Branch_X[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] + Iij[area[i]["Abranch"][0][0]] * (Branch_R[area[i]["Abranch"][0][0]]**2 + Branch_X[area[i]["Abranch"][0][0]]**2)) print( Pij[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] + Qij[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] <= Vi[int(area[i]["Ai"][0])] * Iij[area[i]["Abranch"][0][0]]) else: print(i) expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Pij[area[i]["Cbranch"][0][j]] print(Pij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_R[area[i]["Abranch"][0][0]] + Pi[i] - expr) expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Qij[area[i]["Cbranch"][0][j]] print(Qij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_X[area[i]["Abranch"][0][0]] + Qi[i] - expr) print(Vi[int(area[i]["Ai"][0])] - Vi[i] - 2 * Branch_R[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] - 2 * Branch_X[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] + Iij[area[i]["Abranch"][0][0]] * (Branch_R[area[i]["Abranch"][0][0]]**2 + Branch_X[area[i]["Abranch"][0][0]]**2)) print( Pij[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] + Qij[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] <= Vi[int(area[i]["Ai"][0])] * Iij[area[i]["Abranch"][0][0]]) obj = 0 for i in range(ng): print(Pg[i] - Pi[int(gen[i, GEN_BUS])] - bus[int(gen[i, GEN_BUS]), PD] / baseMVA) print(Qg[i] - Qi[int(gen[i, GEN_BUS])] - bus[int(gen[i, GEN_BUS]), QD] / baseMVA) print(int(gen[i, GEN_BUS])) obj += gencost[i, 4] * Pg[i] * Pg[i] * baseMVA * baseMVA + gencost[ i, 5] * Pg[i] * baseMVA + gencost[i, 6] # Connection matrix Cg = sparse((ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) Branch_R = branch[:, BR_R] Branch_X = branch[:, BR_X] # Obtain the boundary information Slmax = branch[:, RATE_A] / baseMVA Pij_l = -Slmax Qij_l = -Slmax Iij_l = zeros(nl) Vm_l = turn_to_power(bus[:, VMIN], 2) Pg_l = gen[:, PMIN] / baseMVA Qg_l = gen[:, QMIN] / baseMVA Pi_l = -bus[:, PD] / baseMVA + Cg * Pg_l / baseMVA Qi_l = -bus[:, QD] / baseMVA + Cg * Qg_l / baseMVA Pij_u = Slmax Qij_u = Slmax Iij_u = Slmax Vm_u = turn_to_power(bus[:, VMAX], 2) Pg_u = 2 * gen[:, PMAX] / baseMVA Qg_u = 2 * gen[:, QMAX] / baseMVA Pi_u = -bus[:, PD] / baseMVA + Cg * Pg_u # Boundary error Qi_u = -bus[:, QD] / baseMVA + Cg * Qg_u # Boundary error model = Model("OPF") # Define the decision variables, compact set Pij = {} Qij = {} Iij = {} Vi = {} Pg = {} Qg = {} Pi = {} Qi = {} for i in range(nl): Pij[i] = model.addVar(lb=Pij_l[i], ub=Pij_u[i], vtype=GRB.CONTINUOUS, name="Pij{0}".format(i)) Qij[i] = model.addVar(lb=Qij_l[i], ub=Qij_u[i], vtype=GRB.CONTINUOUS, name="Qij{0}".format(i)) Iij[i] = model.addVar(lb=Iij_l[i], ub=Iij_u[i], vtype=GRB.CONTINUOUS, name="Iij{0}".format(i)) for i in range(nb): Vi[i] = model.addVar(lb=Vm_l[i], ub=Vm_u[i], vtype=GRB.CONTINUOUS, name="V{0}".format(i)) for i in range(ng): Pg[i] = model.addVar(lb=Pg_l[i], ub=Pg_u[i], vtype=GRB.CONTINUOUS, name="Pg{0}".format(i)) Qg[i] = model.addVar(lb=Qg_l[i], ub=Qg_u[i], vtype=GRB.CONTINUOUS, name="Qg{0}".format(i)) for i in range(nb): Pi[i] = model.addVar(lb=Pi_l[i], ub=Pi_u[i], vtype=GRB.CONTINUOUS, name="Pi{0}".format(i)) Qi[i] = model.addVar(lb=Qi_l[i], ub=Qi_u[i], vtype=GRB.CONTINUOUS, name="Qi{0}".format(i)) # For each area, before decomposition # Add system level constraints for i in range(nb): # If the bus is the root bus, only the children information is required. if len(area[i]["Ai"]) == 0: expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Pij[area[i]["Cbranch"][0][j]] model.addConstr(lhs=expr - Pi[i], sense=GRB.EQUAL, rhs=0) expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Qij[area[i]["Cbranch"][0][j]] model.addConstr(lhs=expr - Qi[i], sense=GRB.EQUAL, rhs=0) elif len(area[i]["Cbranch"]) == 0: # This bus is the lead node model.addConstr(lhs=Pij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_R[area[i]["Abranch"][0][0]] + Pi[i], sense=GRB.EQUAL, rhs=0) model.addConstr(lhs=Qij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_X[area[i]["Abranch"][0][0]] + Qi[i], sense=GRB.EQUAL, rhs=0) model.addConstr(lhs=Vi[int(area[i]["Ai"][0])] - Vi[i] - 2 * Branch_R[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] - 2 * Branch_X[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] + Iij[area[i]["Abranch"][0][0]] * (Branch_R[area[i]["Abranch"][0][0]]**2 + Branch_X[area[i]["Abranch"][0][0]]**2), sense=GRB.EQUAL, rhs=0) model.addConstr( Pij[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] + Qij[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] <= Vi[area[i]["Ai"][0]] * Iij[area[i]["Abranch"][0][0]], name="rc{0}".format(i)) else: expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Pij[area[i]["Cbranch"][0][j]] model.addConstr(lhs=Pij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_R[area[i]["Abranch"][0][0]] + Pi[i] - expr, sense=GRB.EQUAL, rhs=0) expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Qij[area[i]["Cbranch"][0][j]] model.addConstr(lhs=Qij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_X[area[i]["Abranch"][0][0]] + Qi[i] - expr, sense=GRB.EQUAL, rhs=0) model.addConstr(lhs=Vi[int(area[i]["Ai"][0])] - Vi[i] - 2 * Branch_R[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] - 2 * Branch_X[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] + Iij[area[i]["Abranch"][0][0]] * (Branch_R[area[i]["Abranch"][0][0]]**2 + Branch_X[area[i]["Abranch"][0][0]]**2), sense=GRB.EQUAL, rhs=0) model.addConstr( Pij[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] + Qij[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] <= Vi[area[i]["Ai"][0]] * Iij[area[i]["Abranch"][0][0]], name="rc{0}".format(i)) obj = 0 for i in range(ng): model.addConstr(lhs=Pg[i] - Pi[int(gen[i, GEN_BUS])], sense=GRB.EQUAL, rhs=bus[int(gen[i, GEN_BUS]), PD] / baseMVA) model.addConstr(lhs=Qg[i] - Qi[int(gen[i, GEN_BUS])], sense=GRB.EQUAL, rhs=bus[int(gen[i, GEN_BUS]), QD] / baseMVA) obj += gencost[i, 4] * Pg[i] * Pg[i] * baseMVA * baseMVA + gencost[ i, 5] * Pg[i] * baseMVA + gencost[i, 6] model.setObjective(obj) model.Params.OutputFlag = 0 model.Params.LogToConsole = 0 model.Params.DisplayInterval = 1 model.optimize() Pij = [] Qij = [] Iij = [] Vi = [] Pg = [] Qg = [] Pi = [] Qi = [] for i in range(nl): Pij.append(model.getVarByName("Pij{0}".format(i)).X) Qij.append(model.getVarByName("Qij{0}".format(i)).X) Iij.append(model.getVarByName("Iij{0}".format(i)).X) for i in range(nb): Vi.append(model.getVarByName("V{0}".format(i)).X) Pi.append(model.getVarByName("Pi{0}".format(i)).X) Qi.append(model.getVarByName("Qi{0}".format(i)).X) for i in range(ng): Pg.append(model.getVarByName("Pg{0}".format(i)).X) Qg.append(model.getVarByName("Qg{0}".format(i)).X) obj = obj.getValue() primal_residual = [] for i in range(nl): primal_residual.append(Pij[i] * Pij[i] + Qij[i] * Qij[i] - Iij[i] * Vi[int(f[i])]) return obj, primal_residual
def Ybus(ppc): ppc = ext2int(ppc) baseMVA, bus, gen, branch = ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc[ "branch"] Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) return Ybus
def run_sim(ppc, elements, dynopt=None, events=None, recorder=None): """ Run a time-domain simulation Inputs: ppc PYPOWER load flow case elements Dictionary of dynamic model objects (machines, controllers, etc) with Object ID as key events Events object recorder Recorder object (empty) Outputs: recorder Recorder object (with data) """ ######### # SETUP # ######### # Get version information ver = pydyn_ver() print('PYPOWER-Dynamics ' + ver['Version'] + ', ' + ver['Date']) # Program options if dynopt: h = dynopt['h'] t_sim = dynopt['t_sim'] max_err = dynopt['max_err'] max_iter = dynopt['max_iter'] verbose = dynopt['verbose'] else: # Default program options h = 0.01 # step length (s) t_sim = 5 # simulation time (s) max_err = 0.0001 # Maximum error in network iteration (voltage mismatches) max_iter = 25 # Maximum number of network iterations verbose = False if dynopt['sample_period']: sample_rate = max(int(dynopt['sample_period'] / h) - 1, 0) else: sample_rate = 0 # Make lists of current injection sources (generators, external grids, etc) and controllers sources = [] controllers = [] for element in elements.values(): if element.__module__ in [ 'pydyn.sym_order6a', 'pydyn.sym_order6b', 'pydyn.sym_order4', 'pydyn.ext_grid', 'pydyn.vsc_average', 'pydyn.asym_1cage', 'pydyn.asym_2cage' ]: sources.append(element) if element.__module__ == 'pydyn.controller': controllers.append(element) # Set up interfaces interfaces = init_interfaces(elements) interfaces0 = init_interfaces0(elements) # find events events_controllers = [] # find blocks that create events in controllers for element_id in elements.keys(): element = elements[element_id] if element.__module__ == 'pydyn.controller': for line in element.equations: if line[1] == 'EVENT': new_event = [element_id, line[0], line[2]] + line[3:] events_controllers.append(new_event) ################## # INITIALISATION # ################## print('Initialising models...') if not verbose: ppopt = ppoption(VERBOSE=0, OUT_ALL=0) else: ppopt = ppoption() #print('not verbose') # Run power flow and update bus voltages and angles in PYPOWER case object results, success = runpf(ppc, ppopt) ppc["bus"][:, VM] = results["bus"][:, VM] ppc["bus"][:, VA] = results["bus"][:, VA] # Build Ybus matrix ppc_int = ext2int(ppc) baseMVA, bus, branch = ppc_int["baseMVA"], ppc_int["bus"], ppc_int[ "branch"] Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) # Build modified Ybus matrix try: Ybus = mod_Ybus(Ybus, elements, bus, ppc_int['gen'], baseMVA) except: bp() Ybus = mod_Ybus(Ybus, elements, bus, ppc_int['gen'], baseMVA) # Calculate initial voltage phasors v0 = bus[:, VM] * (np.cos(np.radians(bus[:, VA])) + 1j * np.sin(np.radians(bus[:, VA]))) # Initialise sources from load flow for source in sources: if source.__module__ in ['pydyn.asym_1cage', 'pydyn.asym_2cage']: # Asynchronous machine source_bus = ppc_int['bus'][source.bus_no, 0].astype(np.int64) v_source = v0[source_bus] source.initialise(v_source, 0) else: # Generator or VSC source_bus = ppc_int['gen'][source.gen_no, 0].astype(np.int64) S_source = np.complex(results["gen"][source.gen_no, 1] / baseMVA, results["gen"][source.gen_no, 2] / baseMVA) v_source = v0[source_bus] source.initialise(v_source, S_source) # initialise bus elements['bus'] = bus_int(ppc) elements['sys_matrices'] = sys_matrices_int(ppc) #elements['branch'] = ppc['branch'] # Do we need interfaces0? # Interface controllers and machines (for initialisation) #for intf in interfaces: for k in range(len(interfaces)): intf = interfaces[k] intf0 = interfaces0[k] int_type = intf[0] var_name = intf0[1] source_var = intf[1] source_id = intf[2] dest_var = intf[3] dest_id = intf[4] if int_type == 'OUTPUT': # If an output, interface in the reverse direction for initialisation #intf[2].signals[var_name] = intf[3].signals[var_name] #if (intf0[2] != source_id) or (var_name != source_var) or (var_name != dest_var): # bp() elements[source_id].signals[source_var] = elements[ dest_id].signals[dest_var] else: # Inputs are interfaced in normal direction during initialisation #intf[3].signals[var_name] = intf[2].signals[var_name] #if (intf0[3] != dest_id) or (var_name != source_var) or (var_name != dest_var): # bp() elements[dest_id].signals[dest_var] = elements[source_id].signals[ source_var] #try: # element_source.signals[ var_name_source ] = element_dest.signals[ var_name_dest ] #except: # bp() # Initialise controllers for controller in controllers: controller.initialise() ############# # MAIN LOOP # ############# sample_age = 0 if events == None: print('Warning: no events!') # Factorise Ybus matrix Ybus_inv = splu(Ybus) y1 = [] v_prev = v0 print('Simulating...') for t in range(int(t_sim / h) + 1): if np.mod(t, 1 / h) == 0 and verbose: print('t=' + str(t * h) + 's') # Interface controllers and machines #for intf in interfaces: for k in range(len(interfaces)): intf = interfaces[k] intf0 = interfaces0[k] var_name = intf0[1] source_var = intf[1] source_id = intf[2] dest_var = intf[3] dest_id = intf[4] #if var_name_dest not in element_dest.signals.keys(): #bp() #element_dest.signals[ var_name_dest ] = element_source.signals[ var_name_source ] #if (intf0[2] != source_id) or (var_name != source_var) or (var_name != dest_var) or (intf0[3] != dest_id): # bp() elements[dest_id].signals[dest_var] = elements[source_id].signals[ source_var] #intf[3].signals[var_name] = intf[2].signals[var_name] # Solve differential equations for j in range(4): # Solve step of differential equations for element in elements.values(): try: element.solve_step(h, j) except: bp() element.solve_step(h, j) # Interface with network equations v_prev = solve_network(sources, v_prev, Ybus_inv, ppc_int, len(bus), max_err, max_iter) # check for events for event_c in events_controllers: new_event = None ctrl = event_c[0] ctrl_var = event_c[2] var_result = event_c[1] condition = elements[ctrl].signals[ctrl_var] if condition >= 1: #event_type = event_c[0] #node = event_c[1] new_event = [np.round(t * h, 5)] + event_c[3:] #print(new_event) try: events.event_stack.append(new_event) elements[ctrl].signals[var_result] = 1.0 #bp() except: bp() else: elements[ctrl].signals[var_result] = 0.0 if sample_age < sample_rate: sample_age += 1 else: sample_age = 0 if recorder != None: # Record signals or states recorder.record_variables(t * h, elements) if events != None: #if new_event != None: # bp() # Check event stack ppc, refactorise = events.handle_events(np.round(t * h, 5), elements, ppc, baseMVA) if refactorise == True: # Rebuild Ybus from new ppc_int ppc_int = ext2int(ppc) baseMVA, bus, branch = ppc_int["baseMVA"], ppc_int[ "bus"], ppc_int["branch"] Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) # Rebuild modified Ybus Ybus = mod_Ybus(Ybus, elements, bus, ppc_int['gen'], baseMVA) # Refactorise Ybus Ybus_inv = splu(Ybus) # Solve network equations v_prev = solve_network(sources, v_prev, Ybus_inv, ppc_int, len(bus), max_err, max_iter) # update the voltage in 'bus' matrix ppc['bus'][:, VM] = abs(v_prev) ppc['bus'][:, VA] = 2 * np.arctan(v_prev.imag / (abs(v_prev) + v_prev.real)) # update the system matrices elements['bus'].update(ppc) elements['sys_matrices'].update(ppc) #bp() return recorder
def run(mpc): """ Gurobi based optimal power flow modelling and solution :param mpc: The input case of optimal power flow :return: obtained solution """ mpc = ext2int(mpc) baseMVA, bus, gen, branch, gencost = mpc["baseMVA"], mpc["bus"], mpc[ "gen"], mpc["branch"], mpc["gencost"] nb = shape(mpc['bus'])[0] # number of buses nl = shape(mpc['branch'])[0] # number of branches ng = shape(mpc['gen'])[0] # number of dispatchable injections f = branch[:, F_BUS] ## list of "from" buses t = branch[:, T_BUS] ## list of "to" buses # Modify the bus information Branch_R = branch[:, BR_R] Branch_X = branch[:, BR_X] Slmax = branch[:, RATE_A] / baseMVA gen[:, PMAX] = gen[:, PMAX] / baseMVA gen[:, PMIN] = gen[:, PMIN] / baseMVA gen[:, QMAX] = gen[:, QMAX] / baseMVA gen[:, QMIN] = gen[:, QMIN] / baseMVA gencost[:, 4] = gencost[:, 4] * baseMVA * baseMVA gencost[:, 5] = gencost[:, 5] * baseMVA bus[:, PD] = bus[:, PD] / baseMVA bus[:, QD] = bus[:, QD] / baseMVA area = ancestor_children_generation(f, t, nb, Branch_R, Branch_X, Slmax, gen, bus, gencost, baseMVA) # Initialize algorithm for each sub area # 1) For each area, for self observation # x:=[Pg,Qg,pi,qi,Pi,Qi,Vi,Ii] # y:=[pi,qi,Pi,Qi,Vi,Ii,Pij,Qij,Vij,Iij] f = f.tolist() t = t.tolist() for i in range(nb): area[i]["PG"] = (area[i]["PGMAX"] + area[i]["PGMIN"]) / 2 area[i]["QG"] = (area[i]["QGMIN"] + area[i]["QGMAX"]) / 2 # Observation of x area[i]["pi"] = area[i]["PG"] - area[i]["PD"] area[i]["qi"] = area[i]["QG"] - area[i]["QD"] area[i]["Pi"] = area[i]["pi"] area[i]["Qi"] = area[i]["qi"] area[i]["Vi"] = (area[i]["VMIN"] + area[i]["VMAX"]) / 2 if area[i]["TYPE"] != "ROOT": area[i]["Ii"] = (area[i]["Pi"]**2 + area[i]["Qi"]**2) / area[i]["Vi"] # The self observation area[i]["pi_y"] = area[i]["pi"] area[i]["qi_y"] = area[i]["qi"] if area[i]["TYPE"] != "ROOT": area[i]["Vi_y"] = area[i]["Vi"] area[i]["Ii_y"] = area[i]["Ii"] area[i]["Pi_y"] = area[i]["Pi"] area[i]["Qi_y"] = area[i]["Qi"] # The multipliers area[i]["mu_pi"] = area[i]["pi"] - area[i]["pi_y"] area[i]["mu_qi"] = area[i]["qi"] - area[i]["qi_y"] if area[i]["TYPE"] != "ROOT": area[i]["mu_Vi"] = area[i]["Vi"] - area[i]["Vi_y"] area[i]["mu_Ii"] = area[i]["Ii"] - area[i]["Ii_y"] area[i]["mu_Pi"] = area[i]["Pi"] - area[i]["Pi_y"] area[i]["mu_Qi"] = area[i]["Qi"] - area[i]["Qi_y"] area[i]["COST"] = 0 # Spread the information to the observatory observatory = [] # Store the voltage of parent bus and children power flow information # 1)The ancestor bus voltage information is stored in the observotory # 2)The children bus power and current information is stored in the observotory for i in range(nl): temp = {} temp["Vij_x"] = area[int(f[i])]["Vi"] temp["Pij_x"] = area[int(t[i])]["Pi"] temp["Qij_x"] = area[int(t[i])]["Qi"] temp["Iij_x"] = area[int(t[i])]["Ii"] temp["Vij_y"] = area[int(f[i])]["Vi"] temp["Pij_y"] = area[int(t[i])]["Pi"] temp["Qij_y"] = area[int(t[i])]["Qi"] temp["Iij_y"] = area[int(t[i])]["Ii"] temp["mu_Vij"] = 0 temp["mu_Pij"] = 0 temp["mu_Qij"] = 0 temp["mu_Iij"] = 0 observatory.append(temp) # Begin the iteration, Gap = 1000 Gap_index = [] Dual_gap_index = [] Obj_index = [] k = 0 kmax = 10000 ru = 700 # The iteration mu = 5 t = 2 while k <= kmax and Gap > 0.0001 * 2: observatory0 = deepcopy(observatory) area0 = deepcopy(area) for i in range(nb): (area, observatory) = sub_problem(area, observatory, i, ru / 2) # # multiplier update # for i in range(nb): # area[i]["mu_pi"] += ru * (area[i]["pi"] - area[i]["pi_y"]) # area[i]["mu_qi"] += ru * (area[i]["qi"] - area[i]["qi_y"]) # if area[i]["TYPE"] != "ROOT": # area[i]["mu_Vi"] += ru * (area[i]["Vi"] - area[i]["Vi_y"]) # area[i]["mu_Ii"] += ru * (area[i]["Ii"] - area[i]["Ii_y"]) # area[i]["mu_Pi"] += ru * (area[i]["Pi"] - area[i]["Pi_y"]) # area[i]["mu_Qi"] += ru * (area[i]["Qi"] - area[i]["Qi_y"]) # for i in range(nl): # observatory[i]["mu_Vij"] += ru * (observatory[i]["Vij_x"] - observatory[i]["Vij_y"]) # observatory[i]["mu_Pij"] += ru * (observatory[i]["Pij_x"] - observatory[i]["Pij_y"]) # observatory[i]["mu_Qij"] += ru * (observatory[i]["Qij_x"] - observatory[i]["Qij_y"]) # observatory[i]["mu_Iij"] += ru * (observatory[i]["Iij_x"] - observatory[i]["Iij_y"]) # Calculate the gap gap = 0 for i in range(nb): gap += (area[i]["pi"] - area[i]["pi_y"])**2 gap += (area[i]["qi"] - area[i]["qi_y"])**2 if area[i]["TYPE"] != "ROOT": gap += (area[i]["Vi"] - area[i]["Vi_y"])**2 gap += (area[i]["Ii"] - area[i]["Ii_y"])**2 gap += (area[i]["Pi"] - area[i]["Pi_y"])**2 gap += (area[i]["Qi"] - area[i]["Qi_y"])**2 for i in range(nl): gap += (observatory[i]["Vij_x"] - observatory[i]["Vij_y"])**2 gap += (observatory[i]["Pij_x"] - observatory[i]["Pij_y"])**2 gap += (observatory[i]["Qij_x"] - observatory[i]["Qij_y"])**2 gap += (observatory[i]["Iij_x"] - observatory[i]["Iij_y"])**2 dual_gap = 0 for i in range(nb): dual_gap += (area0[i]["pi_y"] - area[i]["pi_y"])**2 dual_gap += (area0[i]["qi_y"] - area[i]["qi_y"])**2 if area[i]["TYPE"] != "ROOT": dual_gap += (area0[i]["Vi_y"] - area[i]["Vi_y"])**2 dual_gap += (area0[i]["Ii_y"] - area[i]["Ii_y"])**2 dual_gap += (area0[i]["Pi_y"] - area[i]["Pi_y"])**2 dual_gap += (area0[i]["Qi_y"] - area[i]["Qi_y"])**2 for i in range(nl): dual_gap += (observatory0[i]["Vij_y"] - observatory[i]["Vij_y"])**2 dual_gap += (observatory0[i]["Pij_y"] - observatory[i]["Pij_y"])**2 dual_gap += (observatory0[i]["Qij_y"] - observatory[i]["Qij_y"])**2 dual_gap += (observatory0[i]["Iij_y"] - observatory[i]["Iij_y"])**2 # if dual_gap * mu < gap: # ru = ru * t # if gap * mu < dual_gap: # ru = ru / t Gap = sqrt(gap) Gap_index.append(Gap) Dual_gap_index.append(sqrt(dual_gap)) obj = 0 for i in range(nb): obj += area[i]["COST"] k = k + 1 print(k) print(Gap) print(obj) print(sqrt(dual_gap)) # compute the objective function obj = 0 for i in range(nb): obj += area[i]["COST"] Gap_index = array(Gap_index) Dual_gap_index = array(Dual_gap_index) plt.figure(1) plt.subplot(211) plt.plot(Gap_index) plt.ylabel('Primal residual') plt.subplot(212) plt.plot(Dual_gap_index) plt.ylabel('Dual residual') plt.show() return obj, area
def run(mpc): """ Gurobi based optimal power flow modelling and solution :param mpc: The input case of optimal power flow :return: obtained solution """ mpc = ext2int(mpc) baseMVA, bus, gen, branch, gencost = mpc["baseMVA"], mpc["bus"], mpc["gen"], mpc["branch"], mpc["gencost"] nb = shape(mpc['bus'])[0] # number of buses nl = shape(mpc['branch'])[0] # number of branches ng = shape(mpc['gen'])[0] # number of dispatchable injections f = branch[:, F_BUS] ## list of "from" buses t = branch[:, T_BUS] ## list of "to" buses i = range(nl) ## double set of row indices # Connection matrix Cf = sparse((ones(nl), (i, f)), (nl, nb)) Ct = sparse((ones(nl), (i, t)), (nl, nb)) Cg = sparse((ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) # Connection matrix Cg = sparse((ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) Branch_R = branch[:, BR_R] Branch_X = branch[:, BR_X] Slmax = branch[:, RATE_A] / baseMVA gen[:, PMAX] = gen[:, PMAX] / baseMVA gen[:, PMIN] = gen[:, PMIN] / baseMVA gen[:, QMAX] = gen[:, QMAX] / baseMVA gen[:, QMIN] = gen[:, QMIN] / baseMVA gencost[:, 4] = gencost[:, 4] * baseMVA * baseMVA gencost[:, 5] = gencost[:, 5] * baseMVA bus[:, PD] = bus[:, PD] / baseMVA bus[:, QD] = bus[:, QD] / baseMVA area = ancestor_children_generation(f, t, nb, Branch_R, Branch_X, Slmax, gen, bus, gencost, baseMVA) M = inf # Formulate the centralized optimization problem according to the information provided by area model = Model("OPF") # Define the decision variables, compact set # X variables Pi_x = {} Qi_x = {} Ii_x = {} Vi_x = {} pi_x = {} qi_x = {} Pg = {} Qg = {} # Y variables # Part 1), self observation Pii_y = {} Qii_y = {} Iii_y = {} Vii_y = {} pii_y = {} qii_y = {} # Part 2), to the ancestor Pij_y = {} Qij_y = {} Iij_y = {} # Part 3), to the children. The definition is in accordance with the sequence of lines Vij_y = {} # For the given branch obj = 0 for i in range(nb): # The iteration from each bus Pi_x[i] = model.addVar(lb=-area[i]["SMAX"], ub=area[i]["SMAX"], vtype=GRB.CONTINUOUS, name="Pi_x{0}".format(i)) Qi_x[i] = model.addVar(lb=-area[i]["SMAX"], ub=area[i]["SMAX"], vtype=GRB.CONTINUOUS, name="Qi_x{0}".format(i)) Ii_x[i] = model.addVar(lb=-area[i]["SMAX"], ub=area[i]["SMAX"], vtype=GRB.CONTINUOUS, name="Ii_x{0}".format(i)) Vi_x[i] = model.addVar(lb=area[i]["VMIN"], ub=area[i]["VMAX"], vtype=GRB.CONTINUOUS, name="Vi_x{0}".format(i)) pi_x[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="pi_x{0}".format(i)) qi_x[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="qi_x{0}".format(i)) Pg[i] = model.addVar(lb=area[i]["PGMIN"], ub=area[i]["PGMAX"], vtype=GRB.CONTINUOUS, name="Pgi{0}".format(i)) Qg[i] = model.addVar(lb=area[i]["QGMIN"], ub=area[i]["QGMAX"], vtype=GRB.CONTINUOUS, name="Qgi{0}".format(i)) Pii_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="Pii_y{0}".format(i)) Qii_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="Qii_y{0}".format(i)) Iii_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="Iii_y{0}".format(i)) Vii_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="Vii_y{0}".format(i)) pii_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="pii_y{0}".format(i)) qii_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="qii_y{0}".format(i)) # For each branch, the following observation variables should be introduced # According to the sequence of lines for i in range(nl): Pij_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="Pij_y{0}".format(i)) Qij_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="Qij_y{0}".format(i)) Iij_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="Iij_y{0}".format(i)) Vij_y[i] = model.addVar(lb=-M, ub=M, vtype=GRB.CONTINUOUS, name="Vij_y{0}".format(i)) for i in range(nb): # Add constrain for each bus model.addConstr(Pg[i] - pi_x[i] == area[i]["PD"]) model.addConstr(Qg[i] - qi_x[i] == area[i]["QD"]) model.addConstr(Pi_x[i] * Pi_x[i] + Qi_x[i] * Qi_x[i] <= Ii_x[i] * Vi_x[i]) # Update the objective function obj += area[i]["a"] * Pg[i] * Pg[i] + area[i]["b"] * Pg[i] + area[i]["c"] # Add constrain for the observation of each bus # 1)Constrain for KCL equations # 2)Constrain for KVL equations if area[i]["TYPE"] == "ROOT": # Only KCL equation is required expr = 0 for j in range(len(area[i]["Ci"])): expr += Pij_y[area[i]["Cbranch"][j]] - Iij_y[area[i]["Cbranch"][j]] * Branch_R[area[i]["Cbranch"][j]] model.addConstr(pii_y[i] + expr == 0) expr = 0 for j in range(len(area[i]["Ci"])): expr += Qij_y[area[i]["Cbranch"][j]] - Iij_y[area[i]["Cbranch"][j]] * Branch_X[area[i]["Cbranch"][j]] model.addConstr(qii_y[i] + expr == 0) elif area[i]["TYPE"] == "LEAF": # Only KCL equation is required model.addConstr(pii_y[i] - Pii_y[i] == 0) model.addConstr(qii_y[i] - Qii_y[i] == 0) model.addConstr( Vij_y[area[i]["Abranch"]] - Vii_y[i] + 2 * area[i]["BR_R"] * Pii_y[i] + 2 * area[i]["BR_X"] * Qii_y[i] - Iii_y[i] * (area[i]["BR_R"] ** 2 + area[i]["BR_X"] ** 2) == 0) else: expr = 0 for j in range(len(area[i]["Ci"])): expr += Pij_y[area[i]["Cbranch"][j]] - Iij_y[area[i]["Cbranch"][j]] * Branch_R[area[i]["Cbranch"][j]] model.addConstr(pii_y[i] - Pii_y[i] + expr == 0) expr = 0 for j in range(len(area[i]["Ci"])): expr += Qij_y[area[i]["Cbranch"][j]] - Iij_y[area[i]["Cbranch"][j]] * Branch_X[area[i]["Cbranch"][j]] model.addConstr(qii_y[i] - Qii_y[i] + expr == 0) model.addConstr( Vij_y[area[i]["Abranch"]] - Vii_y[i] + 2 * area[i]["BR_R"] * Pii_y[i] + 2 * area[i]["BR_X"] * Qii_y[i] - Iii_y[i] * (area[i]["BR_R"] ** 2 + area[i]["BR_X"] ** 2) == 0) # Formulate consensus constraints # Add constraints # The introduction of Xii is to formulate the closed form of the solution model.addConstr(Pii_y[i] == Pi_x[i]) model.addConstr(Qii_y[i] == Qi_x[i]) model.addConstr(Vii_y[i] == Vi_x[i]) model.addConstr(Iii_y[i] == Ii_x[i]) model.addConstr(pii_y[i] == pi_x[i]) model.addConstr(qii_y[i] == qi_x[i]) # For each branch for i in range(nl): # which stands for the observatory for each line; The observatory constraints model.addConstr(Vij_y[i] == Vi_x[f[i]]) model.addConstr(Pij_y[i] == Pi_x[t[i]]) model.addConstr(Qij_y[i] == Qi_x[t[i]]) model.addConstr(Iij_y[i] == Ii_x[t[i]]) # from the perspective of nodes # for i in range(nb): # if area[i]["nChildren"] != 0: # for j in range(area[i]["nChildren"]): # model.addConstr(Vi_x[i] == Vij_y[area[i]["Cbranch"][j]]) # if area[i]["TYPE"] != "ROOT": # model.addConstr(Pi_x[i] == Pij_y[area[i]["Abranch"]]) # model.addConstr(Qi_x[i] == Qij_y[area[i]["Abranch"]]) # model.addConstr(Ii_x[i] == Iij_y[area[i]["Abranch"]]) model.setObjective(obj) model.Params.OutputFlag = 1 model.Params.LogToConsole = 1 model.Params.DisplayInterval = 1 model.optimize() Pi = [] Qi = [] Ii = [] Vi = [] pi = [] qi = [] pg = [] qg = [] for i in range(nb): Pi.append(model.getVarByName("Pi_x{0}".format(i)).X) Qi.append(model.getVarByName("Pi_x{0}".format(i)).X) Ii.append(model.getVarByName("Ii_x{0}".format(i)).X) Vi.append(model.getVarByName("Vi_x{0}".format(i)).X) pi.append(model.getVarByName("pi_x{0}".format(i)).X) qi.append(model.getVarByName("qi_x{0}".format(i)).X) pg.append(model.getVarByName("Pgi{0}".format(i)).X) qg.append(model.getVarByName("Qgi{0}".format(i)).X) obj = obj.getValue() primal_residual = [] for i in range(nb): primal_residual.append(Pi[i] * Pi[i] + Qi[i] * Qi[i] - Ii[i] * Vi[i]) return obj, primal_residual
def run(mpc): """ Gurobi based optimal power flow modelling and solution :param mpc: The input case of optimal power flow :return: obtained solution """ # Data format from pypower.idx_brch import F_BUS, T_BUS, BR_R, BR_X, TAP, SHIFT, BR_STATUS, RATE_A from pypower.idx_cost import MODEL, NCOST, PW_LINEAR, COST, POLYNOMIAL from pypower.idx_bus import BUS_TYPE, REF, VA, VM, PD, GS, VMAX, VMIN, BUS_I, QD from pypower.idx_gen import GEN_BUS, VG, PG, QG, PMAX, PMIN, QMAX, QMIN from pypower.ext2int import ext2int mpc = ext2int(mpc) baseMVA, bus, gen, branch, gencost = mpc["baseMVA"], mpc["bus"], mpc[ "gen"], mpc["branch"], mpc["gencost"] nb = shape(mpc['bus'])[0] ## number of buses nl = shape(mpc['branch'])[0] ## number of branches ng = shape(mpc['gen'])[0] ## number of dispatchable injections f = branch[:, F_BUS] ## list of "from" buses t = branch[:, T_BUS] ## list of "to" buses i = range(nl) ## double set of row indices # Connection matrix Cf = sparse((ones(nl), (i, f)), (nl, nb)) Ct = sparse((ones(nl), (i, t)), (nl, nb)) Cg = sparse((ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) Branch_R = branch[:, BR_R] Branch_X = branch[:, BR_X] Cf = Cf.T Ct = Ct.T # Obtain the boundary information Slmax = branch[:, RATE_A] / baseMVA Pij_l = -Slmax Qij_l = -Slmax Iij_l = zeros(nl) Vm_l = turn_to_power(bus[:, VMIN], 2) Pg_l = gen[:, PMIN] / baseMVA Qg_l = gen[:, QMIN] / baseMVA Pij_u = Slmax Qij_u = Slmax Iij_u = Slmax Vm_u = turn_to_power(bus[:, VMAX], 2) Pg_u = 2 * gen[:, PMAX] / baseMVA Qg_u = 2 * gen[:, QMAX] / baseMVA lx = concatenate([Pij_l, Qij_l, Iij_l, Vm_l, Pg_l, Qg_l]) ux = concatenate([Pij_u, Qij_u, Iij_u, Vm_u, Pg_u, Qg_u]) model = Model("OPF") # Define the decision variables x = {} nx = 3 * nl + nb + 2 * ng for i in range(nx): x[i] = model.addVar(lb=lx[i], ub=ux[i], vtype=GRB.CONTINUOUS) # Add system level constraints Aeq_p = hstack([ Ct - Cf, zeros((nb, nl)), -diag(Ct * Branch_R) * Ct, zeros((nb, nb)), Cg, zeros((nb, ng)) ]) beq_p = bus[:, PD] / baseMVA # Add constraints for each sub system Aeq_q = hstack([ zeros((nb, nl)), Ct - Cf, -diag(Ct * Branch_X) * Ct, zeros((nb, nb)), zeros((nb, ng)), Cg ]) beq_q = bus[:, QD] / baseMVA Aeq_KVL = hstack([ -2 * diags(Branch_R), -2 * diags(Branch_X), diags(turn_to_power(Branch_R, 2)) + diags(turn_to_power(Branch_X, 2)), Cf.T - Ct.T, zeros((nl, 2 * ng)) ]) beq_KVL = zeros(nl) Aeq = vstack([Aeq_p, Aeq_q, Aeq_KVL]) Aeq = Aeq.todense() beq = concatenate([beq_p, beq_q, beq_KVL]) neq = len(beq) for i in range(neq): expr = 0 for j in range(nx): expr += x[j] * Aeq[i, j] model.addConstr(lhs=expr, sense=GRB.EQUAL, rhs=beq[i]) for i in range(nl): model.addConstr(x[i] * x[i] + x[i + nl] * x[i + nl] <= x[i + 2 * nl] * x[f[i] + 3 * nl], name='"rc{0}"'.format(i)) obj = 0 for i in range(ng): obj += gencost[i, 4] * x[i + 3 * nl + nb] * x[ i + 3 * nl + nb] * baseMVA * baseMVA + gencost[i, 5] * x[ i + 3 * nl + nb] * baseMVA + gencost[i, 6] model.setObjective(obj) model.Params.OutputFlag = 0 model.Params.LogToConsole = 0 model.Params.DisplayInterval = 1 model.optimize() xx = [] for v in model.getVars(): xx.append(v.x) obj = obj.getValue() Pij = xx[0:nl] Qij = xx[nl + 0:2 * nl] Iij = xx[2 * nl:3 * nl] Vi = xx[3 * nl:3 * nl + nb] Pg = xx[3 * nl + nb:3 * nl + nb + ng] Qg = xx[3 * nl + nb + ng:3 * nl + nb + 2 * ng] primal_residual = [] for i in range(nl): primal_residual.append(Pij[i] * Pij[i] + Qij[i] * Qij[i] - Iij[i] * Vi[int(f[i])]) return xx, obj, primal_residual
def run(mpc): """ Gurobi based optimal power flow modelling and solution :param mpc: The input case of optimal power flow :return: obtained solution """ mpc = ext2int(mpc) baseMVA, bus, gen, branch, gencost = mpc["baseMVA"], mpc["bus"], mpc[ "gen"], mpc["branch"], mpc["gencost"] nb = shape(mpc['bus'])[0] # number of buses nl = shape(mpc['branch'])[0] # number of branches ng = shape(mpc['gen'])[0] # number of dispatchable injections f = branch[:, F_BUS] ## list of "from" buses t = branch[:, T_BUS] ## list of "to" buses i = range(nl) ## double set of row indices # Connection matrix Cf = sparse((ones(nl), (i, f)), (nl, nb)) Ct = sparse((ones(nl), (i, t)), (nl, nb)) Cg = sparse((ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) # Connection matrix Cg = sparse((ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) Branch_R = branch[:, BR_R] Branch_X = branch[:, BR_X] Slmax = branch[:, RATE_A] / baseMVA gen[:, PMAX] = gen[:, PMAX] / baseMVA gen[:, PMIN] = gen[:, PMIN] / baseMVA gen[:, QMAX] = gen[:, QMAX] / baseMVA gen[:, QMIN] = gen[:, QMIN] / baseMVA gencost[:, 4] = gencost[:, 4] * baseMVA * baseMVA gencost[:, 5] = gencost[:, 5] * baseMVA bus[:, PD] = bus[:, PD] / baseMVA bus[:, QD] = bus[:, QD] / baseMVA area = ancestor_children_generation(f, t, nb, Branch_R, Branch_X, Slmax, gen, bus, gencost, baseMVA) # Formulate the centralized optimization problem according to the information provided by area # Generate the initial value # 1) For the x-update Pi_x0 = [0] * nb Qi_x0 = [0] * nb Ii_x0 = [1] * nb Vi_x0 = [0] * nb pi_x0 = [0] * nb qi_x0 = [0] * nb Pg0 = [0] * nb Qg0 = [0] * nb # 2) For the y-update Pi_y0 = [0] * nb Qi_y0 = [0] * nb Ii_y0 = [0] * nb Vi_y0 = [1] * nb pi_y0 = [0] * nb qi_y0 = [0] * nb Pij_y0 = [0] * nl Qij_y0 = [0] * nl Iij_y0 = [0] * nl Vij_y0 = [0] * nl # 3) The multiplier part mu_Pi = [0] * nb mu_Qi = [0] * nb mu_Ii = [0] * nb mu_Vi = [0] * nb mu_pi = [0] * nb mu_qi = [0] * nb mu_Pij = [0] * nl mu_Qij = [0] * nl mu_Iij = [0] * nl mu_Vij = [0] * nl f = f.tolist() t = t.tolist() for i in range(nl): f[i] = int(f[i]) t[i] = int(t[i]) for i in range(nl): Pij_y0[i] = Pi_x0[t[i]] Qij_y0[i] = Qi_x0[t[i]] Iij_y0[i] = Ii_x0[t[i]] Vij_y0[i] = Vi_x0[f[i]] Gap = 1000 Gap_index = [] k = 0 kmax = 10000 ru = 1000 half_ru = ru / 2 # The iteration while k <= kmax and Gap > 0.001: # Y variables # Part 1), self observation modelY = Model("Yupdate") Pi_y = {} Qi_y = {} Ii_y = {} Vi_y = {} pi_y = {} qi_y = {} # Part 2), to the ancestor Pij_y = {} Qij_y = {} Iij_y = {} # Part 3), to the children. The definition is in accordance with the sequence of lines Vij_y = {} # For the given branch for i in range(nb): # The iteration from each bus Pi_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="Pi_y{0}".format(i)) Qi_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="Qi_y{0}".format(i)) Ii_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="Ii_y{0}".format(i)) Vi_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="Vi_y{0}".format(i)) pi_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="pi_y{0}".format(i)) qi_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="qi_y{0}".format(i)) for i in range(nl): # The information stored in the observatory Pij_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="Pij_y{0}".format(i)) Qij_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="Qij_y{0}".format(i)) Iij_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="Iij_y{0}".format(i)) Vij_y[i] = modelY.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="Vij_y{0}".format(i)) for i in range(nb): # Add constrain for the observation of each bus # 1)Constrain for KCL equations # 2)Constrain for KVL equations if area[i]["TYPE"] == "ROOT": # Only KCL equation is required expr = 0 for j in range(area[i]["nCi"]): expr += Pij_y[area[i]["Cbranch"][j]] - Iij_y[ area[i]["Cbranch"][j]] * area[i]["BR_R_C"][j] modelY.addConstr(pi_y[i] + expr == 0) expr = 0 for j in range(area[i]["nCi"]): expr += Qij_y[area[i]["Cbranch"][j]] - Iij_y[ area[i]["Cbranch"][j]] * area[i]["BR_X_C"][j] modelY.addConstr(qi_y[i] + expr == 0) elif area[i]["TYPE"] == "LEAF": # Only KCL equation is required modelY.addConstr(pi_y[i] - Pi_y[i] == 0) modelY.addConstr(qi_y[i] - Qi_y[i] == 0) modelY.addConstr( Vij_y[area[i]["Abranch"]] - Vi_y[i] + 2 * area[i]["BR_R"] * Pi_y[i] + 2 * area[i]["BR_X"] * Qi_y[i] - Ii_y[i] * (area[i]["BR_R"]**2 + area[i]["BR_X"]**2) == 0) else: expr = 0 for j in range(area[i]["nCi"]): expr += Pij_y[area[i]["Cbranch"][j]] - Iij_y[ area[i]["Cbranch"][j]] * area[i]["BR_R_C"][j] modelY.addConstr(pi_y[i] - Pi_y[i] + expr == 0) expr = 0 for j in range(area[i]["nCi"]): expr += Qij_y[area[i]["Cbranch"][j]] - Iij_y[ area[i]["Cbranch"][j]] * area[i]["BR_X_C"][j] modelY.addConstr(qi_y[i] - Qi_y[i] + expr == 0) modelY.addConstr( Vij_y[area[i]["Abranch"]] - Vi_y[i] + 2 * area[i]["BR_R"] * Pi_y[i] + 2 * area[i]["BR_X"] * Qi_y[i] - Ii_y[i] * (area[i]["BR_R"]**2 + area[i]["BR_X"]**2) == 0) objY = 0 for i in range(nb): objY += mu_Pi[i] * Pi_y[i] + half_ru * (Pi_y[i] - Pi_x0[i]) * (Pi_y[i] - Pi_x0[i]) + \ mu_Qi[i] * Qi_y[i] + half_ru * (Qi_y[i] - Qi_x0[i]) * (Qi_y[i] - Qi_x0[i]) + \ mu_Ii[i] * Ii_y[i] + half_ru * (Ii_y[i] - Ii_x0[i]) * (Ii_y[i] - Ii_x0[i]) + \ mu_Vi[i] * Vi_y[i] + half_ru * (Vi_y[i] - Vi_x0[i]) * (Vi_y[i] - Vi_x0[i]) + \ mu_pi[i] * pi_y[i] + half_ru * (pi_y[i] - pi_x0[i]) * (pi_y[i] - pi_x0[i]) + \ mu_qi[i] * qi_y[i] + half_ru * (qi_y[i] - qi_x0[i]) * (qi_y[i] - qi_x0[i]) for i in range(nl): objY += mu_Pij[i] * Pij_y[i] + half_ru * (Pij_y[i] - Pi_x0[t[i]]) * (Pij_y[i] - Pi_x0[t[i]]) + \ mu_Qij[i] * Qij_y[i] + half_ru * (Qij_y[i] - Qi_x0[t[i]]) * (Qij_y[i] - Qi_x0[t[i]]) + \ mu_Iij[i] * Iij_y[i] + half_ru * (Iij_y[i] - Ii_x0[t[i]]) * (Iij_y[i] - Ii_x0[t[i]]) + \ mu_Vij[i] * Vij_y[i] + half_ru * (Vij_y[i] - Vi_x0[f[i]]) * (Vij_y[i] - Vi_x0[f[i]]) modelY.setObjective(objY) modelY.Params.OutputFlag = 0 modelY.Params.LogToConsole = 0 modelY.Params.DisplayInterval = 1 modelY.Params.LogFile = "" modelY.optimize() for i in range(nb): Pi_y0[i] = modelY.getVarByName("Pi_y{0}".format(i)).X Qi_y0[i] = modelY.getVarByName("Qi_y{0}".format(i)).X Ii_y0[i] = modelY.getVarByName("Ii_y{0}".format(i)).X Vi_y0[i] = modelY.getVarByName("Vi_y{0}".format(i)).X pi_y0[i] = modelY.getVarByName("pi_y{0}".format(i)).X qi_y0[i] = modelY.getVarByName("qi_y{0}".format(i)).X for i in range(nl): Pij_y0[i] = modelY.getVarByName("Pij_y{0}".format(i)).X Qij_y0[i] = modelY.getVarByName("Qij_y{0}".format(i)).X Iij_y0[i] = modelY.getVarByName("Iij_y{0}".format(i)).X Vij_y0[i] = modelY.getVarByName("Vij_y{0}".format(i)).X del modelY # The sub problems modelX = Model("Xupdate") # Define the decision variables, compact set # X variables Pi_x = {} Qi_x = {} Ii_x = {} Vi_x = {} pi_x = {} qi_x = {} Pg = {} Qg = {} objX = 0 for i in range(nb): # The iteration from each bus Pi_x[i] = modelX.addVar(lb=-area[i]["SMAX"], ub=area[i]["SMAX"], vtype=GRB.CONTINUOUS, name="Pi_x{0}".format(i)) Qi_x[i] = modelX.addVar(lb=-area[i]["SMAX"], ub=area[i]["SMAX"], vtype=GRB.CONTINUOUS, name="Qi_x{0}".format(i)) Ii_x[i] = modelX.addVar(lb=-area[i]["SMAX"], ub=area[i]["SMAX"], vtype=GRB.CONTINUOUS, name="Ii_x{0}".format(i)) Vi_x[i] = modelX.addVar(lb=area[i]["VMIN"], ub=area[i]["VMAX"], vtype=GRB.CONTINUOUS, name="Vi_x{0}".format(i)) pi_x[i] = modelX.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="pi_x{0}".format(i)) qi_x[i] = modelX.addVar(lb=-inf, ub=inf, vtype=GRB.CONTINUOUS, name="qi_x{0}".format(i)) Pg[i] = modelX.addVar(lb=area[i]["PGMIN"], ub=area[i]["PGMAX"], vtype=GRB.CONTINUOUS, name="Pgi{0}".format(i)) Qg[i] = modelX.addVar(lb=area[i]["QGMIN"], ub=area[i]["QGMAX"], vtype=GRB.CONTINUOUS, name="Qgi{0}".format(i)) # For each branch, the following observation variables should be introduced # According to the sequence of line for i in range(nb): # Add constrain for each bus modelX.addConstr(Pg[i] - pi_x[i] == area[i]["PD"]) modelX.addConstr(Qg[i] - qi_x[i] == area[i]["QD"]) modelX.addConstr( Pi_x[i] * Pi_x[i] + Qi_x[i] * Qi_x[i] <= Ii_x[i] * Vi_x[i]) for i in range(nb): # Update the objective function objX += area[i]["a"] * Pg[i] * Pg[i] + area[i]["b"] * Pg[i] + area[i]["c"] - \ mu_Pi[i] * Pi_x[i] + half_ru * (Pi_x[i] - Pi_y0[i]) * (Pi_x[i] - Pi_y0[i]) - \ mu_Qi[i] * Qi_x[i] + half_ru * (Qi_x[i] - Qi_y0[i]) * (Qi_x[i] - Qi_y0[i]) - \ mu_Ii[i] * Ii_x[i] + half_ru * (Ii_x[i] - Ii_y0[i]) * (Ii_x[i] - Ii_y0[i]) - \ mu_Vi[i] * Vi_x[i] + half_ru * (Vi_x[i] - Vi_y0[i]) * (Vi_x[i] - Vi_y0[i]) - \ mu_pi[i] * pi_x[i] + half_ru * (pi_x[i] - pi_y0[i]) * (pi_x[i] - pi_y0[i]) - \ mu_qi[i] * qi_x[i] + half_ru * (qi_x[i] - qi_y0[i]) * (qi_x[i] - qi_y0[i]) for i in range(nl): objX += -mu_Pij[i] * Pi_x[t[i]] + half_ru * (Pi_x[t[i]] - Pij_y0[i]) * (Pi_x[t[i]] - Pij_y0[i]) \ - mu_Qij[i] * Qi_x[t[i]] + half_ru * (Qi_x[t[i]] - Qij_y0[i]) * (Qi_x[t[i]] - Qij_y0[i]) \ - mu_Iij[i] * Ii_x[t[i]] + half_ru * (Ii_x[t[i]] - Iij_y0[i]) * (Ii_x[t[i]] - Iij_y0[i]) \ - mu_Vij[i] * Vi_x[f[i]] + half_ru * (Vi_x[f[i]] - Vij_y0[i]) * (Vi_x[f[i]] - Vij_y0[i]) modelX.setObjective(objX) modelX.Params.OutputFlag = 0 modelX.Params.LogToConsole = 0 modelX.Params.DisplayInterval = 1 modelX.Params.LogFile = "" modelX.optimize() for i in range(nb): Pi_x0[i] = modelX.getVarByName("Pi_x{0}".format(i)).X Qi_x0[i] = modelX.getVarByName("Qi_x{0}".format(i)).X Ii_x0[i] = modelX.getVarByName("Ii_x{0}".format(i)).X Vi_x0[i] = modelX.getVarByName("Vi_x{0}".format(i)).X pi_x0[i] = modelX.getVarByName("pi_x{0}".format(i)).X qi_x0[i] = modelX.getVarByName("qi_x{0}".format(i)).X Pg0[i] = modelX.getVarByName("Pgi{0}".format(i)).X Qg0[i] = modelX.getVarByName("Qgi{0}".format(i)).X del modelX # Update mutiplier for i in range(nb): mu_Pi[i] += ru * (Pi_y0[i] - Pi_x0[i]) mu_Qi[i] += ru * (Qi_y0[i] - Qi_x0[i]) mu_Ii[i] += ru * (Ii_y0[i] - Ii_x0[i]) mu_Vi[i] += ru * (Vi_y0[i] - Vi_x0[i]) mu_pi[i] += ru * (pi_y0[i] - pi_x0[i]) mu_qi[i] += ru * (qi_y0[i] - qi_x0[i]) for i in range(nl): mu_Pij[i] += ru * (Pij_y0[i] - Pi_x0[t[i]]) mu_Qij[i] += ru * (Qij_y0[i] - Qi_x0[t[i]]) mu_Iij[i] += ru * (Iij_y0[i] - Ii_x0[t[i]]) mu_Vij[i] += ru * (Vij_y0[i] - Vi_x0[f[i]]) # Update residual residual = 0 for i in range(nb): residual += abs(Pi_y0[i] - Pi_x0[i]) residual += abs(Qi_y0[i] - Qi_x0[i]) residual += abs(Ii_y0[i] - Ii_x0[i]) residual += abs(Vi_y0[i] - Vi_x0[i]) residual += abs(pi_y0[i] - pi_x0[i]) residual += abs(qi_y0[i] - qi_x0[i]) for i in range(nl): residual += abs(Pij_y0[i] - Pi_x0[t[i]]) residual += abs(Qij_y0[i] - Qi_x0[t[i]]) residual += abs(Iij_y0[i] - Ii_x0[t[i]]) residual += abs(Vij_y0[i] - Vi_x0[f[i]]) obj = 0 for i in range(nb): obj += area[i]["a"] * Pg0[i] * Pg0[i] + area[i]["b"] * Pg0[ i] + area[i]["c"] Gap = residual k = k + 1 print(k) print(Gap) print(obj) obj = 0 for i in range(nb): obj += area[i]["a"] * Pg0[i] * Pg0[i] + area[i]["b"] * Pg0[i] + area[ i]["c"] primal_residual = [] for i in range(nb): primal_residual.append(Pi_x0[i] * Pi_x0[i] + Qi_x0[i] * Qi_x0[i] - Ii_x0[i] * Vi_x0[i]) return obj, primal_residual
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 run_sim(ppc, elements, dynopt=None, events=None, recorder=None): """ Run a time-domain simulation Inputs: ppc PYPOWER load flow case elements Dictionary of dynamic model objects (machines, controllers, etc) with Object ID as key events Events object recorder Recorder object (empty) Outputs: recorder Recorder object (with data) """ ######### # SETUP # ######### # Get version information ver = pydyn_ver() print('PYPOWER-Dynamics ' + ver['Version'] + ', ' + ver['Date']) # Program options if dynopt: h = dynopt['h'] t_sim = dynopt['t_sim'] max_err = dynopt['max_err'] max_iter = dynopt['max_iter'] verbose = dynopt['verbose'] else: # Default program options h = 0.01 # step length (s) t_sim = 5 # simulation time (s) max_err = 0.0001 # Maximum error in network iteration (voltage mismatches) max_iter = 25 # Maximum number of network iterations verbose = False # Make lists of current injection sources (generators, external grids, etc) and controllers sources = [] controllers = [] for element in elements.values(): if element.__module__ in [ 'pydyn.sym_order6a', 'pydyn.sym_order6b', 'pydyn.sym_order4', 'pydyn.ext_grid', 'pydyn.vsc_average', 'pydyn.asym_1cage', 'pydyn.asym_2cage' ]: sources.append(element) if element.__module__ == 'pydyn.controller': controllers.append(element) # Set up interfaces interfaces = init_interfaces(elements) ################## # INITIALISATION # ################## print('Initialising models...') # Run power flow and update bus voltages and angles in PYPOWER case object results, success = runpf(ppc) ppc["bus"][:, VM] = results["bus"][:, VM] ppc["bus"][:, VA] = results["bus"][:, VA] # Build Ybus matrix ppc_int = ext2int(ppc) baseMVA, bus, branch = ppc_int["baseMVA"], ppc_int["bus"], ppc_int[ "branch"] Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) # Build modified Ybus matrix Ybus = mod_Ybus(Ybus, elements, bus, ppc_int['gen'], baseMVA) # Calculate initial voltage phasors v0 = bus[:, VM] * (np.cos(np.radians(bus[:, VA])) + 1j * np.sin(np.radians(bus[:, VA]))) # Initialise sources from load flow for source in sources: if source.__module__ in ['pydyn.asym_1cage', 'pydyn.asym_2cage']: # Asynchronous machine source_bus = int(ppc_int['bus'][source.bus_no, 0]) v_source = v0[source_bus] source.initialise(v_source, 0) else: # Generator or VSC source_bus = int(ppc_int['gen'][source.gen_no, 0]) S_source = np.complex(results["gen"][source.gen_no, 1] / baseMVA, results["gen"][source.gen_no, 2] / baseMVA) v_source = v0[source_bus] source.initialise(v_source, S_source) # Interface controllers and machines (for initialisation) for intf in interfaces: int_type = intf[0] var_name = intf[1] if int_type == 'OUTPUT': # If an output, interface in the reverse direction for initialisation intf[2].signals[var_name] = intf[3].signals[var_name] else: # Inputs are interfaced in normal direction during initialisation intf[3].signals[var_name] = intf[2].signals[var_name] # Initialise controllers for controller in controllers: controller.initialise() ############# # MAIN LOOP # ############# if events == None: print('Warning: no events!') # Factorise Ybus matrix Ybus_inv = splu(Ybus) y1 = [] v_prev = v0 print('Simulating...') for t in range(int(t_sim / h) + 1): if np.mod(t, 1 / h) == 0: print('t=' + str(t * h) + 's') # Interface controllers and machines for intf in interfaces: var_name = intf[1] intf[3].signals[var_name] = intf[2].signals[var_name] # Solve differential equations for j in range(4): # Solve step of differential equations for element in elements.values(): element.solve_step(h, j) # Interface with network equations v_prev = solve_network(sources, v_prev, Ybus_inv, ppc_int, len(bus), max_err, max_iter) if recorder != None: # Record signals or states recorder.record_variables(t * h, elements) if events != None: # Check event stack ppc, refactorise = events.handle_events(np.round(t * h, 5), elements, ppc, baseMVA) if refactorise == True: # Rebuild Ybus from new ppc_int prev_ybus = Ybus.todense().copy() ppc_int = ext2int(ppc) baseMVA, bus, branch = ppc_int["baseMVA"], ppc_int[ "bus"], ppc_int["branch"] Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) # Rebuild modified Ybus Ybus = mod_Ybus(Ybus, elements, bus, ppc_int['gen'], baseMVA) ybus_delta = np.array(Ybus - prev_ybus) # Refactorise Ybus Ybus_inv = splu(Ybus) # Solve network equations v_prev = solve_network(sources, v_prev, Ybus_inv, ppc_int, len(bus), max_err, max_iter) return recorder
fnameWhole = "Net1_UKGDS_60.m" convert_mcase(fname) from Net1_UKGDS_60_subgrid import Net1_UKGDS_60_ as net from Net1_UKGDS_60 import Net1_UKGDS_60_ as netWhole t_f = 96 # time variation t_ges = 1440 # all time in min delta_t = 15 # time intervals in min time = np.arange(delta_t, t_ges + delta_t, delta_t) casedataWhole = netWhole() convert_to_python_indices(casedataWhole) ppc = casedataWhole ppopt = ppoption(PF_ALG=2) ppc = ext2int(ppc) baseMVA, bus, gen, branch = ppc["baseMVA"], ppc["bus"], ppc["gen"], ppc[ "branch"] baseMVA = 1000 Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) # nodal admittance matrix Ybus condition = np.hstack((1, np.arange(16, 27, 1))) Yws = Ybus[np.ix_(condition, condition)] #Yws = Y t_f = 96 slack_idx = 0 p0 = 1e-3 iterstop = 50 accuracy = 1e-9 roundY = None
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 run(mpc): """ Gurobi based optimal power flow modelling and solution :param mpc: The input case of optimal power flow :return: obtained solution """ # Data format from pypower.idx_brch import F_BUS, T_BUS, BR_R, BR_X, TAP, SHIFT, BR_STATUS, RATE_A from pypower.idx_cost import MODEL, NCOST, PW_LINEAR, COST, POLYNOMIAL from pypower.idx_bus import BUS_TYPE, REF, VA, VM, PD, GS, VMAX, VMIN, BUS_I, QD from pypower.idx_gen import GEN_BUS, VG, PG, QG, PMAX, PMIN, QMAX, QMIN from pypower.ext2int import ext2int mpc = ext2int(mpc) baseMVA, bus, gen, branch, gencost = mpc["baseMVA"], mpc["bus"], mpc[ "gen"], mpc["branch"], mpc["gencost"] nb = shape(mpc['bus'])[0] # number of buses nl = shape(mpc['branch'])[0] # number of branches ng = shape(mpc['gen'])[0] # number of dispatchable injections for i in range(nl): # branch indexing exchange if branch[i, F_BUS] > branch[i, T_BUS]: temp = branch[i, F_BUS] branch[i, F_BUS] = branch[i, T_BUS] branch[i, T_BUS] = temp f = branch[:, F_BUS] ## list of "from" buses t = branch[:, T_BUS] ## list of "to" buses area = ancestor_children_generation(f, t, range(nb)) # Connection matrix Cg = sparse((ones(ng), (gen[:, GEN_BUS], range(ng))), (nb, ng)) Branch_R = branch[:, BR_R] Branch_X = branch[:, BR_X] # Obtain the boundary information Slmax = branch[:, RATE_A] / baseMVA Pij_l = -Slmax Qij_l = -Slmax Iij_l = zeros(nl) Vm_l = turn_to_power(bus[:, VMIN], 2) Pg_l = gen[:, PMIN] / baseMVA Qg_l = gen[:, QMIN] / baseMVA Pi_l = -bus[:, PD] / baseMVA + Cg * Pg_l Qi_l = -bus[:, QD] / baseMVA + Cg * Qg_l Pij_u = Slmax Qij_u = Slmax Iij_u = Slmax Vm_u = turn_to_power(bus[:, VMAX], 2) Pg_u = 2 * gen[:, PMAX] / baseMVA Qg_u = 2 * gen[:, QMAX] / baseMVA Pi_u = -bus[:, PD] / baseMVA + Cg * Pg_u Qi_u = -bus[:, QD] / baseMVA + Cg * Qg_u # model = Model("OPF") # Define the decision variables, compact set Pij = {} Qij = {} Iij = {} Vi = {} Pg = {} Qg = {} Pi = {} Qi = {} for i in range(nl): Pij[i] = model.addVar(lb=Pij_l[i], ub=Pij_u[i], vtype=GRB.CONTINUOUS, name="Pij{0}".format(i)) Qij[i] = model.addVar(lb=Qij_l[i], ub=Qij_u[i], vtype=GRB.CONTINUOUS, name="Qij{0}".format(i)) Iij[i] = model.addVar(lb=Iij_l[i], ub=Iij_u[i], vtype=GRB.CONTINUOUS, name="Iij{0}".format(i)) for i in range(nb): Vi[i] = model.addVar(lb=Vm_l[i], ub=Vm_u[i], vtype=GRB.CONTINUOUS, name="V{0}".format(i)) for i in range(ng): Pg[i] = model.addVar(lb=Pg_l[i], ub=Pg_u[i], vtype=GRB.CONTINUOUS, name="Pg{0}".format(i)) Qg[i] = model.addVar(lb=Qg_l[i], ub=Qg_u[i], vtype=GRB.CONTINUOUS, name="Qg{0}".format(i)) for i in range(nb): Pi[i] = model.addVar(lb=Pi_l[i], ub=Pi_u[i], vtype=GRB.CONTINUOUS, name="Pi{0}".format(i)) Qi[i] = model.addVar(lb=Qi_l[i], ub=Qi_u[i], vtype=GRB.CONTINUOUS, name="Qi{0}".format(i)) # For each area, before decomposition # Add system level constraints for i in range(nb): # If the bus is the root bus, only the children information is required. if len(area[i]["Ai"]) == 0: print(i) expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Pij[area[i]["Cbranch"][0][j]] model.addConstr(lhs=expr - Pi[i], sense=GRB.EQUAL, rhs=0) expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Qij[area[i]["Cbranch"][0][j]] model.addConstr(lhs=expr - Qi[i], sense=GRB.EQUAL, rhs=0) elif len(area[i]["Cbranch"]) == 0: # This bus is the lead node model.addConstr(lhs=Pij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_R[area[i]["Abranch"][0][0]] + Pi[i], sense=GRB.EQUAL, rhs=0) model.addConstr(lhs=Qij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_X[area[i]["Abranch"][0][0]] + Qi[i], sense=GRB.EQUAL, rhs=0) model.addConstr(lhs=Vi[int(area[i]["Ai"][0])] - Vi[i] - 2 * Branch_R[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] - 2 * Branch_X[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] + Iij[area[i]["Abranch"][0][0]] * (Branch_R[area[i]["Abranch"][0][0]]**2 + Branch_X[area[i]["Abranch"][0][0]]**2), sense=GRB.EQUAL, rhs=0) model.addConstr( Pij[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] + Qij[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] <= Vi[int(area[i]["Ai"][0])] * Iij[area[i]["Abranch"][0][0]]) else: expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Pij[area[i]["Cbranch"][0][j]] model.addConstr(lhs=Pij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_R[area[i]["Abranch"][0][0]] + Pi[i] - expr, sense=GRB.EQUAL, rhs=0) expr = 0 for j in range(len(area[i]["Cbranch"][0])): expr += Qij[area[i]["Cbranch"][0][j]] model.addConstr(Qij[area[i]["Abranch"][0][0]] - Iij[area[i]["Abranch"][0][0]] * Branch_X[area[i]["Abranch"][0][0]] + Qi[i] - expr, sense=GRB.EQUAL, rhs=0) model.addConstr(lhs=Vi[int(area[i]["Ai"][0])] - Vi[i] - 2 * Branch_R[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] - 2 * Branch_X[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] + Iij[area[i]["Abranch"][0][0]] * (Branch_R[area[i]["Abranch"][0][0]]**2 + Branch_X[area[i]["Abranch"][0][0]]**2), sense=GRB.EQUAL, rhs=0) model.addConstr( Pij[area[i]["Abranch"][0][0]] * Pij[area[i]["Abranch"][0][0]] + Qij[area[i]["Abranch"][0][0]] * Qij[area[i]["Abranch"][0][0]] <= Vi[int(area[i]["Ai"][0])] * Iij[area[i]["Abranch"][0][0]]) obj = 0 for i in range(ng): model.addConstr(lhs=Pg[i] - Pi[int(gen[i, GEN_BUS])], sense=GRB.EQUAL, rhs=bus[int(gen[i, GEN_BUS]), PD] / baseMVA) model.addConstr(lhs=Qg[i] - Qi[int(gen[i, GEN_BUS])], sense=GRB.EQUAL, rhs=bus[int(gen[i, GEN_BUS]), QD] / baseMVA) obj += gencost[i, 4] * Pg[i] * Pg[i] * baseMVA * baseMVA + gencost[ i, 5] * Pg[i] * baseMVA + gencost[i, 6] model.setObjective(obj) model.Params.OutputFlag = 0 model.Params.LogToConsole = 0 model.Params.DisplayInterval = 1 model.optimize() Pij = [] Qij = [] Iij = [] Vi = [] Pg = [] Qg = [] Pi = [] Qi = [] for i in range(nl): Pij.append(model.getVarByName("Pij{0}".format(i)).X) Qij.append(model.getVarByName("Qij{0}".format(i)).X) Iij.append(model.getVarByName("Iij{0}".format(i)).X) for i in range(nb): Vi.append(model.getVarByName("V{0}".format(i)).X) Pi.append(model.getVarByName("Pi{0}".format(i)).X) Qi.append(model.getVarByName("Qi{0}".format(i)).X) for i in range(ng): Pg.append(model.getVarByName("Pg{0}".format(i)).X) Qg.append(model.getVarByName("Qg{0}".format(i)).X) obj = obj.getValue() primal_residual = [] for i in range(nl): primal_residual.append(Pij[i] * Pij[i] + Qij[i] * Qij[i] - Iij[i] * Vi[int(f[i])]) return obj, primal_residual
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 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 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
if __name__ == "__main__": from unit_commitment.test_cases import case118 test_case = case118.case118() from pypower.case118 import case118 from pypower.idx_brch import F_BUS, T_BUS, BR_X, TAP, SHIFT, BR_STATUS, RATE_A from pypower.idx_cost import MODEL, NCOST, PW_LINEAR, COST, POLYNOMIAL from pypower.idx_bus import BUS_TYPE, REF, VA, VM, PD, GS, VMAX, VMIN, BUS_I from pypower.idx_gen import GEN_BUS, VG, PG, QG, PMAX, PMIN, QMAX, QMIN from numpy import flatnonzero as find casedata = case118() mpc = loadcase.loadcase(casedata) mpc = ext2int.ext2int(mpc) baseMVA, bus, gen, branch, gencost = mpc["baseMVA"], mpc["bus"], mpc[ "gen"], mpc["branch"], mpc["gencost"] # nb = shape(mpc['bus'])[0] ## number of buses nl = shape(mpc['branch'])[0] ## number of branches ng = shape(mpc['gen'])[0] ## number of dispatchable injections ## Formualte the stat = branch[:, BR_STATUS] ## ones at in-service branches b = stat / branch[:, BR_X] ## series susceptance tap = ones(nl) ## default tap ratio = 1 i = find(branch[:, TAP]) ## indices of non-zero tap ratios tap[i] = branch[i, TAP] ## assign non-zero tap ratios ## build connection matrix Cft = Cf - Ct for line and from - to buses
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 run_sim(ppc, elements, dynopt = None, events = None, recorder = None): """ Run a time-domain simulation Inputs: ppc PYPOWER load flow case elements Dictionary of dynamic model objects (machines, controllers, etc) with Object ID as key events Events object recorder Recorder object (empty) Outputs: recorder Recorder object (with data) """ ######### # SETUP # ######### # Get version information ver = pydyn_ver() print('PYPOWER-Dynamics ' + ver['Version'] + ', ' + ver['Date']) # Program options if dynopt: h = dynopt['h'] t_sim = dynopt['t_sim'] max_err = dynopt['max_err'] max_iter = dynopt['max_iter'] verbose = dynopt['verbose'] else: # Default program options h = 0.01 # step length (s) t_sim = 5 # simulation time (s) max_err = 0.0001 # Maximum error in network iteration (voltage mismatches) max_iter = 25 # Maximum number of network iterations verbose = False # Make lists of current injection sources (generators, external grids, etc) and controllers sources = [] controllers = [] for element in elements.values(): if element.__module__ in ['pydyn.sym_order6a', 'pydyn.sym_order6b', 'pydyn.sym_order4', 'pydyn.ext_grid', 'pydyn.vsc_average', 'pydyn.asym_1cage', 'pydyn.asym_2cage']: sources.append(element) if element.__module__ == 'pydyn.controller': controllers.append(element) # Set up interfaces interfaces = init_interfaces(elements) ################## # INITIALISATION # ################## print('Initialising models...') # Run power flow and update bus voltages and angles in PYPOWER case object results, success = runpf(ppc) ppc["bus"][:, VM] = results["bus"][:, VM] ppc["bus"][:, VA] = results["bus"][:, VA] # Build Ybus matrix ppc_int = ext2int(ppc) baseMVA, bus, branch = ppc_int["baseMVA"], ppc_int["bus"], ppc_int["branch"] Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) # Build modified Ybus matrix Ybus = mod_Ybus(Ybus, elements, bus, ppc_int['gen'], baseMVA) # Calculate initial voltage phasors v0 = bus[:, VM] * (np.cos(np.radians(bus[:, VA])) + 1j * np.sin(np.radians(bus[:, VA]))) # Initialise sources from load flow for source in sources: if source.__module__ in ['pydyn.asym_1cage', 'pydyn.asym_2cage']: # Asynchronous machine source_bus = ppc_int['bus'][source.bus_no,0] v_source = v0[source_bus] source.initialise(v_source,0) else: # Generator or VSC source_bus = ppc_int['gen'][source.gen_no,0] S_source = np.complex(results["gen"][source.gen_no, 1] / baseMVA, results["gen"][source.gen_no, 2] / baseMVA) v_source = v0[source_bus] source.initialise(v_source,S_source) # Interface controllers and machines (for initialisation) for intf in interfaces: int_type = intf[0] var_name = intf[1] if int_type == 'OUTPUT': # If an output, interface in the reverse direction for initialisation intf[2].signals[var_name] = intf[3].signals[var_name] else: # Inputs are interfaced in normal direction during initialisation intf[3].signals[var_name] = intf[2].signals[var_name] # Initialise controllers for controller in controllers: controller.initialise() ############# # MAIN LOOP # ############# if events == None: print('Warning: no events!') # Factorise Ybus matrix Ybus_inv = splu(Ybus) y1 = [] v_prev = v0 print('Simulating...') for t in range(int(t_sim / h) + 1): if np.mod(t,1/h) == 0: print('t=' + str(t*h) + 's') # Interface controllers and machines for intf in interfaces: var_name = intf[1] intf[3].signals[var_name] = intf[2].signals[var_name] # Solve differential equations for j in range(4): # Solve step of differential equations for element in elements.values(): element.solve_step(h,j) # Interface with network equations v_prev = solve_network(sources, v_prev, Ybus_inv, ppc_int, len(bus), max_err, max_iter) if recorder != None: # Record signals or states recorder.record_variables(t*h, elements) if events != None: # Check event stack ppc, refactorise = events.handle_events(np.round(t*h,5), elements, ppc, baseMVA) if refactorise == True: # Rebuild Ybus from new ppc_int ppc_int = ext2int(ppc) baseMVA, bus, branch = ppc_int["baseMVA"], ppc_int["bus"], ppc_int["branch"] Ybus, Yf, Yt = makeYbus(baseMVA, bus, branch) # Rebuild modified Ybus Ybus = mod_Ybus(Ybus, elements, bus, ppc_int['gen'], baseMVA) # Refactorise Ybus Ybus_inv = splu(Ybus) # Solve network equations v_prev = solve_network(sources, v_prev, Ybus_inv, ppc_int, len(bus), max_err, max_iter) return recorder