


TOGGLE_SOFTLIMS Relax DC optimal power flow branch limits.
MPC = TOGGLE_SOFTLIMS(MPC, 'on')
MPC = TOGGLE_SOFTLIMS(MPC, 'off')
T_F = TOGGLE_SOFTLIMS(MPC, 'status')
Enables, disables or checks the status of a set of OPF userfcn
callbacks to implement relaxed branch flow limits a DC optimal flow model.
These callbacks expect to find a 'softlims' field in the input MPC,
where MPC.softlims is a struct with the following fields:
idx (optional) n x 1, index vector for branches whos flow
limits are to be relaxed, default is to use all on-line
branches with non-zero limits specified in RATE_A
cost (optional) n x 1, linear marginal cost per MW of exceeding
RATE_A. Can optionally be a scalar, in which case it is
applied to all soft limits. Default if not specified is
$1000/MW.
The 'int2ext' callback also packages up results and stores them in
the following output fields of results.softlims:
overload - nl x 1, amount of overload of each line in MW
ovl_cost - nl x 1, total cost of overload in $/hr
The shadow prices on the soft limit constraints are also returned in the
MU_SF and MU_ST columns of the branch matrix.
Note: These shadow prices are equal to the corresponding hard limit
shadow prices when the soft limits are not violated. When violated,
the shadow price on a soft limit constraint is equal to the
user-specified soft limit violation cost.
See also ADD_USERFCN, REMOVE_USERFCN, RUN_USERFCN, T_CASE30_USERFCNS.


0001 function mpc = toggle_softlims(mpc, on_off) 0002 %TOGGLE_SOFTLIMS Relax DC optimal power flow branch limits. 0003 % MPC = TOGGLE_SOFTLIMS(MPC, 'on') 0004 % MPC = TOGGLE_SOFTLIMS(MPC, 'off') 0005 % T_F = TOGGLE_SOFTLIMS(MPC, 'status') 0006 % 0007 % Enables, disables or checks the status of a set of OPF userfcn 0008 % callbacks to implement relaxed branch flow limits a DC optimal flow model. 0009 % 0010 % These callbacks expect to find a 'softlims' field in the input MPC, 0011 % where MPC.softlims is a struct with the following fields: 0012 % idx (optional) n x 1, index vector for branches whos flow 0013 % limits are to be relaxed, default is to use all on-line 0014 % branches with non-zero limits specified in RATE_A 0015 % cost (optional) n x 1, linear marginal cost per MW of exceeding 0016 % RATE_A. Can optionally be a scalar, in which case it is 0017 % applied to all soft limits. Default if not specified is 0018 % $1000/MW. 0019 % 0020 % The 'int2ext' callback also packages up results and stores them in 0021 % the following output fields of results.softlims: 0022 % overload - nl x 1, amount of overload of each line in MW 0023 % ovl_cost - nl x 1, total cost of overload in $/hr 0024 % 0025 % The shadow prices on the soft limit constraints are also returned in the 0026 % MU_SF and MU_ST columns of the branch matrix. 0027 % Note: These shadow prices are equal to the corresponding hard limit 0028 % shadow prices when the soft limits are not violated. When violated, 0029 % the shadow price on a soft limit constraint is equal to the 0030 % user-specified soft limit violation cost. 0031 % 0032 % See also ADD_USERFCN, REMOVE_USERFCN, RUN_USERFCN, T_CASE30_USERFCNS. 0033 0034 % To do for future versions: 0035 % - make softlims input field optional, convert all lines if missing 0036 % Inputs: 0037 % cost n x 3, linear marginal cost per MW of exceeding each of 0038 % RATE_A, RATE_B and RATE_C. Columns 2 and 3 are optional. 0039 % brkpts n x npts, allow to specify arbitrary breakpoints at which 0040 % cost increases, defined as percentages above RATE_A. 0041 % base_flow n x 1, arbitrary baseline (other than RATE_A) 0042 0043 % MATPOWER 0044 % $Id: toggle_softlims.m 2433 2014-12-03 14:45:50Z ray $ 0045 % by Ray Zimmerman, PSERC Cornell 0046 % Copyright (c) 2009-2014 by Power System Engineering Research Center (PSERC) 0047 % 0048 % This file is part of MATPOWER. 0049 % See http://www.pserc.cornell.edu/matpower/ for more info. 0050 % 0051 % MATPOWER is free software: you can redistribute it and/or modify 0052 % it under the terms of the GNU General Public License as published 0053 % by the Free Software Foundation, either version 3 of the License, 0054 % or (at your option) any later version. 0055 % 0056 % MATPOWER is distributed in the hope that it will be useful, 0057 % but WITHOUT ANY WARRANTY; without even the implied warranty of 0058 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0059 % GNU General Public License for more details. 0060 % 0061 % You should have received a copy of the GNU General Public License 0062 % along with MATPOWER. If not, see <http://www.gnu.org/licenses/>. 0063 % 0064 % Additional permission under GNU GPL version 3 section 7 0065 % 0066 % If you modify MATPOWER, or any covered work, to interface with 0067 % other modules (such as MATLAB code and MEX-files) available in a 0068 % MATLAB(R) or comparable environment containing parts covered 0069 % under other licensing terms, the licensors of MATPOWER grant 0070 % you additional permission to convey the resulting work. 0071 0072 if strcmp(upper(on_off), 'ON') 0073 %% check for proper softlims inputs 0074 %% (inputs are optional, defaults handled in ext2int callback) 0075 0076 %% add callback functions 0077 %% note: assumes all necessary data included in 1st arg (mpc, om, results) 0078 %% so, no additional explicit args are needed 0079 mpc = add_userfcn(mpc, 'ext2int', @userfcn_softlims_ext2int); 0080 mpc = add_userfcn(mpc, 'formulation', @userfcn_softlims_formulation); 0081 mpc = add_userfcn(mpc, 'int2ext', @userfcn_softlims_int2ext); 0082 mpc = add_userfcn(mpc, 'printpf', @userfcn_softlims_printpf); 0083 mpc = add_userfcn(mpc, 'savecase', @userfcn_softlims_savecase); 0084 mpc.userfcn.status.softlims = 1; 0085 elseif strcmp(upper(on_off), 'OFF') 0086 mpc = remove_userfcn(mpc, 'savecase', @userfcn_softlims_savecase); 0087 mpc = remove_userfcn(mpc, 'printpf', @userfcn_softlims_printpf); 0088 mpc = remove_userfcn(mpc, 'int2ext', @userfcn_softlims_int2ext); 0089 mpc = remove_userfcn(mpc, 'formulation', @userfcn_softlims_formulation); 0090 mpc = remove_userfcn(mpc, 'ext2int', @userfcn_softlims_ext2int); 0091 mpc.userfcn.status.softlims = 0; 0092 elseif strcmp(upper(on_off), 'STATUS') 0093 if isfield(mpc, 'userfcn') && isfield(mpc.userfcn, 'status') && ... 0094 isfield(mpc.userfcn.status, 'softlims') 0095 mpc = mpc.userfcn.status.softlims; 0096 else 0097 mpc = 0; 0098 end 0099 else 0100 error('toggle_softlims: 2nd argument must be ''on'', ''off'' or ''status'''); 0101 end 0102 0103 0104 %%----- ext2int ------------------------------------------------------ 0105 function mpc = userfcn_softlims_ext2int(mpc, args) 0106 % 0107 % mpc = userfcn_softlims_ext2int(mpc, args) 0108 % 0109 % This is the 'ext2int' stage userfcn callback that prepares the input 0110 % data for the formulation stage. It expects to find a 'softlims' field in 0111 % mpc as described above. The optional args are not currently used. 0112 0113 %% define named indices into data matrices 0114 [F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, ... 0115 TAP, SHIFT, BR_STATUS, PF, QF, PT, QT, MU_SF, MU_ST, ... 0116 ANGMIN, ANGMAX, MU_ANGMIN, MU_ANGMAX] = idx_brch; 0117 0118 %% check for proper softlims inputs 0119 default_cost = 1000; %% used if cost is not specified 0120 if isfield(mpc, 'softlims') 0121 if ~isfield(mpc.softlims, 'idx') 0122 mpc.softlims.idx = []; 0123 end 0124 if ~isfield(mpc.softlims, 'cost') 0125 mpc.softlims.cost = default_cost; 0126 end 0127 else 0128 mpc.softlims.idx = []; 0129 mpc.softlims.cost = default_cost; 0130 end 0131 0132 %% initialize some things 0133 s = mpc.softlims; 0134 o = mpc.order; 0135 nl0 = size(o.ext.branch, 1); %% original number of branches 0136 nl = size(mpc.branch, 1); %% number of on-line branches 0137 0138 %% save softlims struct for external indexing 0139 mpc.order.ext.softlims = s; 0140 0141 %%----- convert stuff to internal indexing ----- 0142 s = softlims_defaults(s, o.ext.branch); %% get defaults 0143 e2i = zeros(nl0, 1); 0144 e2i(o.branch.status.on) = (1:nl)'; %% ext->int branch index mapping 0145 s.idx = e2i(s.idx); 0146 k = find(s.idx == 0); %% find idxes corresponding to off-line branches 0147 s.idx(k) = []; %% delete them 0148 s.cost(k, :) = []; 0149 k = find(mpc.branch(s.idx, RATE_A) <= 0); %% find branches w/o limits 0150 s.idx(k) = []; %% delete them 0151 s.cost(k, :) = []; 0152 0153 %%----- remove hard limits on branches with soft limits ----- 0154 s.Pfmax = mpc.branch(s.idx, RATE_A) / mpc.baseMVA; %% save limit first 0155 mpc.branch(s.idx, RATE_A) = 0; %% then remove it 0156 0157 mpc.softlims = s; 0158 mpc.order.int.softlims = s; 0159 0160 0161 %%----- formulation -------------------------------------------------- 0162 function om = userfcn_softlims_formulation(om, args) 0163 % 0164 % om = userfcn_softlims_formulation(om, args) 0165 % 0166 % This is the 'formulation' stage userfcn callback that defines the 0167 % user costs and constraints for interface flow limits. It expects to 0168 % find a 'softlims' field in the mpc stored in om, as described above. The 0169 % optional args are not currently used. 0170 0171 %% define named indices into data matrices 0172 [F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, ... 0173 TAP, SHIFT, BR_STATUS, PF, QF, PT, QT, MU_SF, MU_ST, ... 0174 ANGMIN, ANGMAX, MU_ANGMIN, MU_ANGMAX] = idx_brch; 0175 0176 %% initialize some things 0177 mpc = get_mpc(om); 0178 [baseMVA, bus, branch] = deal(mpc.baseMVA, mpc.bus, mpc.branch); 0179 s = mpc.softlims; 0180 0181 %% form B matrices for DC model 0182 [B, Bf, Pbusinj, Pfinj] = makeBdc(baseMVA, bus, branch); 0183 n = size(Bf, 2); %% dim of theta 0184 0185 %% form constraints (flv is flow limit violation variable) 0186 %% -Bf * Va - flv <= Pfinj + Pfmax 0187 %% Bf * Va - flv <= -Pfinj + Pfmax 0188 ns = length(s.idx); %% number of soft limits 0189 I = speye(ns, ns); 0190 As = [-Bf(s.idx, :) -I; Bf(s.idx, :) -I]; 0191 ls = -Inf(2*ns, 1); 0192 us = [ Pfinj(s.idx) + s.Pfmax; 0193 -Pfinj(s.idx) + s.Pfmax ]; 0194 0195 %% costs on flv variable 0196 Cw = s.cost(:, 1) * mpc.baseMVA; 0197 0198 %% add vars, costs, constraints 0199 om = add_vars(om, 'flv', ns, zeros(ns, 1), zeros(ns, 1), Inf(ns, 1)); 0200 om = add_costs(om, 'vc', struct('N', I, 'Cw', Cw), {'flv'}); 0201 om = add_constraints(om, 'softlims', As, ls, us, {'Va', 'flv'}); %% 2*ns 0202 0203 0204 %%----- int2ext ------------------------------------------------------ 0205 function results = userfcn_softlims_int2ext(results, args) 0206 % 0207 % results = userfcn_softlims_int2ext(results, args) 0208 % 0209 % This is the 'int2ext' stage userfcn callback that converts everything 0210 % back to external indexing and packages up the results. It expects to 0211 % find a 'softlims' field in the results struct as described for mpc above. 0212 % It also expects the results to contain solved branch flows and linear 0213 % constraints named 'softlims' which are used to populate output fields 0214 % in results.softlims. The optional args are not currently used. 0215 0216 %% define named indices into data matrices 0217 [F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, ... 0218 TAP, SHIFT, BR_STATUS, PF, QF, PT, QT, MU_SF, MU_ST, ... 0219 ANGMIN, ANGMAX, MU_ANGMIN, MU_ANGMAX] = idx_brch; 0220 0221 %% get internal softlims struct 0222 s = results.softlims; 0223 0224 %%----- convert stuff back to external indexing ----- 0225 o = results.order; 0226 nl0 = size(o.ext.branch, 1); %% original number of branches 0227 nl = size(results.branch, 1); %% number of on-line branches 0228 o.branch.status.on; 0229 results.softlims = results.order.ext.softlims; 0230 0231 %%----- restore hard limits ----- 0232 results.branch(s.idx, RATE_A) = s.Pfmax * results.baseMVA; 0233 0234 %%----- results post-processing ----- 0235 %% get shadow prices 0236 n = size(results.lin.mu.u.softlims, 1) / 2; 0237 results.branch(s.idx, MU_ST) = results.lin.mu.u.softlims(1:n) / results.baseMVA; 0238 results.branch(s.idx, MU_SF) = results.lin.mu.u.softlims(n+1:end) / results.baseMVA; 0239 %% get overloads and overload costs 0240 results.softlims.overload = zeros(nl0, 1); 0241 k = find(results.branch(:, RATE_A) & ... 0242 abs(results.branch(:, PF)) > results.branch(:, RATE_A) ); 0243 results.softlims.overload(o.branch.status.on(k)) = ... 0244 abs(results.branch(k, PF)) - results.branch(k, RATE_A); 0245 results.softlims.ovl_cost = zeros(size(results.softlims.overload)); 0246 results.softlims.ovl_cost(o.branch.status.on(s.idx)) = ... 0247 results.softlims.overload(o.branch.status.on(s.idx)) .* s.cost(:, 1); 0248 0249 0250 %%----- printpf ------------------------------------------------------ 0251 function results = userfcn_softlims_printpf(results, fd, mpopt, args) 0252 % 0253 % results = userfcn_softlims_printpf(results, fd, mpopt, args) 0254 % 0255 % This is the 'printpf' stage userfcn callback that pretty-prints the 0256 % results. It expects a results struct, a file descriptor and a MATPOWER 0257 % options struct. The optional args are not currently used. 0258 0259 %% define named indices into data matrices 0260 [F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, ... 0261 TAP, SHIFT, BR_STATUS, PF, QF, PT, QT, MU_SF, MU_ST, ... 0262 ANGMIN, ANGMAX, MU_ANGMIN, MU_ANGMAX] = idx_brch; 0263 0264 %%----- print results ----- 0265 ptol = 1e-6; %% tolerance for displaying shadow prices 0266 isOPF = isfield(results, 'f') && ~isempty(results.f); 0267 SUPPRESS = mpopt.out.suppress_detail; 0268 if SUPPRESS == -1 0269 if size(results.bus, 1) > 500 0270 SUPPRESS = 1; 0271 else 0272 SUPPRESS = 0; 0273 end 0274 end 0275 OUT_ALL = mpopt.out.all; 0276 OUT_FORCE = mpopt.out.force; 0277 OUT_BRANCH = OUT_ALL == 1 || (OUT_ALL == -1 && ~SUPPRESS && mpopt.out.branch); 0278 0279 if isOPF && OUT_BRANCH && (results.success || OUT_FORCE) 0280 s = softlims_defaults(results.softlims, results.branch); %% get defaults 0281 k = find(s.overload(s.idx) | sum(results.branch(s.idx, MU_SF:MU_ST), 2) > ptol); 0282 0283 fprintf(fd, '\n================================================================================'); 0284 fprintf(fd, '\n| Soft Flow Limits |'); 0285 fprintf(fd, '\n================================================================================'); 0286 fprintf(fd, '\nBrnch From To Flow Limit Overload mu'); 0287 fprintf(fd, '\n # Bus Bus (MW) (MW) (MW) ($/MW)'); 0288 fprintf(fd, '\n----- ----- ----- -------- -------- -------- ---------'); 0289 fprintf(fd, '\n%4d%7d%7d%10.2f%10.2f%10.2f%11.3f', ... 0290 [ s.idx(k), results.branch(s.idx(k), [F_BUS, T_BUS]), ... 0291 results.branch(s.idx(k), [PF, RATE_A]), ... 0292 s.overload(s.idx(k)), ... 0293 sum(results.branch(s.idx(k), [MU_SF:MU_ST]), 2) ... 0294 ]'); 0295 fprintf(fd, '\n --------'); 0296 fprintf(fd, '\n Total:%10.2f', ... 0297 sum(s.overload(s.idx(k)))); 0298 fprintf(fd, '\n'); 0299 end 0300 0301 0302 %%----- savecase ----------------------------------------------------- 0303 function mpc = userfcn_softlims_savecase(mpc, fd, prefix, args) 0304 % 0305 % mpc = userfcn_softlims_savecase(mpc, fd, mpopt, args) 0306 % 0307 % This is the 'savecase' stage userfcn callback that prints the M-file 0308 % code to save the 'softlims' field in the case file. It expects a 0309 % MATPOWER case struct (mpc), a file descriptor and variable prefix 0310 % (usually 'mpc.'). The optional args are not currently used. 0311 0312 if isfield(mpc, 'softlims') 0313 s = mpc.softlims; 0314 0315 fprintf(fd, '\n%%%%----- Soft Flow Limit Data -----%%%%\n'); 0316 0317 if isfield(s, 'idx') 0318 fprintf(fd, '%%%% branch indexes\n'); 0319 fprintf(fd, '%%\tbranchidx\n'); 0320 if isempty(s.idx) 0321 fprintf(fd, '%ssoftlims.idx = [];\n\n', prefix); 0322 else 0323 fprintf(fd, '%ssoftlims.idx = [\n', prefix); 0324 fprintf(fd, '\t%d;\n', s.idx); 0325 fprintf(fd, '];\n\n'); 0326 end 0327 end 0328 0329 fprintf(fd, '%%%% violation cost coefficients\n'); 0330 fprintf(fd, '%%\trate_a_cost\n'); 0331 fprintf(fd, '%ssoftlims.cost = [\n', prefix); 0332 fprintf(fd, '\t%g;\n', s.cost); 0333 fprintf(fd, '];\n'); 0334 0335 %% save output fields for solved case 0336 if isfield(mpc.softlims, 'overload') 0337 fprintf(fd, '\n%%%% overloads\n'); 0338 fprintf(fd, '%%\toverload\n'); 0339 fprintf(fd, '%ssoftlims.overload = [\n', prefix); 0340 fprintf(fd, '\t%g;\n', s.overload); 0341 fprintf(fd, '];\n'); 0342 0343 fprintf(fd, '\n%%%% overload costs\n'); 0344 fprintf(fd, '%%\toverload_costs\n'); 0345 fprintf(fd, '%ssoftlims.ovl_cost = [\n', prefix); 0346 fprintf(fd, '\t%g;\n', s.ovl_cost); 0347 fprintf(fd, '];\n'); 0348 end 0349 end 0350 0351 %%----- softlims_defaults -------------------------------------------- 0352 function s = softlims_defaults(s, branch) 0353 % 0354 % s = softlims_defaults(s, branch) 0355 % 0356 % Takes a softlims struct that could have an empty 'idx' field or a 0357 % scalar 'cost' field and fills them out with the defaults, where the 0358 % default for 'idx' includes all on-line branches with non-zero RATE_A, 0359 % and the default for the cost is to apply the scalar to each soft limit 0360 % violation. 0361 0362 %% define named indices into data matrices 0363 [F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, ... 0364 TAP, SHIFT, BR_STATUS, PF, QF, PT, QT, MU_SF, MU_ST, ... 0365 ANGMIN, ANGMAX, MU_ANGMIN, MU_ANGMAX] = idx_brch; 0366 0367 if isempty(s.idx) 0368 s.idx = find(branch(:, BR_STATUS) > 0 & branch(:, RATE_A) > 0); 0369 end 0370 if length(s.cost) == 1 && length(s.idx) > 1 0371 s.cost = s.cost * ones(size(s.idx)); 0372 end