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 % $Id: auction.m 2194 2013-09-03 19:12:26Z ray $ 0045 % by Ray Zimmerman, PSERC Cornell 0046 % Copyright (c) 1996-2010 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 %%----- initialization ----- 0073 %% define named indices into data matrices 0074 [PQ, PV, REF, NONE, BUS_I, BUS_TYPE, PD, QD, GS, BS, BUS_AREA, VM, ... 0075 VA, BASE_KV, ZONE, VMAX, VMIN, LAM_P, LAM_Q, MU_VMAX, MU_VMIN] = idx_bus; 0076 [GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ... 0077 MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ... 0078 QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen; 0079 0080 %% initialize some stuff 0081 delta = 1e-3; %% prices smaller than this are not used to determine X 0082 zero_tol = 1e-5; 0083 % zero_tol = 0.1; %% fmincon is SO bad with prices that it is 0084 %% NOT recommended for use with auction.m 0085 big_num = 1e6; 0086 if isempty(bids) 0087 bids = struct( 'qty', [], ... 0088 'prc', [], ... 0089 'lam', [], ... 0090 'total_qty', [] ); 0091 end 0092 if nargin < 4 || isempty(limit_prc) 0093 limit_prc = struct( 'max_offer', [], 'min_bid', [], ... 0094 'max_cleared_offer', [], 'min_cleared_bid', [] ); 0095 else 0096 if ~isfield(limit_prc, 'max_offer'), limit_prc.max_offer = []; end 0097 if ~isfield(limit_prc, 'min_bid'), limit_prc.min_bid = []; end 0098 if ~isfield(limit_prc, 'max_cleared_offer'), limit_prc.max_cleared_offer = []; end 0099 if ~isfield(limit_prc, 'min_cleared_bid'), limit_prc.min_cleared_bid = []; end 0100 end 0101 if nargin < 5 || isempty(gtee_prc) 0102 gtee_prc = struct( 'offer', 1, 'bid', 1 ); 0103 else 0104 if ~isfield(gtee_prc, 'offer'), gtee_prc.offer = 1; end 0105 if ~isfield(gtee_prc, 'bid'), gtee_prc.bid = 1; end 0106 end 0107 0108 [nro, nco] = size(offers.qty); 0109 [nrb, ncb] = size(bids.qty); 0110 0111 %% determine cleared quantities 0112 if isempty(limit_prc.max_offer) 0113 [co.qty, o.on, o.off] = clear_qty(offers.qty, offers.total_qty); 0114 else 0115 mask = offers.prc <= limit_prc.max_offer; 0116 [co.qty, o.on, o.off] = clear_qty(offers.qty, offers.total_qty, mask); 0117 end 0118 if isempty(limit_prc.min_bid) 0119 [cb.qty, b.on, b.off] = clear_qty(bids.qty, bids.total_qty); 0120 else 0121 mask = bids.prc <= limit_prc.min_bid; 0122 [cb.qty, b.on, b.off] = clear_qty(bids.qty, bids.total_qty, mask); 0123 end 0124 0125 %% initialize cleared prices 0126 co.prc = zeros(nro, nco); %% cleared offer prices 0127 cb.prc = zeros(nrb, ncb); %% cleared bid prices 0128 0129 %%----- compute exchange rates to scale lam to get desired pricing ----- 0130 %% The locationally adjusted offer/bid price, when normalized to an arbitrary 0131 %% reference location where lambda is equal to ref_lam, is: 0132 %% norm_prc = prc * (ref_lam / lam) 0133 %% Then we can define the ratio between the normalized offer/bid prices 0134 %% and the ref_lam as an exchange rate X: 0135 %% X = norm_prc / ref_lam = prc / lam 0136 %% This X represents the ratio between the marginal unit (setting lambda) 0137 %% and the offer/bid price in question. 0138 0139 if auction_type == 0 || auction_type == 5 %% don't bother scaling anything 0140 X = struct( 'LAO', 1, ... 0141 'FRO', 1, ... 0142 'LAB', 1, ... 0143 'FRB', 1); 0144 else 0145 X = compute_exchange_rates(offers, bids, o, b); 0146 end 0147 0148 %% cleared offer/bid prices for different auction types 0149 if auction_type == 0 %% discriminative 0150 co.prc = offers.prc; 0151 cb.prc = bids.prc; 0152 elseif auction_type == 1 %% LAO 0153 co.prc = offers.lam * X.LAO; 0154 cb.prc = bids.lam * X.LAO; 0155 elseif auction_type == 2 %% FRO 0156 co.prc = offers.lam * X.FRO; 0157 cb.prc = bids.lam * X.FRO; 0158 elseif auction_type == 3 %% LAB 0159 co.prc = offers.lam * X.LAB; 0160 cb.prc = bids.lam * X.LAB; 0161 elseif auction_type == 4 %% FRB 0162 co.prc = offers.lam * X.FRB; 0163 cb.prc = bids.lam * X.FRB; 0164 elseif auction_type == 5 %% 1st price 0165 co.prc = offers.lam; 0166 cb.prc = bids.lam; 0167 elseif auction_type == 6 %% 2nd price 0168 if abs(1 - X.LAO) < zero_tol 0169 co.prc = offers.lam * min(X.FRO,X.LAB); 0170 cb.prc = bids.lam * min(X.FRO,X.LAB); 0171 else 0172 co.prc = offers.lam * max(X.LAO,X.FRB); 0173 cb.prc = bids.lam * max(X.LAO,X.FRB); 0174 end 0175 elseif auction_type == 7 %% split the difference 0176 co.prc = offers.lam * (X.LAO + X.LAB) / 2; 0177 cb.prc = bids.lam * (X.LAO + X.LAB) / 2; 0178 elseif auction_type == 8 %% LAO seller, LAB buyer 0179 co.prc = offers.lam * X.LAO; 0180 cb.prc = bids.lam * X.LAB; 0181 end 0182 0183 %% guarantee that cleared offer prices are >= offers 0184 if gtee_prc.offer 0185 clip = o.on .* (offers.prc - co.prc); 0186 co.prc = co.prc + (clip > zero_tol) .* clip; 0187 end 0188 0189 %% guarantee that cleared bid prices are <= bids 0190 if gtee_prc.bid 0191 clip = b.on .* (bids.prc - cb.prc); 0192 cb.prc = cb.prc + (clip < -zero_tol) .* clip; 0193 end 0194 0195 %% clip cleared offer prices by limit_prc.max_cleared_offer 0196 if ~isempty(limit_prc.max_cleared_offer) 0197 co.prc = co.prc + (co.prc > limit_prc.max_cleared_offer) .* ... 0198 (limit_prc.max_cleared_offer - co.prc); 0199 end 0200 0201 %% clip cleared bid prices by limit_prc.min_cleared_bid 0202 if ~isempty(limit_prc.min_cleared_bid) 0203 cb.prc = cb.prc + (cb.prc < limit_prc.min_cleared_bid) .* ... 0204 (limit_prc.min_cleared_bid - cb.prc); 0205 end 0206 0207 %% make prices uniform after clipping (except for discrim auction) 0208 %% since clipping may only affect a single block of a multi-block generator 0209 if auction_type ~= 0 0210 %% equal to largest price in row 0211 if nco > 1 0212 co.prc = diag(max(co.prc')) * ones(nro,nco); 0213 end 0214 if ncb > 1 0215 cb.prc = diag(min(cb.prc')) * ones(nrb,ncb); 0216 end 0217 end 0218 0219 0220 function X = compute_exchange_rates(offers, bids, o, b, delta) 0221 %COMPUTE_EXCHANGE_RATES Determine the scale factors for LAO, FRO, LAB, FRB 0222 % Inputs: 0223 % offers, bids (same as for auction) 0224 % o, b - structs with on, off fields, each same dim as qty field of offers 0225 % or bids, 1 if corresponding block is accepted, 0 otherwise 0226 % delta - optional prices smaller than this are not used to determine X 0227 % Outputs: 0228 % X - struct with fields LAO, FRO, LAB, FRB containing scale factors 0229 % to use for each type of auction 0230 0231 if nargin < 5 0232 delta = 1e-3; %% prices smaller than this are not used to determine X 0233 end 0234 zero_tol = 1e-5; 0235 0236 %% eliminate terms with lam < delta (X would not be accurate) 0237 olam = offers.lam; 0238 blam = bids.lam; 0239 olam(olam(:) < delta) = NaN; 0240 blam(blam(:) < delta) = NaN; 0241 0242 %% eliminate rows for 0 qty offers/bids 0243 [nro, nco] = size(offers.qty); 0244 [nrb, ncb] = size(bids.qty); 0245 omask = ones(nro,nco); 0246 if nco == 1 0247 temp = offers.qty; 0248 else 0249 temp = sum(offers.qty')'; 0250 end 0251 omask(temp == 0, :) = NaN; 0252 bmask = ones(nrb,ncb); 0253 if ncb == 1 0254 temp = bids.qty; 0255 else 0256 temp = sum(bids.qty')'; 0257 end 0258 bmask(temp == 0, :) = NaN; 0259 0260 %% by default, don't scale anything 0261 X.LAO = 1; 0262 X.FRO = 1; 0263 X.LAB = 1; 0264 X.FRB = 1; 0265 0266 %% don't scale if we have any negative lambdas or all are too close to 0 0267 if all(all(offers.lam > -zero_tol)) 0268 %% ratios 0269 Xo = omask .* offers.prc ./ olam; 0270 Xb = bmask .* bids.prc ./ blam; 0271 0272 %% exchange rate for LAO (X.LAO * lambda == LAO, for corresponding lambda) 0273 X.LAO = o.on .* Xo; 0274 X.LAO( o.off(:) ) = NaN; 0275 X.LAO( X.LAO(:) > 1+zero_tol ) = NaN; %% don't let gens @ Pmin set price 0276 X.LAO = max( X.LAO(:) ); 0277 0278 %% exchange rate for FRO (X.FRO * lambda == FRO, for corresponding lambda) 0279 X.FRO = o.off .* Xo; 0280 X.FRO( o.on(:) ) = NaN; 0281 X.FRO = min( X.FRO(:) ); 0282 0283 if nrb 0284 %% exchange rate for LAB (X.LAB * lambda == LAB, for corresponding lambda) 0285 X.LAB = b.on .* Xb; 0286 X.LAB( b.off(:) ) = NaN; 0287 X.LAB( X.LAB(:) < 1-zero_tol ) = NaN; %% don't let set price 0288 X.LAB = min( X.LAB(:) ); 0289 0290 %% exchange rate for FRB (X.FRB * lambda == FRB, for corresponding lambda) 0291 X.FRB = b.off .* Xb; 0292 X.FRB( b.on(:) ) = NaN; 0293 X.FRB = max( X.FRB(:) ); 0294 end 0295 end 0296 0297 0298 function [cqty, on, off] = clear_qty(qty, total_cqty, mask) 0299 %CLEAR_QTY Computed cleared offer/bid quantities from totals. 0300 % Inputs: 0301 % qty - m x n, offer/bid quantities, m offers/bids, n blocks 0302 % total_cqty - m x 1, total cleared quantity for each offer/bid 0303 % mask - m x n, boolean indicating which offers/bids are valid (not withheld) 0304 % Outputs: 0305 % cqty - m x n, cleared offer/bid quantities, m offers/bids, n blocks 0306 % on - m x n, 1 if partially or fully accepted, 0 if rejected 0307 % off - m x n, 1 if rejected, 0 if partially or fully accepted 0308 0309 [nr, nc] = size(qty); 0310 accept = zeros(nr,nc); 0311 cqty = zeros(nr,nc); 0312 for i = 1:nr %% offer/bid i 0313 for j = 1:nc %% block j 0314 if qty(i, j) %% ignore zero quantity offers/bids 0315 %% compute fraction of the block accepted ... 0316 accept(i, j) = (total_cqty(i) - sum(qty(i, 1:j-1))) / qty(i, j); 0317 %% ... clipped to the range [0, 1] (i.e. 0-100%) 0318 if accept(i, j) > 1 0319 accept(i, j) = 1; 0320 elseif accept(i, j) < 1.0e-5 0321 accept(i, j) = 0; 0322 end 0323 cqty(i, j) = qty(i, j) * accept(i, j); 0324 end 0325 end 0326 end 0327 0328 if nargin == 3 0329 accept = mask .* accept; 0330 end 0331 0332 on = (accept > 0); 0333 off = (accept == 0);