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