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