def poly2pwl(polycost, Pmin, Pmax, npts): """Converts polynomial cost variable to piecewise linear. Converts the polynomial cost variable C{polycost} into a piece-wise linear cost by evaluating at zero and then at C{npts} evenly spaced points between C{Pmin} and C{Pmax}. If C{Pmin <= 0} (such as for reactive power, where C{P} really means C{Q}) it just uses C{npts} evenly spaced points between C{Pmin} and C{Pmax}. """ pwlcost = polycost ## size of piece being changed m, n = polycost.shape ## change cost model pwlcost[:, MODEL] = PW_LINEAR * ones(m) ## zero out old data pwlcost[:, COST:COST + n] = zeros(pwlcost[:, COST:COST + n].shape) ## change number of data points pwlcost[:, NCOST] = npts * ones(m) for i in range(m): if Pmin[i] == 0: step = (Pmax[i] - Pmin[i]) / (npts - 1) xx = range(Pmin[i], step, Pmax[i]) elif Pmin[i] > 0: step = (Pmax[i] - Pmin[i]) / (npts - 2) xx = r_[0, range(Pmin[i], step, Pmax[i])] elif Pmin[i] < 0 & Pmax[i] > 0: ## for when P really means Q step = (Pmax[i] - Pmin[i]) / (npts - 1) xx = range(Pmin[i], step, Pmax[i]) yy = totcost(polycost[i, :], xx) pwlcost[i, COST:2:(COST + 2*(npts-1) )] = xx pwlcost[i, (COST+1):2:(COST + 2*(npts-1) + 1)] = yy return pwlcost
def t_totcost(quiet=False): """Tests for code in C{totcost}. @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln """ n_tests = 22 t_begin(n_tests, quiet) ## generator cost data # 1 startup shutdown n x1 y1 ... xn yn # 2 startup shutdown n c(n-1) ... c0 gencost = array([ [2, 0, 0, 3, 0.01, 0.1, 1, 0, 0, 0, 0, 0], [2, 0, 0, 5, 0.0006, 0.005, 0.04, 0.3, 2, 0, 0, 0], [1, 0, 0, 4, 0, 0, 10, 200, 20, 600, 30, 1200], [1, 0, 0, 4, -30, -2400, -20, -1800, -10, -1000, 0, 0] ]) t = 'totcost - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0])), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0])), [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0])), [1.24, 2, 0, 0], 8, t) t = 'totcost - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0])), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0])), [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0])), [1, 2.8096, 0, 0], 8, t) t = 'totcost - pwl (gen)' t_is(totcost(gencost, array([0, 0, -10, 0 ])), [1, 2, -200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 5, 0 ])), [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0])), [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0])), [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0])), [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0])), [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0])), [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0])), [1, 2, 1500, 0], 8, t) t = 'totcost - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, 10 ])), [1, 2, 0, 1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -5 ])), [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10])), [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15])), [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20])), [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25])), [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30])), [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35])), [1, 2, 0, -2700], 8, t) t_end()
def opf_costfcn(x, om, return_hessian=False): """Evaluates objective function, gradient and Hessian for OPF. Objective function evaluation routine for AC optimal power flow, suitable for use with L{pips}. Computes objective function value, gradient and Hessian. @param x: optimization vector @param om: OPF model object @return: C{F} - value of objective function. C{df} - (optional) gradient of objective function (column vector). C{d2f} - (optional) Hessian of objective function (sparse matrix). @see: L{opf_consfcn}, L{opf_hessfcn} @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Ray Zimmerman (PSERC Cornell) """ ##----- initialize ----- ## unpack data ppc = om.get_ppc() baseMVA, gen, gencost = ppc["baseMVA"], ppc["gen"], ppc["gencost"] cp = om.get_cost_params() N, Cw, H, dd, rh, kk, mm = \ cp["N"], cp["Cw"], cp["H"], cp["dd"], cp["rh"], cp["kk"], cp["mm"] vv, _, _, _ = om.get_idx() ## problem dimensions ng = gen.shape[0] ## number of dispatchable injections ny = om.getN('var', 'y') ## number of piece-wise linear costs nxyz = len(x) ## total number of control vars of all types ## grab Pg & Qg Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]] ## active generation in p.u. Qg = x[vv["i1"]["Qg"]:vv["iN"]["Qg"]] ## reactive generation in p.u. ##----- evaluate objective function ----- ## polynomial cost of P and Q # use totcost only on polynomial cost in the minimization problem # formulation, pwl cost is the sum of the y variables. ipol = find(gencost[:, MODEL] == POLYNOMIAL) ## poly MW and MVAr costs xx = r_[Pg, Qg] * baseMVA if any(ipol): f = sum(totcost(gencost[ipol, :], xx[ipol])) ## cost of poly P or Q else: f = 0 ## piecewise linear cost of P and Q if ny > 0: ccost = sparse( (ones(ny), (zeros(ny), arange(vv["i1"]["y"], vv["iN"]["y"]))), (1, nxyz)).toarray().flatten() f = f + dot(ccost, x) else: ccost = zeros(nxyz) ## generalized cost term if issparse(N) and N.nnz > 0: nw = N.shape[0] r = N * x - rh ## Nx - rhat iLT = find(r < -kk) ## below dead zone iEQ = find((r == 0) & (kk == 0)) ## dead zone doesn't exist iGT = find(r > kk) ## above dead zone iND = r_[iLT, iEQ, iGT] ## rows that are Not in the Dead region iL = find(dd == 1) ## rows using linear function iQ = find(dd == 2) ## rows using quadratic function LL = sparse((ones(len(iL)), (iL, iL)), (nw, nw)) QQ = sparse((ones(len(iQ)), (iQ, iQ)), (nw, nw)) kbar = sparse((r_[ones(len(iLT)), zeros(len(iEQ)), -ones(len(iGT))], (iND, iND)), (nw, nw)) * kk rr = r + kbar ## apply non-dead zone shift M = sparse((mm[iND], (iND, iND)), (nw, nw)) ## dead zone or scale diagrr = sparse((rr, (arange(nw), arange(nw))), (nw, nw)) ## linear rows multiplied by rr(i), quadratic rows by rr(i)^2 w = M * (LL + QQ * diagrr) * rr f = f + dot(w * H, w) / 2 + dot(Cw, w) ##----- evaluate cost gradient ----- ## index ranges iPg = list(range(vv["i1"]["Pg"], vv["iN"]["Pg"])) iQg = list(range(vv["i1"]["Qg"], vv["iN"]["Qg"])) ## polynomial cost of P and Q df_dPgQg = zeros(2 * ng) ## w.r.t p.u. Pg and Qg df_dPgQg[ipol] = baseMVA * polycost(gencost[ipol, :], xx[ipol], 1) df = zeros(nxyz) df[iPg] = df_dPgQg[:ng] df[iQg] = df_dPgQg[ng:ng + ng] ## piecewise linear cost of P and Q df = df + ccost # The linear cost row is additive wrt any nonlinear cost. ## generalized cost term if issparse(N) and N.nnz > 0: HwC = H * w + Cw AA = N.T * M * (LL + 2 * QQ * diagrr) df = df + AA * HwC ## numerical check if 0: ## 1 to check, 0 to skip check ddff = zeros(df.shape) step = 1e-7 tol = 1e-3 for k in range(len(x)): xx = x xx[k] = xx[k] + step ddff[k] = (opf_costfcn(xx, om) - f) / step if max(abs(ddff - df)) > tol: idx = find(abs(ddff - df) == max(abs(ddff - df))) print('Mismatch in gradient') print('idx df(num) df diff') print(('%4d%16g%16g%16g' % (list(range(len(df))), ddff.T, df.T, abs(ddff - df).T))) print('MAX') print(('%4d%16g%16g%16g' % (idx.T, ddff[idx].T, df[idx].T, abs(ddff[idx] - df[idx]).T))) if not return_hessian: return f, df ## ---- evaluate cost Hessian ----- pcost = gencost[list(range(ng)), :] if gencost.shape[0] > ng: qcost = gencost[ng + 1:2 * ng, :] else: qcost = array([]) ## polynomial generator costs d2f_dPg2 = zeros(ng) ## w.r.t. p.u. Pg d2f_dQg2 = zeros(ng) ## w.r.t. p.u. Qg ipolp = find(pcost[:, MODEL] == POLYNOMIAL) d2f_dPg2[ipolp] = \ baseMVA**2 * polycost(pcost[ipolp, :], Pg[ipolp]*baseMVA, 2) if any(qcost): ## Qg is not free ipolq = find(qcost[:, MODEL] == POLYNOMIAL) d2f_dQg2[ipolq] = \ baseMVA**2 * polycost(qcost[ipolq, :], Qg[ipolq] * baseMVA, 2) i = r_[iPg, iQg].T d2f = sparse((r_[d2f_dPg2, d2f_dQg2], (i, i)), (nxyz, nxyz)) ## generalized cost if N is not None and issparse(N): d2f = d2f + AA * H * AA.T + 2 * N.T * M * QQ * \ sparse((HwC, (list(range(nw)), list(range(nw)))), (nw, nw)) * N return f, df, d2f
def t_totcost(quiet=False): """Tests for code in C{totcost}. @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln """ n_tests = 22 t_begin(n_tests, quiet) ## generator cost data # 1 startup shutdown n x1 y1 ... xn yn # 2 startup shutdown n c(n-1) ... c0 gencost = array([[2, 0, 0, 3, 0.01, 0.1, 1, 0, 0, 0, 0, 0], [2, 0, 0, 5, 0.0006, 0.005, 0.04, 0.3, 2, 0, 0, 0], [1, 0, 0, 4, 0, 0, 10, 200, 20, 600, 30, 1200], [1, 0, 0, 4, -30, -2400, -20, -1800, -10, -1000, 0, 0]]) t = 'totcost - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0])), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0])), [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0])), [1.24, 2, 0, 0], 8, t) t = 'totcost - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0])), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0])), [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0])), [1, 2.8096, 0, 0], 8, t) t = 'totcost - pwl (gen)' t_is(totcost(gencost, array([0, 0, -10, 0])), [1, 2, -200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 5, 0])), [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0])), [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0])), [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0])), [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0])), [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0])), [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0])), [1, 2, 1500, 0], 8, t) t = 'totcost - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, 10])), [1, 2, 0, 1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -5])), [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10])), [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15])), [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20])), [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25])), [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30])), [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35])), [1, 2, 0, -2700], 8, t) t_end()
def uopf(*args): """Solves combined unit decommitment / optimal power flow. Solves a combined unit decommitment and optimal power flow for a single time period. Uses an algorithm similar to dynamic programming. It proceeds through a sequence of stages, where stage C{N} has C{N} generators shut down, starting with C{N=0}. In each stage, it forms a list of candidates (gens at their C{Pmin} limits) and computes the cost with each one of them shut down. It selects the least cost case as the starting point for the next stage, continuing until there are no more candidates to be shut down or no more improvement can be gained by shutting something down. If C{verbose} in ppopt (see L{ppoption} is C{true}, it prints progress info, if it is > 1 it prints the output of each individual opf. @see: L{opf}, L{runuopf} @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln """ ##----- initialization ----- t0 = time() ## start timer ## process input arguments ppc, ppopt = opf_args2(*args) ## options verbose = ppopt["VERBOSE"] if verbose: ## turn down verbosity one level for calls to opf ppopt = ppoption(ppopt, VERBOSE=verbose - 1) ##----- do combined unit commitment/optimal power flow ----- ## check for sum(Pmin) > total load, decommit as necessary on = find((ppc["gen"][:, GEN_STATUS] > 0) & ~isload(ppc["gen"])) ## gens in service onld = find((ppc["gen"][:, GEN_STATUS] > 0) & isload(ppc["gen"])) ## disp loads in serv load_capacity = sum(ppc["bus"][:, PD]) - sum( ppc["gen"][onld, PMIN]) ## total load capacity Pmin = ppc["gen"][on, PMIN] while sum(Pmin) > load_capacity: ## shut down most expensive unit avgPmincost = totcost(ppc["gencost"][on, :], Pmin) / Pmin _, i = fairmax(avgPmincost) ## pick one with max avg cost at Pmin i = on[i] ## convert to generator index if verbose: print( 'Shutting down generator %d so all Pmin limits can be satisfied.\n' % i) ## set generation to zero ppc["gen"][i, [PG, QG, GEN_STATUS]] = 0 ## update minimum gen capacity on = find((ppc["gen"][:, GEN_STATUS] > 0) & ~isload(ppc["gen"])) ## gens in service Pmin = ppc["gen"][on, PMIN] ## run initial opf results = opf(ppc, ppopt) ## best case so far results1 = deepcopy(results) ## best case for this stage (ie. with n gens shut down, n=0,1,2 ...) results0 = deepcopy(results1) ppc["bus"] = results0["bus"].copy( ) ## use these V as starting point for OPF while True: ## get candidates for shutdown candidates = find((results0["gen"][:, MU_PMIN] > 0) & (results0["gen"][:, PMIN] > 0)) if len(candidates) == 0: break ## do not check for further decommitment unless we ## see something better during this stage done = True for k in candidates: ## start with best for this stage ppc["gen"] = results0["gen"].copy() ## shut down gen k ppc["gen"][k, [PG, QG, GEN_STATUS]] = 0 ## run opf results = opf(ppc, ppopt) ## something better? if results['success'] and (results["f"] < results1["f"]): results1 = deepcopy(results) k1 = k done = False ## make sure we check for further decommitment if done: ## decommits at this stage did not help, so let's quit break else: ## shutting something else down helps, so let's keep going if verbose: print('Shutting down generator %d.\n' % k1) results0 = deepcopy(results1) ppc["bus"] = results0["bus"].copy( ) ## use these V as starting point for OPF ## compute elapsed time et = time() - t0 ## finish preparing output results0['et'] = et return results0
def t_modcost(quiet=False): """Tests for code in C{modcost}. @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln """ n_tests = 80 t_begin(n_tests, quiet) ## generator cost data # 1 startup shutdown n x1 y1 ... xn yn # 2 startup shutdown n c(n-1) ... c0 gencost0 = array([ [2, 0, 0, 3, 0.01, 0.1, 1, 0, 0, 0, 0, 0], [2, 0, 0, 5, 0.0006, 0.005, 0.04, 0.3, 2, 0, 0, 0], [1, 0, 0, 4, 0, 0, 10, 200, 20, 600, 30, 1200], [1, 0, 0, 4, -30, -2400, -20, -1800, -10, -1000, 0, 0] ]) gencost = modcost(gencost0, 5, 'SCALE_F') ##----- POLYSHIFT ----- t = 'modcost SCALE_F - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0])) / 5, [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0])) / 5, [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0])) / 5, [1.24, 2, 0, 0], 8, t) t = 'modcost SCALE_F - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0])) / 5, [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0])) / 5, [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0])) / 5, [1, 2.8096, 0, 0], 8, t) t = 'modcost SCALE_F - pwl (gen)' t_is(totcost(gencost, array([0, 0, 5, 0 ])) / 5, [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0])) / 5, [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0])) / 5, [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0])) / 5, [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0])) / 5, [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0])) / 5, [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0])) / 5, [1, 2, 1500, 0], 8, t) t = 'modcost SCALE_F - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, -5 ])) / 5, [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10])) / 5, [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15])) / 5, [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20])) / 5, [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25])) / 5, [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30])) / 5, [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35])) / 5, [1, 2, 0, -2700], 8, t) gencost = modcost(gencost0, 2, 'SCALE_X') t = 'modcost SCALE_X - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0]) * 2), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0]) * 2), [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0]) * 2), [1.24, 2, 0, 0], 8, t) t = 'modcost SCALE_X - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0]) * 2), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0]) * 2), [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0]) * 2), [1, 2.8096, 0, 0], 8, t) t = 'modcost SCALE_X - pwl (gen)' t_is(totcost(gencost, array([0, 0, 5, 0 ]) * 2), [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0]) * 2), [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0]) * 2), [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0]) * 2), [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0]) * 2), [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0]) * 2), [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0]) * 2), [1, 2, 1500, 0], 8, t) t = 'modcost SCALE_X - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, -5 ]) * 2), [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10]) * 2), [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15]) * 2), [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20]) * 2), [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25]) * 2), [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30]) * 2), [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35]) * 2), [1, 2, 0, -2700], 8, t) gencost = modcost(gencost0, 3, 'SHIFT_F') t = 'modcost SHIFT_F - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0])) - 3, [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0])) - 3, [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0])) - 3, [1.24, 2, 0, 0], 8, t) t = 'modcost SHIFT_F - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0])) - 3, [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0])) - 3, [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0])) - 3, [1, 2.8096, 0, 0], 8, t) t = 'modcost SHIFT_F - pwl (gen)' t_is(totcost(gencost, array([0, 0, 5, 0 ])) - 3, [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0])) - 3, [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0])) - 3, [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0])) - 3, [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0])) - 3, [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0])) - 3, [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0])) - 3, [1, 2, 1500, 0], 8, t) t = 'modcost SHIFT_F - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, -5 ])) - 3, [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10])) - 3, [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15])) - 3, [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20])) - 3, [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25])) - 3, [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30])) - 3, [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35])) - 3, [1, 2, 0, -2700], 8, t) gencost = modcost(gencost0, -4, 'SHIFT_X') t = 'modcost SHIFT_X - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0]) - 4), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0]) - 4), [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0]) - 4), [1.24, 2, 0, 0], 8, t) t = 'modcost SHIFT_X - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0]) - 4), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0]) - 4), [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0]) - 4), [1, 2.8096, 0, 0], 8, t) t = 'modcost SHIFT_X - pwl (gen)' t_is(totcost(gencost, array([0, 0, 5, 0 ]) - 4), [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0]) - 4), [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0]) - 4), [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0]) - 4), [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0]) - 4), [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0]) - 4), [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0]) - 4), [1, 2, 1500, 0], 8, t) t = 'modcost SHIFT_X - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, -5 ]) - 4), [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10]) - 4), [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15]) - 4), [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20]) - 4), [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25]) - 4), [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30]) - 4), [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35]) - 4), [1, 2, 0, -2700], 8, t) t_end()
def uopf(*args): """Solves combined unit decommitment / optimal power flow. Solves a combined unit decommitment and optimal power flow for a single time period. Uses an algorithm similar to dynamic programming. It proceeds through a sequence of stages, where stage C{N} has C{N} generators shut down, starting with C{N=0}. In each stage, it forms a list of candidates (gens at their C{Pmin} limits) and computes the cost with each one of them shut down. It selects the least cost case as the starting point for the next stage, continuing until there are no more candidates to be shut down or no more improvement can be gained by shutting something down. If C{verbose} in ppopt (see L{ppoption} is C{true}, it prints progress info, if it is > 1 it prints the output of each individual opf. @see: L{opf}, L{runuopf} @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln """ ##----- initialization ----- t0 = time() ## start timer ## process input arguments ppc, ppopt = opf_args2(*args) ## options verbose = ppopt["VERBOSE"] if verbose: ## turn down verbosity one level for calls to opf ppopt = ppoption(ppopt, VERBOSE=verbose - 1) ##----- do combined unit commitment/optimal power flow ----- ## check for sum(Pmin) > total load, decommit as necessary on = find( (ppc["gen"][:, GEN_STATUS] > 0) & ~isload(ppc["gen"]) ) ## gens in service onld = find( (ppc["gen"][:, GEN_STATUS] > 0) & isload(ppc["gen"]) ) ## disp loads in serv load_capacity = sum(ppc["bus"][:, PD]) - sum(ppc["gen"][onld, PMIN]) ## total load capacity Pmin = ppc["gen"][on, PMIN] while sum(Pmin) > load_capacity: ## shut down most expensive unit avgPmincost = totcost(ppc["gencost"][on, :], Pmin) / Pmin _, i = fairmax(avgPmincost) ## pick one with max avg cost at Pmin i = on[i] ## convert to generator index if verbose: print('Shutting down generator %d so all Pmin limits can be satisfied.\n' % i) ## set generation to zero ppc["gen"][i, [PG, QG, GEN_STATUS]] = 0 ## update minimum gen capacity on = find( (ppc["gen"][:, GEN_STATUS] > 0) & ~isload(ppc["gen"]) ) ## gens in service Pmin = ppc["gen"][on, PMIN] ## run initial opf results = opf(ppc, ppopt) ## best case so far results1 = deepcopy(results) ## best case for this stage (ie. with n gens shut down, n=0,1,2 ...) results0 = deepcopy(results1) ppc["bus"] = results0["bus"].copy() ## use these V as starting point for OPF while True: ## get candidates for shutdown candidates = find((results0["gen"][:, MU_PMIN] > 0) & (results0["gen"][:, PMIN] > 0)) if len(candidates) == 0: break ## do not check for further decommitment unless we ## see something better during this stage done = True for k in candidates: ## start with best for this stage ppc["gen"] = results0["gen"].copy() ## shut down gen k ppc["gen"][k, [PG, QG, GEN_STATUS]] = 0 ## run opf results = opf(ppc, ppopt) ## something better? if results['success'] and (results["f"] < results1["f"]): results1 = deepcopy(results) k1 = k done = False ## make sure we check for further decommitment if done: ## decommits at this stage did not help, so let's quit break else: ## shutting something else down helps, so let's keep going if verbose: print('Shutting down generator %d.\n' % k1) results0 = deepcopy(results1) ppc["bus"] = results0["bus"].copy() ## use these V as starting point for OPF ## compute elapsed time et = time() - t0 ## finish preparing output results0['et'] = et return results0
def t_modcost(quiet=False): """Tests for code in C{modcost}. @author: Ray Zimmerman (PSERC Cornell) """ n_tests = 80 t_begin(n_tests, quiet) ## generator cost data # 1 startup shutdown n x1 y1 ... xn yn # 2 startup shutdown n c(n-1) ... c0 gencost0 = array([[2, 0, 0, 3, 0.01, 0.1, 1, 0, 0, 0, 0, 0], [2, 0, 0, 5, 0.0006, 0.005, 0.04, 0.3, 2, 0, 0, 0], [1, 0, 0, 4, 0, 0, 10, 200, 20, 600, 30, 1200], [1, 0, 0, 4, -30, -2400, -20, -1800, -10, -1000, 0, 0]]) gencost = modcost(gencost0, 5, 'SCALE_F') ##----- POLYSHIFT ----- t = 'modcost SCALE_F - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0])) / 5, [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0])) / 5, [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0])) / 5, [1.24, 2, 0, 0], 8, t) t = 'modcost SCALE_F - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0])) / 5, [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0])) / 5, [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0])) / 5, [1, 2.8096, 0, 0], 8, t) t = 'modcost SCALE_F - pwl (gen)' t_is(totcost(gencost, array([0, 0, 5, 0])) / 5, [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0])) / 5, [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0])) / 5, [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0])) / 5, [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0])) / 5, [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0])) / 5, [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0])) / 5, [1, 2, 1500, 0], 8, t) t = 'modcost SCALE_F - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, -5])) / 5, [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10])) / 5, [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15])) / 5, [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20])) / 5, [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25])) / 5, [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30])) / 5, [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35])) / 5, [1, 2, 0, -2700], 8, t) gencost = modcost(gencost0, 2, 'SCALE_X') t = 'modcost SCALE_X - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0]) * 2), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0]) * 2), [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0]) * 2), [1.24, 2, 0, 0], 8, t) t = 'modcost SCALE_X - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0]) * 2), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0]) * 2), [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0]) * 2), [1, 2.8096, 0, 0], 8, t) t = 'modcost SCALE_X - pwl (gen)' t_is(totcost(gencost, array([0, 0, 5, 0]) * 2), [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0]) * 2), [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0]) * 2), [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0]) * 2), [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0]) * 2), [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0]) * 2), [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0]) * 2), [1, 2, 1500, 0], 8, t) t = 'modcost SCALE_X - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, -5]) * 2), [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10]) * 2), [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15]) * 2), [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20]) * 2), [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25]) * 2), [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30]) * 2), [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35]) * 2), [1, 2, 0, -2700], 8, t) gencost = modcost(gencost0, 3, 'SHIFT_F') t = 'modcost SHIFT_F - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0])) - 3, [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0])) - 3, [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0])) - 3, [1.24, 2, 0, 0], 8, t) t = 'modcost SHIFT_F - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0])) - 3, [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0])) - 3, [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0])) - 3, [1, 2.8096, 0, 0], 8, t) t = 'modcost SHIFT_F - pwl (gen)' t_is(totcost(gencost, array([0, 0, 5, 0])) - 3, [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0])) - 3, [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0])) - 3, [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0])) - 3, [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0])) - 3, [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0])) - 3, [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0])) - 3, [1, 2, 1500, 0], 8, t) t = 'modcost SHIFT_F - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, -5])) - 3, [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10])) - 3, [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15])) - 3, [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20])) - 3, [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25])) - 3, [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30])) - 3, [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35])) - 3, [1, 2, 0, -2700], 8, t) gencost = modcost(gencost0, -4, 'SHIFT_X') t = 'modcost SHIFT_X - quadratic' t_is(totcost(gencost, array([0, 0, 0, 0]) - 4), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([1, 0, 0, 0]) - 4), [1.11, 2, 0, 0], 8, t) t_is(totcost(gencost, array([2, 0, 0, 0]) - 4), [1.24, 2, 0, 0], 8, t) t = 'modcost SHIFT_X - 4th order polynomial' t_is(totcost(gencost, array([0, 0, 0, 0]) - 4), [1, 2, 0, 0], 8, t) t_is(totcost(gencost, array([0, 1, 0, 0]) - 4), [1, 2.3456, 0, 0], 8, t) t_is(totcost(gencost, array([0, 2, 0, 0]) - 4), [1, 2.8096, 0, 0], 8, t) t = 'modcost SHIFT_X - pwl (gen)' t_is(totcost(gencost, array([0, 0, 5, 0]) - 4), [1, 2, 100, 0], 8, t) t_is(totcost(gencost, array([0, 0, 10, 0]) - 4), [1, 2, 200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 15, 0]) - 4), [1, 2, 400, 0], 8, t) t_is(totcost(gencost, array([0, 0, 20, 0]) - 4), [1, 2, 600, 0], 8, t) t_is(totcost(gencost, array([0, 0, 25, 0]) - 4), [1, 2, 900, 0], 8, t) t_is(totcost(gencost, array([0, 0, 30, 0]) - 4), [1, 2, 1200, 0], 8, t) t_is(totcost(gencost, array([0, 0, 35, 0]) - 4), [1, 2, 1500, 0], 8, t) t = 'modcost SHIFT_X - pwl (load)' t_is(totcost(gencost, array([0, 0, 0, -5]) - 4), [1, 2, 0, -500], 8, t) t_is(totcost(gencost, array([0, 0, 0, -10]) - 4), [1, 2, 0, -1000], 8, t) t_is(totcost(gencost, array([0, 0, 0, -15]) - 4), [1, 2, 0, -1400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -20]) - 4), [1, 2, 0, -1800], 8, t) t_is(totcost(gencost, array([0, 0, 0, -25]) - 4), [1, 2, 0, -2100], 8, t) t_is(totcost(gencost, array([0, 0, 0, -30]) - 4), [1, 2, 0, -2400], 8, t) t_is(totcost(gencost, array([0, 0, 0, -35]) - 4), [1, 2, 0, -2700], 8, t) t_end()
def opf_costfcn(x, om, return_hessian=False): """Evaluates objective function, gradient and Hessian for OPF. Objective function evaluation routine for AC optimal power flow, suitable for use with L{pips}. Computes objective function value, gradient and Hessian. @param x: optimization vector @param om: OPF model object @return: C{F} - value of objective function. C{df} - (optional) gradient of objective function (column vector). C{d2f} - (optional) Hessian of objective function (sparse matrix). @see: L{opf_consfcn}, L{opf_hessfcn} @author: Carlos E. Murillo-Sanchez (PSERC Cornell & Universidad Autonoma de Manizales) @author: Ray Zimmerman (PSERC Cornell) @author: Richard Lincoln """ ##----- initialize ----- ## unpack data ppc = om.get_ppc() baseMVA, gen, gencost = ppc["baseMVA"], ppc["gen"], ppc["gencost"] cp = om.get_cost_params() N, Cw, H, dd, rh, kk, mm = \ cp["N"], cp["Cw"], cp["H"], cp["dd"], cp["rh"], cp["kk"], cp["mm"] vv, _, _, _ = om.get_idx() ## problem dimensions ng = gen.shape[0] ## number of dispatchable injections ny = om.getN('var', 'y') ## number of piece-wise linear costs nxyz = len(x) ## total number of control vars of all types ## grab Pg & Qg Pg = x[vv["i1"]["Pg"]:vv["iN"]["Pg"]] ## active generation in p.u. Qg = x[vv["i1"]["Qg"]:vv["iN"]["Qg"]] ## reactive generation in p.u. ##----- evaluate objective function ----- ## polynomial cost of P and Q # use totcost only on polynomial cost in the minimization problem # formulation, pwl cost is the sum of the y variables. ipol = find(gencost[:, MODEL] == POLYNOMIAL) ## poly MW and MVAr costs xx = r_[ Pg, Qg ] * baseMVA if any(ipol): f = sum( totcost(gencost[ipol, :], xx[ipol]) ) ## cost of poly P or Q else: f = 0 ## piecewise linear cost of P and Q if ny > 0: ccost = sparse((ones(ny), (zeros(ny), arange(vv["i1"]["y"], vv["iN"]["y"]))), (1, nxyz)).toarray().flatten() f = f + dot(ccost, x) else: ccost = zeros(nxyz) ## generalized cost term if issparse(N) and N.nnz > 0: nw = N.shape[0] r = N * x - rh ## Nx - rhat iLT = find(r < -kk) ## below dead zone iEQ = find((r == 0) & (kk == 0)) ## dead zone doesn't exist iGT = find(r > kk) ## above dead zone iND = r_[iLT, iEQ, iGT] ## rows that are Not in the Dead region iL = find(dd == 1) ## rows using linear function iQ = find(dd == 2) ## rows using quadratic function LL = sparse((ones(len(iL)), (iL, iL)), (nw, nw)) QQ = sparse((ones(len(iQ)), (iQ, iQ)), (nw, nw)) kbar = sparse((r_[ones(len(iLT)), zeros(len(iEQ)), -ones(len(iGT))], (iND, iND)), (nw, nw)) * kk rr = r + kbar ## apply non-dead zone shift M = sparse((mm[iND], (iND, iND)), (nw, nw)) ## dead zone or scale diagrr = sparse((rr, (arange(nw), arange(nw))), (nw, nw)) ## linear rows multiplied by rr(i), quadratic rows by rr(i)^2 w = M * (LL + QQ * diagrr) * rr f = f + dot(w * H, w) / 2 + dot(Cw, w) ##----- evaluate cost gradient ----- ## index ranges iPg = range(vv["i1"]["Pg"], vv["iN"]["Pg"]) iQg = range(vv["i1"]["Qg"], vv["iN"]["Qg"]) ## polynomial cost of P and Q df_dPgQg = zeros(2 * ng) ## w.r.t p.u. Pg and Qg df_dPgQg[ipol] = baseMVA * polycost(gencost[ipol, :], xx[ipol], 1) df = zeros(nxyz) df[iPg] = df_dPgQg[:ng] df[iQg] = df_dPgQg[ng:ng + ng] ## piecewise linear cost of P and Q df = df + ccost # The linear cost row is additive wrt any nonlinear cost. ## generalized cost term if issparse(N) and N.nnz > 0: HwC = H * w + Cw AA = N.T * M * (LL + 2 * QQ * diagrr) df = df + AA * HwC ## numerical check if 0: ## 1 to check, 0 to skip check ddff = zeros(df.shape) step = 1e-7 tol = 1e-3 for k in range(len(x)): xx = x xx[k] = xx[k] + step ddff[k] = (opf_costfcn(xx, om) - f) / step if max(abs(ddff - df)) > tol: idx = find(abs(ddff - df) == max(abs(ddff - df))) print('Mismatch in gradient') print('idx df(num) df diff') print('%4d%16g%16g%16g' % (range(len(df)), ddff.T, df.T, abs(ddff - df).T)) print('MAX') print('%4d%16g%16g%16g' % (idx.T, ddff[idx].T, df[idx].T, abs(ddff[idx] - df[idx]).T)) if not return_hessian: return f, df ## ---- evaluate cost Hessian ----- pcost = gencost[range(ng), :] if gencost.shape[0] > ng: qcost = gencost[ng + 1:2 * ng, :] else: qcost = array([]) ## polynomial generator costs d2f_dPg2 = zeros(ng) ## w.r.t. p.u. Pg d2f_dQg2 = zeros(ng) ## w.r.t. p.u. Qg ipolp = find(pcost[:, MODEL] == POLYNOMIAL) d2f_dPg2[ipolp] = \ baseMVA**2 * polycost(pcost[ipolp, :], Pg[ipolp]*baseMVA, 2) if any(qcost): ## Qg is not free ipolq = find(qcost[:, MODEL] == POLYNOMIAL) d2f_dQg2[ipolq] = \ baseMVA**2 * polycost(qcost[ipolq, :], Qg[ipolq] * baseMVA, 2) i = r_[iPg, iQg].T d2f = sparse((r_[d2f_dPg2, d2f_dQg2], (i, i)), (nxyz, nxyz)) ## generalized cost if N is not None and issparse(N): d2f = d2f + AA * H * AA.T + 2 * N.T * M * QQ * \ sparse((HwC, (range(nw), range(nw))), (nw, nw)) * N return f, df, d2f