INSOLVABLEPF_LIMITQ A sufficient condition for power flow insolvability considering generator reactive power limits [INSOLVABLE,VSLACK_MIN,SIGMA,ETA,MINEIGRATIO] = INSOLVABLEPF_LIMITQ(MPC,MPOPT) Evaluates a sufficient condition for insolvability of the power flow equations considering generator reactive power limits. This function uses a mixed-integer semidefinite programming formulation of the power flow equations with generator reactive power limits that maximizes the power injections in a uniform, constant power factor profile. eta indicates the factor by which the power injections can be increased in this profile. If eta < 1, no power flow solution exists that satisfies the generator reactive power limits. The converse does not necessarily hold; a power flow solution may not exist even for cases that this function does not indicate are insolvable. See [1] for further details. Note that this function is only suitable for small systems due to the computational requirements of the mixed-integer semidefinite programming solver in YALMIP. Inputs: MPC : A MATPOWER case specifying the desired power flow equations. MPOPT : A MATPOWER options struct. If not specified, it is assumed to be the default mpoption. Outputs: INSOLVABLE : Binary variable. A value of 1 indicates that the specified power flow equations are insolvable, while a value of 0 means that the insolvability condition is indeterminant (a solution may or may not exist). ETA : Power injection margin to the power flow solvability boundary in the profile of a uniform, constant power factor change in power injections. MINEIGRATIO : A measure of satisfaction of the rank relaxation. Large values indicate satisfaction. (Note that satisfaction of the rank relaxation is not required for correctness of the insolvability condition). [1] D.K. Molzahn, V. Dawar, B.C. Lesieutre, and C.L. DeMarco, "Sufficient Conditions for Power Flow Insolvability Considering Reactive Power Limited Generators with Applications to Voltage Stability Margins," in Bulk Power System Dynamics and Control - IX. Optimization, Security and Control of the Emerging Power Grid, 2013 IREP Symposium, 25-30 August 2013.
0001 function [insolvable,eta,mineigratio] = insolvablepf_limitQ(mpc,mpopt) 0002 %INSOLVABLEPF_LIMITQ A sufficient condition for power flow insolvability 0003 %considering generator reactive power limits 0004 % 0005 % [INSOLVABLE,VSLACK_MIN,SIGMA,ETA,MINEIGRATIO] = 0006 % INSOLVABLEPF_LIMITQ(MPC,MPOPT) 0007 % 0008 % Evaluates a sufficient condition for insolvability of the power flow 0009 % equations considering generator reactive power limits. This function 0010 % uses a mixed-integer semidefinite programming formulation of the power 0011 % flow equations with generator reactive power limits that maximizes the 0012 % power injections in a uniform, constant power factor profile. eta 0013 % indicates the factor by which the power injections can be increased in 0014 % this profile. If eta < 1, no power flow solution exists that satisfies 0015 % the generator reactive power limits. The converse does not 0016 % necessarily hold; a power flow solution may not exist even for cases 0017 % that this function does not indicate are insolvable. See [1] for 0018 % further details. 0019 % 0020 % Note that this function is only suitable for small systems due to the 0021 % computational requirements of the mixed-integer semidefinite 0022 % programming solver in YALMIP. 0023 % 0024 % Inputs: 0025 % MPC : A MATPOWER case specifying the desired power flow equations. 0026 % MPOPT : A MATPOWER options struct. If not specified, it is 0027 % assumed to be the default mpoption. 0028 % 0029 % Outputs: 0030 % INSOLVABLE : Binary variable. A value of 1 indicates that the 0031 % specified power flow equations are insolvable, while a value of 0032 % 0 means that the insolvability condition is indeterminant (a 0033 % solution may or may not exist). 0034 % ETA : Power injection margin to the power flow solvability 0035 % boundary in the profile of a uniform, constant power factor 0036 % change in power injections. 0037 % MINEIGRATIO : A measure of satisfaction of the rank relaxation. 0038 % Large values indicate satisfaction. (Note that satisfaction of 0039 % the rank relaxation is not required for correctness of the 0040 % insolvability condition). 0041 % 0042 % [1] D.K. Molzahn, V. Dawar, B.C. Lesieutre, and C.L. DeMarco, "Sufficient 0043 % Conditions for Power Flow Insolvability Considering Reactive Power 0044 % Limited Generators with Applications to Voltage Stability Margins," 0045 % in Bulk Power System Dynamics and Control - IX. Optimization, 0046 % Security and Control of the Emerging Power Grid, 2013 IREP Symposium, 0047 % 25-30 August 2013. 0048 0049 % MATPOWER 0050 % Copyright (c) 2013-2019, Power Systems Engineering Research Center (PSERC) 0051 % by Daniel Molzahn, PSERC U of Wisc, Madison 0052 % 0053 % This file is part of MATPOWER/mx-sdp_pf. 0054 % Covered by the 3-clause BSD License (see LICENSE file for details). 0055 % See https://github.com/MATPOWER/mx-sdp_pf/ for more info. 0056 0057 if nargin < 2 0058 mpopt = mpoption; 0059 end 0060 0061 mpc = loadcase(mpc); 0062 0063 % Unpack options 0064 ignore_angle_lim = mpopt.opf.ignore_angle_lim; 0065 verbose = mpopt.verbose; 0066 ndisplay = mpopt.sdp_pf.ndisplay; %% Determine display frequency of diagonastic information 0067 0068 maxSystemSize = 57; 0069 fixPVbusInjection = 0; % If equal to 1, don't allow changes in active power injections at PV buses. 0070 0071 if ~have_feature('yalmip') 0072 error('insolvablepf_limitQ: The software package YALMIP is required to run insolvablepf_limitQ. See https://yalmip.github.io'); 0073 end 0074 0075 % set YALMIP options struct in SDP_PF (for details, see help sdpsettings) 0076 sdpopts = yalmip_options([], mpopt); 0077 0078 % Change solver to YALMIP's branch-and-bound algorithm 0079 sdpopts = sdpsettings(sdpopts,'solver','bnb','bnb.solver',sdpopts.solver); 0080 0081 if strcmp(sdpopts.solver, 'sedumi') || strcmp(sdpopts.solver,'sdpt3') 0082 sdpopts = sdpsettings(sdpopts,'solver','bnb'); 0083 end 0084 0085 %% define named indices into data matrices 0086 [PQ, PV, REF, NONE, BUS_I, BUS_TYPE, PD, QD, GS, BS, BUS_AREA, VM, ... 0087 VA, BASE_KV, ZONE, VMAX, VMIN, LAM_P, LAM_Q, MU_VMAX, MU_VMIN] = idx_bus; 0088 [GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ... 0089 MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ... 0090 QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen; 0091 [F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, ... 0092 TAP, SHIFT, BR_STATUS, PF, QF, PT, QT, MU_SF, MU_ST, ... 0093 ANGMIN, ANGMAX, MU_ANGMIN, MU_ANGMAX] = idx_brch; 0094 [PW_LINEAR, POLYNOMIAL, MODEL, STARTUP, SHUTDOWN, NCOST, COST] = idx_cost; 0095 0096 0097 %% Load mpc data 0098 0099 mpc = ext2int(mpc); 0100 0101 if toggle_dcline(mpc, 'status') 0102 error('insolvablepf_limitQ: DC lines are not implemented in insolvablepf'); 0103 end 0104 0105 nbus = size(mpc.bus,1); 0106 ngen = size(mpc.gen,1); 0107 0108 if nbus > maxSystemSize 0109 error('insolvablepf_limitQ: System is too large (more than 57 buses) to solve with insolvablepf_limitQ'); 0110 end 0111 0112 if ~ignore_angle_lim && (any(mpc.branch(:,ANGMIN) ~= -360) || any(mpc.branch(:,ANGMAX) ~= 360)) 0113 warning('insolvablepf_limitQ: Angle difference constraints are not implemented in SDP_PF. Ignoring angle difference constraints.'); 0114 end 0115 0116 % Some of the larger system (e.g., case2746wp) have generators 0117 % corresponding to buses that have bustype == PQ. Change these 0118 % to PV buses. 0119 for i=1:ngen 0120 busidx = find(mpc.bus(:,BUS_I) == mpc.gen(i,GEN_BUS)); 0121 if isempty(busidx) || ~(mpc.bus(busidx,BUS_TYPE) == PV || mpc.bus(busidx,BUS_TYPE) == REF) 0122 mpc.bus(busidx,BUS_TYPE) = PV; 0123 if verbose >= 1 0124 warning('insolvablepf_limitQ: Bus %s has generator(s) but is listed as a PQ bus. Changing to a PV bus.',int2str(busidx)); 0125 end 0126 end 0127 end 0128 0129 % Buses may be listed as PV buses without associated generators. Change 0130 % these buses to PQ. 0131 for i=1:nbus 0132 if mpc.bus(i,BUS_TYPE) == PV 0133 genidx = find(mpc.gen(:,GEN_BUS) == mpc.bus(i,BUS_I), 1); 0134 if isempty(genidx) 0135 mpc.bus(i,BUS_TYPE) = PQ; 0136 if verbose >= 1 0137 warning('insolvablepf_limitQ: PV bus %i has no associated generator! Changing these buses to PQ.',i); 0138 end 0139 end 0140 end 0141 end 0142 0143 0144 Sbase = mpc.baseMVA; 0145 0146 % Create vectors of power injections and voltage magnitudes 0147 Qd = mpc.bus(:,QD) / Sbase; 0148 Qinj = -Qd; 0149 Vmag = mpc.bus(:,VM); 0150 0151 Pd = mpc.bus(:,PD) / Sbase; 0152 Pg = zeros(nbus,1); 0153 Qmin = zeros(nbus,1); 0154 Qmax = zeros(nbus,1); 0155 for i=1:nbus 0156 genidx = find(mpc.gen(:,GEN_BUS) == i); 0157 if ~isempty(genidx) 0158 Pg(i) = sum(mpc.gen(genidx,PG)) / Sbase; 0159 Vmag(i) = mpc.gen(genidx(1),VG); 0160 Qmin(i) = sum(mpc.gen(genidx,QMIN)) / Sbase - Qd(i); 0161 Qmax(i) = sum(mpc.gen(genidx,QMAX)) / Sbase - Qd(i); 0162 end 0163 end 0164 Pinj = Pg - Pd; 0165 0166 0167 %% Functions to build matrices 0168 0169 [Yk,Yk_,Mk,Ylineft,Ylinetf,Y_lineft,Y_linetf] = makesdpmat(mpc); 0170 0171 %% Create primal SDP variables 0172 0173 [junk1,uniqueGenIdx,junk2] = unique(mpc.gen(:,GEN_BUS)); 0174 mpc.gen = mpc.gen(uniqueGenIdx,:); 0175 ngen = size(mpc.gen,1); 0176 0177 W = sdpvar(2*nbus,2*nbus); 0178 eta = sdpvar(1,1); 0179 constraints = []; 0180 0181 % Binary variables 0182 yL = binvar(ngen,1); 0183 yU = binvar(ngen,1); 0184 0185 % We need a number greater than any plausible value of V^2, but too large 0186 % of a value causes numerical problems 0187 bigM = 10*max(Vmag)^2; 0188 0189 %% Build primal problem 0190 0191 for k=1:nbus 0192 0193 if ~fixPVbusInjection 0194 % PQ and PV buses have uniform active power injection changes 0195 if mpc.bus(k,BUS_TYPE) == PQ || mpc.bus(k,BUS_TYPE) == PV 0196 constraints = [constraints; 0197 trace(Yk(k)*W) == eta*Pinj(k)]; 0198 end 0199 else 0200 % Alternatively, we can fix PV bus active power injections for a 0201 % different power injection profile. 0202 if mpc.bus(k,BUS_TYPE) == PQ 0203 constraints = [constraints; 0204 trace(Yk(k)*W) == eta*Pinj(k)]; 0205 end 0206 if mpc.bus(k,BUS_TYPE) == PV 0207 constraints = [constraints; 0208 trace(Yk(k)*W) == Pinj(k)]; 0209 end 0210 end 0211 0212 % PQ buses have uniform reactive power injection changes 0213 if mpc.bus(k,BUS_TYPE) == PQ 0214 constraints = [constraints; 0215 trace(Yk_(k)*W) == eta*Qinj(k)]; 0216 end 0217 0218 % Mixed integer formulation for generator reactive power limits 0219 if mpc.bus(k,BUS_TYPE) == PV || mpc.bus(k,BUS_TYPE) == REF 0220 0221 genidx = find(mpc.gen(:,GEN_BUS) == k,1); 0222 0223 constraints = [constraints; 0224 trace(Yk_(k)*W) <= Qmin(k)*yL(genidx) + Qmax(k)*(1-yL(genidx)); 0225 trace(Yk_(k)*W) >= Qmax(k)*yU(genidx) + Qmin(k)*(1-yU(genidx))]; 0226 0227 constraints = [constraints; 0228 trace(Mk(k)*W) >= Vmag(k)^2*(1-yU(genidx)); 0229 trace(Mk(k)*W) <= Vmag(k)^2*(1-yL(genidx)) + bigM*(yL(genidx))]; 0230 0231 constraints = [constraints; 0232 yU(genidx) + yL(genidx) <= 1]; 0233 0234 end 0235 0236 if verbose >= 2 && mod(k,ndisplay) == 0 0237 fprintf('SDP creation: Bus %i of %i\n',k,nbus); 0238 end 0239 0240 end % Loop through all buses 0241 0242 % Force at least one bus to supply reactive power balance 0243 % genbuses = (mpc.bus(:,BUS_TYPE) == PV | mpc.bus(:,BUS_TYPE) == REF); 0244 % constraints = [constraints; 0245 % sum(yL(genbuses)+yU(genbuses)) <= ngen - 1]; 0246 constraints = [constraints; 0247 sum(yL + yU) <= ngen - 1]; 0248 0249 constraints = [constraints; W >= 0]; 0250 0251 0252 0253 %% Solve the SDP 0254 0255 % Preserve warning settings 0256 S = warning; 0257 0258 % Run sdp solver 0259 sdpinfo = solvesdp(constraints, -eta, sdpopts); 0260 if sdpinfo.problem == 2 || sdpinfo.problem == -2 || sdpinfo.problem == -3 0261 error(yalmiperror(sdpinfo.problem)); 0262 end 0263 if ~have_feature('octave') || have_feature('octave', 'vnum') >= 4.001 0264 %% (avoid bug in Octave 4.0.x, where warning state is left corrupted) 0265 warning(S); 0266 end 0267 0268 if verbose >= 2 0269 fprintf('Solver exit message: %s\n',sdpinfo.info); 0270 end 0271 0272 0273 %% Calculate rank characteristics of the solution 0274 0275 evl = sort(eig(double(W))); 0276 mineigratio = abs(evl(end) / evl(end-2)); 0277 0278 0279 %% Output results 0280 0281 eta = double(eta); 0282 insolvable = eta < 1; % Is this right? I think it should be the other way around...