0001 function mpc = toggle_reserves(mpc, on_off)
0002
0003
0004
0005
0006
0007
0008
0009
0010
0011
0012
0013
0014
0015
0016
0017
0018
0019
0020
0021
0022
0023
0024
0025
0026
0027
0028
0029
0030
0031
0032
0033
0034
0035
0036
0037
0038
0039
0040
0041
0042
0043
0044
0045 if strcmp(upper(on_off), 'ON')
0046
0047 if ~isfield(mpc, 'reserves') || ~isstruct(mpc.reserves) || ...
0048 ~isfield(mpc.reserves, 'zones') || ...
0049 ~isfield(mpc.reserves, 'req') || ...
0050 ~isfield(mpc.reserves, 'cost')
0051 error('toggle_reserves: case must contain a ''reserves'' field, a struct defining ''zones'', ''req'' and ''cost''');
0052 end
0053
0054
0055
0056
0057 mpc = add_userfcn(mpc, 'ext2int', @userfcn_reserves_ext2int);
0058 mpc = add_userfcn(mpc, 'formulation', @userfcn_reserves_formulation);
0059 mpc = add_userfcn(mpc, 'int2ext', @userfcn_reserves_int2ext);
0060 mpc = add_userfcn(mpc, 'printpf', @userfcn_reserves_printpf);
0061 mpc = add_userfcn(mpc, 'savecase', @userfcn_reserves_savecase);
0062 mpc.userfcn.status.reserves = 1;
0063 elseif strcmp(upper(on_off), 'OFF')
0064 mpc = remove_userfcn(mpc, 'savecase', @userfcn_reserves_savecase);
0065 mpc = remove_userfcn(mpc, 'printpf', @userfcn_reserves_printpf);
0066 mpc = remove_userfcn(mpc, 'int2ext', @userfcn_reserves_int2ext);
0067 mpc = remove_userfcn(mpc, 'formulation', @userfcn_reserves_formulation);
0068 mpc = remove_userfcn(mpc, 'ext2int', @userfcn_reserves_ext2int);
0069 mpc.userfcn.status.reserves = 0;
0070 elseif strcmp(upper(on_off), 'STATUS')
0071 if isfield(mpc, 'userfcn') && isfield(mpc.userfcn, 'status') && ...
0072 isfield(mpc.userfcn.status, 'reserves')
0073 mpc = mpc.userfcn.status.reserves;
0074 else
0075 mpc = 0;
0076 end
0077 else
0078 error('toggle_reserves: 2nd argument must be ''on'', ''off'' or ''status''');
0079 end
0080
0081
0082
0083 function mpc = userfcn_reserves_ext2int(mpc, mpopt, args)
0084
0085
0086
0087
0088
0089
0090
0091
0092 r = mpc.reserves;
0093 o = mpc.order;
0094 ng0 = size(o.ext.gen, 1);
0095 nrz = size(r.req, 1);
0096 if nrz > 1
0097 mpc.reserves.rgens = any(r.zones);
0098 else
0099 mpc.reserves.rgens = r.zones;
0100 end
0101 igr = find(mpc.reserves.rgens);
0102 ngr = length(igr);
0103
0104
0105 if size(r.zones, 1) ~= nrz
0106 error('userfcn_reserves_ext2int: the number of rows in mpc.reserves.req (%d) and mpc.reserves.zones (%d) must match', nrz, size(r.zones, 1));
0107 end
0108 if size(r.cost, 1) ~= ng0 && size(r.cost, 1) ~= ngr
0109 error('userfcn_reserves_ext2int: the number of rows in mpc.reserves.cost (%d) must equal the total number of generators (%d) or the number of generators able to provide reserves (%d)', size(r.cost, 1), ng0, ngr);
0110 end
0111 if isfield(r, 'qty') && size(r.qty, 1) ~= size(r.cost, 1)
0112 error('userfcn_reserves_ext2int: mpc.reserves.cost (%d x 1) and mpc.reserves.qty (%d x 1) must be the same dimension', size(r.cost, 1), size(r.qty, 1));
0113 end
0114
0115
0116 if size(r.cost, 1) < ng0
0117 mpc.reserves.original.cost = r.cost;
0118 cost = zeros(ng0, 1);
0119 cost(igr) = r.cost;
0120 mpc.reserves.cost = cost;
0121 if isfield(r, 'qty')
0122 mpc.reserves.original.qty = r.qty;
0123 qty = zeros(ng0, 1);
0124 qty(igr) = r.qty;
0125 mpc.reserves.qty = qty;
0126 end
0127 end
0128
0129
0130
0131 if isfield(r, 'qty')
0132 mpc = e2i_field(mpc, {'reserves', 'qty'}, 'gen');
0133 end
0134 mpc = e2i_field(mpc, {'reserves', 'cost'}, 'gen');
0135 mpc = e2i_field(mpc, {'reserves', 'zones'}, 'gen', 2);
0136 mpc = e2i_field(mpc, {'reserves', 'rgens'}, 'gen', 2);
0137
0138
0139 mpc.order.ext.reserves.igr = igr;
0140 mpc.reserves.igr = find(mpc.reserves.rgens);
0141
0142
0143
0144 function om = userfcn_reserves_formulation(om, mpopt, args)
0145
0146
0147
0148
0149
0150
0151
0152
0153
0154
0155
0156
0157
0158
0159
0160
0161 [GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ...
0162 MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ...
0163 QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen;
0164
0165
0166 mpc = om.get_mpc();
0167 r = mpc.reserves;
0168 igr = r.igr;
0169 ngr = length(igr);
0170 ng = size(mpc.gen, 1);
0171
0172
0173 Rmin = zeros(ngr, 1);
0174 Rmax = Inf(ngr, 1);
0175 k = find(mpc.gen(igr, RAMP_10));
0176 Rmax(k) = mpc.gen(igr(k), RAMP_10);
0177 if isfield(r, 'qty')
0178 k = find(r.qty(igr) < Rmax);
0179 Rmax(k) = r.qty(igr(k));
0180 end
0181 Rmax = Rmax / mpc.baseMVA;
0182
0183
0184 I = speye(ngr);
0185 Ar = [sparse(1:ngr, igr, 1, ngr, ng) I];
0186 ur = mpc.gen(igr, PMAX) / mpc.baseMVA;
0187 lreq = r.req / mpc.baseMVA;
0188
0189
0190 Cw = r.cost(igr) * mpc.baseMVA;
0191
0192
0193 om.add_var('R', ngr, [], Rmin, Rmax);
0194 om.add_lin_constraint('Pg_plus_R', Ar, [], ur, {'Pg', 'R'});
0195 om.add_lin_constraint('Rreq', r.zones(:, igr), lreq, [], {'R'});
0196 om.add_quad_cost('Rcost', [], Cw, 0, {'R'});
0197
0198
0199
0200 function results = userfcn_reserves_int2ext(results, mpopt, args)
0201
0202
0203
0204
0205
0206
0207
0208
0209
0210
0211
0212
0213 r = results.reserves;
0214
0215
0216 igr = r.igr;
0217 ng = size(results.gen, 1);
0218
0219
0220
0221 if isfield(r, 'qty')
0222 results = i2e_field(results, {'reserves', 'qty'}, 'gen');
0223 end
0224 results = i2e_field(results, {'reserves', 'cost'}, 'gen');
0225 results = i2e_field(results, {'reserves', 'zones'}, 'gen', 2);
0226 results = i2e_field(results, {'reserves', 'rgens'}, 'gen', 2);
0227 results.order.int.reserves.igr = results.reserves.igr;
0228 results.reserves.igr = results.order.ext.reserves.igr;
0229 r = results.reserves;
0230 o = results.order;
0231
0232
0233 igr0 = r.igr;
0234 ng0 = size(o.ext.gen, 1);
0235
0236
0237
0238
0239 [R0, Rl, Ru] = results.om.params_var('R');
0240 R = zeros(ng, 1);
0241 Rmin = zeros(ng, 1);
0242 Rmax = zeros(ng, 1);
0243 mu_l = zeros(ng, 1);
0244 mu_u = zeros(ng, 1);
0245 mu_Pmax = zeros(ng, 1);
0246 R(igr) = results.var.val.R * results.baseMVA;
0247 Rmin(igr) = Rl * results.baseMVA;
0248 Rmax(igr) = Ru * results.baseMVA;
0249 mu_l(igr) = results.var.mu.l.R / results.baseMVA;
0250 mu_u(igr) = results.var.mu.u.R / results.baseMVA;
0251 mu_Pmax(igr) = results.lin.mu.u.Pg_plus_R / results.baseMVA;
0252
0253
0254 z = zeros(ng0, 1);
0255 results.reserves.R = i2e_data(results, R, z, 'gen');
0256 results.reserves.Rmin = i2e_data(results, Rmin, z, 'gen');
0257 results.reserves.Rmax = i2e_data(results, Rmax, z, 'gen');
0258 results.reserves.mu.l = i2e_data(results, mu_l, z, 'gen');
0259 results.reserves.mu.u = i2e_data(results, mu_u, z, 'gen');
0260 results.reserves.mu.Pmax = i2e_data(results, mu_Pmax, z, 'gen');
0261 results.reserves.prc = z;
0262 for k = igr0
0263 iz = find(r.zones(:, k));
0264 results.reserves.prc(k) = sum(results.lin.mu.l.Rreq(iz)) / results.baseMVA;
0265 end
0266 results.reserves.totalcost = sum(results.qdc.Rcost);
0267
0268
0269 if isfield(r, 'original')
0270 if isfield(r, 'qty')
0271 results.reserves.qty = r.original.qty;
0272 end
0273 results.reserves.cost = r.original.cost;
0274 results.reserves = rmfield(results.reserves, 'original');
0275 end
0276
0277
0278
0279 function results = userfcn_reserves_printpf(results, fd, mpopt, args)
0280
0281
0282
0283
0284
0285
0286
0287
0288 [GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ...
0289 MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ...
0290 QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen;
0291
0292
0293 r = results.reserves;
0294 nrz = size(r.req, 1);
0295 isOPF = isfield(results, 'f') && ~isempty(results.f);
0296 SUPPRESS = mpopt.out.suppress_detail;
0297 if SUPPRESS == -1
0298 if size(results.bus, 1) > 500
0299 SUPPRESS = 1;
0300 else
0301 SUPPRESS = 0;
0302 end
0303 end
0304 OUT_ALL = mpopt.out.all;
0305 OUT_FORCE = mpopt.out.force;
0306 OUT_RES = OUT_ALL == 1 || (OUT_ALL == -1 && ~SUPPRESS && (mpopt.out.bus || mpopt.out.gen));
0307
0308 if isOPF && OUT_RES && (results.success || OUT_FORCE)
0309 fprintf(fd, '\n================================================================================');
0310 fprintf(fd, '\n| Reserves |');
0311 fprintf(fd, '\n================================================================================');
0312 fprintf(fd, '\n Gen Bus Status Reserves Price');
0313 fprintf(fd, '\n # # (MW) ($/MW) Included in Zones ...');
0314 fprintf(fd, '\n---- ----- ------ -------- -------- ------------------------');
0315 for k = r.igr
0316 iz = find(r.zones(:, k));
0317 fprintf(fd, '\n%3d %6d %2d ', k, results.gen(k, GEN_BUS), results.gen(k, GEN_STATUS));
0318 if results.gen(k, GEN_STATUS) > 0 && abs(results.reserves.R(k)) > 1e-6
0319 fprintf(fd, '%10.2f', results.reserves.R(k));
0320 else
0321 fprintf(fd, ' - ');
0322 end
0323 fprintf(fd, '%10.2f ', results.reserves.prc(k));
0324 for i = 1:length(iz)
0325 if i ~= 1
0326 fprintf(fd, ', ');
0327 end
0328 fprintf(fd, '%d', iz(i));
0329 end
0330 end
0331 fprintf(fd, '\n --------');
0332 fprintf(fd, '\n Total:%10.2f Total Cost: $%.2f', ...
0333 sum(results.reserves.R(r.igr)), results.reserves.totalcost);
0334 fprintf(fd, '\n');
0335
0336 fprintf(fd, '\nZone Reserves Price ');
0337 fprintf(fd, '\n # (MW) ($/MW) ');
0338 fprintf(fd, '\n---- -------- --------');
0339 for k = 1:nrz
0340 iz = find(r.zones(k, :));
0341 fprintf(fd, '\n%3d%10.2f%10.2f', k, sum(results.reserves.R(iz)), ...
0342 results.lin.mu.l.Rreq(k) / results.baseMVA);
0343 end
0344 fprintf(fd, '\n');
0345
0346 fprintf(fd, '\n================================================================================');
0347 fprintf(fd, '\n| Reserve Limits |');
0348 fprintf(fd, '\n================================================================================');
0349 fprintf(fd, '\n Gen Bus Status Rmin mu Rmin Reserves Rmax Rmax mu Pmax mu ');
0350 fprintf(fd, '\n # # ($/MW) (MW) (MW) (MW) ($/MW) ($/MW) ');
0351 fprintf(fd, '\n---- ----- ------ -------- -------- -------- -------- -------- --------');
0352 for k = r.igr
0353 fprintf(fd, '\n%3d %6d %2d ', k, results.gen(k, GEN_BUS), results.gen(k, GEN_STATUS));
0354 if results.gen(k, GEN_STATUS) > 0 && results.reserves.mu.l(k) > 1e-6
0355 fprintf(fd, '%10.2f', results.reserves.mu.l(k));
0356 else
0357 fprintf(fd, ' - ');
0358 end
0359 fprintf(fd, '%10.2f', results.reserves.Rmin(k));
0360 if results.gen(k, GEN_STATUS) > 0 && abs(results.reserves.R(k)) > 1e-6
0361 fprintf(fd, '%10.2f', results.reserves.R(k));
0362 else
0363 fprintf(fd, ' - ');
0364 end
0365 fprintf(fd, '%10.2f', results.reserves.Rmax(k));
0366 if results.gen(k, GEN_STATUS) > 0 && results.reserves.mu.u(k) > 1e-6
0367 fprintf(fd, '%10.2f', results.reserves.mu.u(k));
0368 else
0369 fprintf(fd, ' - ');
0370 end
0371 if results.gen(k, GEN_STATUS) > 0 && results.reserves.mu.Pmax(k) > 1e-6
0372 fprintf(fd, '%10.2f', results.reserves.mu.Pmax(k));
0373 else
0374 fprintf(fd, ' - ');
0375 end
0376 end
0377 fprintf(fd, '\n --------');
0378 fprintf(fd, '\n Total:%10.2f', sum(results.reserves.R(r.igr)));
0379 fprintf(fd, '\n');
0380 end
0381
0382
0383
0384 function mpc = userfcn_reserves_savecase(mpc, fd, prefix, args)
0385
0386
0387
0388
0389
0390
0391
0392
0393 r = mpc.reserves;
0394
0395 fprintf(fd, '\n%%%%----- Reserve Data -----%%%%\n');
0396 fprintf(fd, '%%%% reserve zones, element i, j is 1 if gen j is in zone i, 0 otherwise\n');
0397 fprintf(fd, '%sreserves.zones = [\n', prefix);
0398 template = '';
0399 for i = 1:size(r.zones, 2)
0400 template = [template, '\t%d'];
0401 end
0402 template = [template, ';\n'];
0403 fprintf(fd, template, r.zones.');
0404 fprintf(fd, '];\n');
0405
0406 fprintf(fd, '\n%%%% reserve requirements for each zone in MW\n');
0407 fprintf(fd, '%sreserves.req = [\t%g', prefix, r.req(1));
0408 if length(r.req) > 1
0409 fprintf(fd, ';\t%g', r.req(2:end));
0410 end
0411 fprintf(fd, '\t];\n');
0412
0413 fprintf(fd, '\n%%%% reserve costs in $/MW for each gen that belongs to at least 1 zone\n');
0414 fprintf(fd, '%%%% (same order as gens, but skipping any gen that does not belong to any zone)\n');
0415 fprintf(fd, '%sreserves.cost = [\t%g', prefix, r.cost(1));
0416 if length(r.cost) > 1
0417 fprintf(fd, ';\t%g', r.cost(2:end));
0418 end
0419 fprintf(fd, '\t];\n');
0420
0421 if isfield(r, 'qty')
0422 fprintf(fd, '\n%%%% OPTIONAL max reserve quantities for each gen that belongs to at least 1 zone\n');
0423 fprintf(fd, '%%%% (same order as gens, but skipping any gen that does not belong to any zone)\n');
0424 fprintf(fd, '%sreserves.qty = [\t%g', prefix, r.qty(1));
0425 if length(r.qty) > 1
0426 fprintf(fd, ';\t%g', r.qty(2:end));
0427 end
0428 fprintf(fd, '\t];\n');
0429 end
0430
0431
0432 if isfield(r, 'R')
0433 if exist('serialize', 'file') == 2
0434 fprintf(fd, '\n%%%% solved values\n');
0435 fprintf(fd, '%sreserves.R = %s\n', prefix, serialize(r.R));
0436 fprintf(fd, '%sreserves.Rmin = %s\n', prefix, serialize(r.Rmin));
0437 fprintf(fd, '%sreserves.Rmax = %s\n', prefix, serialize(r.Rmax));
0438 fprintf(fd, '%sreserves.mu.l = %s\n', prefix, serialize(r.mu.l));
0439 fprintf(fd, '%sreserves.mu.u = %s\n', prefix, serialize(r.mu.u));
0440 fprintf(fd, '%sreserves.prc = %s\n', prefix, serialize(r.prc));
0441 fprintf(fd, '%sreserves.totalcost = %s\n', prefix, serialize(r.totalcost));
0442 else
0443 url = 'https://www.mathworks.com/matlabcentral/fileexchange/12063-serialize';
0444 warning('MATPOWER:serialize', ...
0445 'userfcn_reserves_savecase: Cannot save the ''reserves'' output fields without the ''serialize'' function, which is available as a free download from:\n<%s>\n\n', url);
0446 end
0447 end