AUCTION Clear auction based on OPF results (qty's and lambdas). [CO, CB] = AUCTION(OFFERS, BIDS, AUCTION_TYPE, LIMIT_PRC, GTEE_PRC) Clears a set of BIDS and OFFERS based on the results of an OPF, where the pricing is adjusted for network losses and binding constraints. The arguments OFFERS and BIDS are structs with the following fields: qty - m x n, offer/bid quantities, m offers/bids, n blocks prc - m x n, offer/bid prices lam - m x n, corresponding lambdas total_qty - m x 1, total quantity cleared for each offer/bid There are 8 types of auctions implemented, specified by AUCTION_TYPE. 0 - discriminative pricing (price equal to offer or bid) 1 - last accepted offer auction 2 - first rejected offer auction 3 - last accepted bid auction 4 - first rejected bid auction 5 - first price auction (marginal unit, offer or bid, sets the price) 6 - second price auction (if offer is marginal, price set by min(FRO,LAB), else max(FRB,LAO) 7 - split the difference pricing (set by last accepted offer & bid) 8 - LAO sets seller price, LAB sets buyer price Whether or not cleared offer (bid) prices are guaranteed to be greater (less) than or equal to the corresponding offer (bid) price is specified by a flag GTEE_PRC.offer (GTEE_PRC.bid). The default is value true. Offer/bid and cleared offer/bid min and max prices are specified in the LIMIT_PRC struct with the following fields: max_offer min_bid max_cleared_offer min_cleared_bid Offers (bids) above (below) max_offer (min_bid) are treated as withheld and cleared offer (bid) prices above (below) max_cleared_offer (min_cleared_bid) are clipped to max_cleared offer (min_cleared_bid) if given. All of these limit prices are ignored if the field is missing or is empty. See also RUNMARKET, SMARTMKT.
0001 function [co, cb] = auction(offers, bids, auction_type, limit_prc, gtee_prc) 0002 %AUCTION Clear auction based on OPF results (qty's and lambdas). 0003 % [CO, CB] = AUCTION(OFFERS, BIDS, AUCTION_TYPE, LIMIT_PRC, GTEE_PRC) 0004 % Clears a set of BIDS and OFFERS based on the results of an OPF, where the 0005 % pricing is adjusted for network losses and binding constraints. 0006 % The arguments OFFERS and BIDS are structs with the following fields: 0007 % qty - m x n, offer/bid quantities, m offers/bids, n blocks 0008 % prc - m x n, offer/bid prices 0009 % lam - m x n, corresponding lambdas 0010 % total_qty - m x 1, total quantity cleared for each offer/bid 0011 % 0012 % There are 8 types of auctions implemented, specified by AUCTION_TYPE. 0013 % 0014 % 0 - discriminative pricing (price equal to offer or bid) 0015 % 1 - last accepted offer auction 0016 % 2 - first rejected offer auction 0017 % 3 - last accepted bid auction 0018 % 4 - first rejected bid auction 0019 % 5 - first price auction (marginal unit, offer or bid, sets the price) 0020 % 6 - second price auction (if offer is marginal, price set by 0021 % min(FRO,LAB), else max(FRB,LAO) 0022 % 7 - split the difference pricing (set by last accepted offer & bid) 0023 % 8 - LAO sets seller price, LAB sets buyer price 0024 % 0025 % Whether or not cleared offer (bid) prices are guaranteed to be greater 0026 % (less) than or equal to the corresponding offer (bid) price is specified by 0027 % a flag GTEE_PRC.offer (GTEE_PRC.bid). The default is value true. 0028 % 0029 % Offer/bid and cleared offer/bid min and max prices are specified in the 0030 % LIMIT_PRC struct with the following fields: 0031 % max_offer 0032 % min_bid 0033 % max_cleared_offer 0034 % min_cleared_bid 0035 % Offers (bids) above (below) max_offer (min_bid) are treated as withheld 0036 % and cleared offer (bid) prices above (below) max_cleared_offer 0037 % (min_cleared_bid) are clipped to max_cleared offer (min_cleared_bid) if 0038 % given. All of these limit prices are ignored if the field is missing 0039 % or is empty. 0040 % 0041 % See also RUNMARKET, SMARTMKT. 0042 0043 % MATPOWER 0044 % Copyright (c) 1996-2016, Power Systems Engineering Research Center (PSERC) 0045 % by Ray Zimmerman, PSERC Cornell 0046 % 0047 % This file is part of MATPOWER Extras. 0048 % Covered by the 3-clause BSD License (see LICENSE file for details). 0049 % See https://github.com/MATPOWER/matpower-extras for more info. 0050 0051 %%----- initialization ----- 0052 %% define named indices into data matrices 0053 [PQ, PV, REF, NONE, BUS_I, BUS_TYPE, PD, QD, GS, BS, BUS_AREA, VM, ... 0054 VA, BASE_KV, ZONE, VMAX, VMIN, LAM_P, LAM_Q, MU_VMAX, MU_VMIN] = idx_bus; 0055 [GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ... 0056 MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ... 0057 QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen; 0058 0059 %% initialize some stuff 0060 delta = 1e-3; %% prices smaller than this are not used to determine X 0061 zero_tol = 1e-5; 0062 % zero_tol = 0.1; %% fmincon is SO bad with prices that it is 0063 %% NOT recommended for use with auction.m 0064 big_num = 1e6; 0065 if isempty(bids) 0066 bids = struct( 'qty', [], ... 0067 'prc', [], ... 0068 'lam', [], ... 0069 'total_qty', [] ); 0070 end 0071 if nargin < 4 || isempty(limit_prc) 0072 limit_prc = struct( 'max_offer', [], 'min_bid', [], ... 0073 'max_cleared_offer', [], 'min_cleared_bid', [] ); 0074 else 0075 if ~isfield(limit_prc, 'max_offer'), limit_prc.max_offer = []; end 0076 if ~isfield(limit_prc, 'min_bid'), limit_prc.min_bid = []; end 0077 if ~isfield(limit_prc, 'max_cleared_offer'), limit_prc.max_cleared_offer = []; end 0078 if ~isfield(limit_prc, 'min_cleared_bid'), limit_prc.min_cleared_bid = []; end 0079 end 0080 if nargin < 5 || isempty(gtee_prc) 0081 gtee_prc = struct( 'offer', 1, 'bid', 1 ); 0082 else 0083 if ~isfield(gtee_prc, 'offer'), gtee_prc.offer = 1; end 0084 if ~isfield(gtee_prc, 'bid'), gtee_prc.bid = 1; end 0085 end 0086 0087 [nro, nco] = size(offers.qty); 0088 [nrb, ncb] = size(bids.qty); 0089 0090 %% determine cleared quantities 0091 if isempty(limit_prc.max_offer) 0092 [co.qty, o.on, o.off] = clear_qty(offers.qty, offers.total_qty); 0093 else 0094 mask = offers.prc <= limit_prc.max_offer; 0095 [co.qty, o.on, o.off] = clear_qty(offers.qty, offers.total_qty, mask); 0096 end 0097 if isempty(limit_prc.min_bid) 0098 [cb.qty, b.on, b.off] = clear_qty(bids.qty, bids.total_qty); 0099 else 0100 mask = bids.prc <= limit_prc.min_bid; 0101 [cb.qty, b.on, b.off] = clear_qty(bids.qty, bids.total_qty, mask); 0102 end 0103 0104 %% initialize cleared prices 0105 co.prc = zeros(nro, nco); %% cleared offer prices 0106 cb.prc = zeros(nrb, ncb); %% cleared bid prices 0107 0108 %%----- compute exchange rates to scale lam to get desired pricing ----- 0109 %% The locationally adjusted offer/bid price, when normalized to an arbitrary 0110 %% reference location where lambda is equal to ref_lam, is: 0111 %% norm_prc = prc * (ref_lam / lam) 0112 %% Then we can define the ratio between the normalized offer/bid prices 0113 %% and the ref_lam as an exchange rate X: 0114 %% X = norm_prc / ref_lam = prc / lam 0115 %% This X represents the ratio between the marginal unit (setting lambda) 0116 %% and the offer/bid price in question. 0117 0118 if auction_type == 0 || auction_type == 5 %% don't bother scaling anything 0119 X = struct( 'LAO', 1, ... 0120 'FRO', 1, ... 0121 'LAB', 1, ... 0122 'FRB', 1); 0123 else 0124 X = compute_exchange_rates(offers, bids, o, b); 0125 end 0126 0127 %% cleared offer/bid prices for different auction types 0128 if auction_type == 0 %% discriminative 0129 co.prc = offers.prc; 0130 cb.prc = bids.prc; 0131 elseif auction_type == 1 %% LAO 0132 co.prc = offers.lam * X.LAO; 0133 cb.prc = bids.lam * X.LAO; 0134 elseif auction_type == 2 %% FRO 0135 co.prc = offers.lam * X.FRO; 0136 cb.prc = bids.lam * X.FRO; 0137 elseif auction_type == 3 %% LAB 0138 co.prc = offers.lam * X.LAB; 0139 cb.prc = bids.lam * X.LAB; 0140 elseif auction_type == 4 %% FRB 0141 co.prc = offers.lam * X.FRB; 0142 cb.prc = bids.lam * X.FRB; 0143 elseif auction_type == 5 %% 1st price 0144 co.prc = offers.lam; 0145 cb.prc = bids.lam; 0146 elseif auction_type == 6 %% 2nd price 0147 if abs(1 - X.LAO) < zero_tol 0148 co.prc = offers.lam * min(X.FRO,X.LAB); 0149 cb.prc = bids.lam * min(X.FRO,X.LAB); 0150 else 0151 co.prc = offers.lam * max(X.LAO,X.FRB); 0152 cb.prc = bids.lam * max(X.LAO,X.FRB); 0153 end 0154 elseif auction_type == 7 %% split the difference 0155 co.prc = offers.lam * (X.LAO + X.LAB) / 2; 0156 cb.prc = bids.lam * (X.LAO + X.LAB) / 2; 0157 elseif auction_type == 8 %% LAO seller, LAB buyer 0158 co.prc = offers.lam * X.LAO; 0159 cb.prc = bids.lam * X.LAB; 0160 end 0161 0162 %% guarantee that cleared offer prices are >= offers 0163 if gtee_prc.offer 0164 clip = o.on .* (offers.prc - co.prc); 0165 co.prc = co.prc + (clip > zero_tol) .* clip; 0166 end 0167 0168 %% guarantee that cleared bid prices are <= bids 0169 if gtee_prc.bid 0170 clip = b.on .* (bids.prc - cb.prc); 0171 cb.prc = cb.prc + (clip < -zero_tol) .* clip; 0172 end 0173 0174 %% clip cleared offer prices by limit_prc.max_cleared_offer 0175 if ~isempty(limit_prc.max_cleared_offer) 0176 co.prc = co.prc + (co.prc > limit_prc.max_cleared_offer) .* ... 0177 (limit_prc.max_cleared_offer - co.prc); 0178 end 0179 0180 %% clip cleared bid prices by limit_prc.min_cleared_bid 0181 if ~isempty(limit_prc.min_cleared_bid) 0182 cb.prc = cb.prc + (cb.prc < limit_prc.min_cleared_bid) .* ... 0183 (limit_prc.min_cleared_bid - cb.prc); 0184 end 0185 0186 %% make prices uniform after clipping (except for discrim auction) 0187 %% since clipping may only affect a single block of a multi-block generator 0188 if auction_type ~= 0 0189 %% equal to largest price in row 0190 if nco > 1 0191 co.prc = diag(max(co.prc')) * ones(nro,nco); 0192 end 0193 if ncb > 1 0194 cb.prc = diag(min(cb.prc')) * ones(nrb,ncb); 0195 end 0196 end 0197 0198 0199 function X = compute_exchange_rates(offers, bids, o, b, delta) 0200 %COMPUTE_EXCHANGE_RATES Determine the scale factors for LAO, FRO, LAB, FRB 0201 % Inputs: 0202 % offers, bids (same as for auction) 0203 % o, b - structs with on, off fields, each same dim as qty field of offers 0204 % or bids, 1 if corresponding block is accepted, 0 otherwise 0205 % delta - optional prices smaller than this are not used to determine X 0206 % Outputs: 0207 % X - struct with fields LAO, FRO, LAB, FRB containing scale factors 0208 % to use for each type of auction 0209 0210 if nargin < 5 0211 delta = 1e-3; %% prices smaller than this are not used to determine X 0212 end 0213 zero_tol = 1e-5; 0214 0215 %% eliminate terms with lam < delta (X would not be accurate) 0216 olam = offers.lam; 0217 blam = bids.lam; 0218 olam(olam(:) < delta) = NaN; 0219 blam(blam(:) < delta) = NaN; 0220 0221 %% eliminate rows for 0 qty offers/bids 0222 [nro, nco] = size(offers.qty); 0223 [nrb, ncb] = size(bids.qty); 0224 omask = ones(nro,nco); 0225 if nco == 1 0226 temp = offers.qty; 0227 else 0228 temp = sum(offers.qty')'; 0229 end 0230 omask(temp == 0, :) = NaN; 0231 bmask = ones(nrb,ncb); 0232 if ncb == 1 0233 temp = bids.qty; 0234 else 0235 temp = sum(bids.qty')'; 0236 end 0237 bmask(temp == 0, :) = NaN; 0238 0239 %% by default, don't scale anything 0240 X.LAO = 1; 0241 X.FRO = 1; 0242 X.LAB = 1; 0243 X.FRB = 1; 0244 0245 %% don't scale if we have any negative lambdas or all are too close to 0 0246 if all(all(offers.lam > -zero_tol)) 0247 %% ratios 0248 Xo = omask .* offers.prc ./ olam; 0249 Xb = bmask .* bids.prc ./ blam; 0250 0251 %% exchange rate for LAO (X.LAO * lambda == LAO, for corresponding lambda) 0252 X.LAO = o.on .* Xo; 0253 X.LAO( o.off(:) ) = NaN; 0254 X.LAO( X.LAO(:) > 1+zero_tol ) = NaN; %% don't let gens @ Pmin set price 0255 X.LAO = max( X.LAO(:) ); 0256 0257 %% exchange rate for FRO (X.FRO * lambda == FRO, for corresponding lambda) 0258 X.FRO = o.off .* Xo; 0259 X.FRO( o.on(:) ) = NaN; 0260 X.FRO = min( X.FRO(:) ); 0261 0262 if nrb 0263 %% exchange rate for LAB (X.LAB * lambda == LAB, for corresponding lambda) 0264 X.LAB = b.on .* Xb; 0265 X.LAB( b.off(:) ) = NaN; 0266 X.LAB( X.LAB(:) < 1-zero_tol ) = NaN; %% don't let set price 0267 X.LAB = min( X.LAB(:) ); 0268 0269 %% exchange rate for FRB (X.FRB * lambda == FRB, for corresponding lambda) 0270 X.FRB = b.off .* Xb; 0271 X.FRB( b.on(:) ) = NaN; 0272 X.FRB = max( X.FRB(:) ); 0273 end 0274 end 0275 0276 0277 function [cqty, on, off] = clear_qty(qty, total_cqty, mask) 0278 %CLEAR_QTY Computed cleared offer/bid quantities from totals. 0279 % Inputs: 0280 % qty - m x n, offer/bid quantities, m offers/bids, n blocks 0281 % total_cqty - m x 1, total cleared quantity for each offer/bid 0282 % mask - m x n, boolean indicating which offers/bids are valid (not withheld) 0283 % Outputs: 0284 % cqty - m x n, cleared offer/bid quantities, m offers/bids, n blocks 0285 % on - m x n, 1 if partially or fully accepted, 0 if rejected 0286 % off - m x n, 1 if rejected, 0 if partially or fully accepted 0287 0288 [nr, nc] = size(qty); 0289 accept = zeros(nr,nc); 0290 cqty = zeros(nr,nc); 0291 for i = 1:nr %% offer/bid i 0292 for j = 1:nc %% block j 0293 if qty(i, j) %% ignore zero quantity offers/bids 0294 %% compute fraction of the block accepted ... 0295 accept(i, j) = (total_cqty(i) - sum(qty(i, 1:j-1))) / qty(i, j); 0296 %% ... clipped to the range [0, 1] (i.e. 0-100%) 0297 if accept(i, j) > 1 0298 accept(i, j) = 1; 0299 elseif accept(i, j) < 1.0e-5 0300 accept(i, j) = 0; 0301 end 0302 cqty(i, j) = qty(i, j) * accept(i, j); 0303 end 0304 end 0305 end 0306 0307 if nargin == 3 0308 accept = mask .* accept; 0309 end 0310 0311 on = (accept > 0); 0312 off = (accept == 0);