function [co, cb] = auction(offers, bids, auction_type, limit_prc, gtee_prc)
%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.

%   MATPOWER
%   Copyright (c) 1996-2016 by Power System Engineering Research Center (PSERC)
%   by Ray Zimmerman, PSERC Cornell
%
%   This file is part of MATPOWER.
%   Covered by the 3-clause BSD License (see LICENSE file for details).
%   See http://www.pserc.cornell.edu/matpower/ for more info.

%%-----  initialization  -----
%% define named indices into data matrices
[PQ, PV, REF, NONE, BUS_I, BUS_TYPE, PD, QD, GS, BS, BUS_AREA, VM, ...
    VA, BASE_KV, ZONE, VMAX, VMIN, LAM_P, LAM_Q, MU_VMAX, MU_VMIN] = idx_bus;
[GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ...
    MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ...
    QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen;

%% initialize some stuff
delta = 1e-3;       %% prices smaller than this are not used to determine X
zero_tol = 1e-5;
% zero_tol = 0.1;   %% fmincon is SO bad with prices that it is
                    %% NOT recommended for use with auction.m
big_num = 1e6;
if isempty(bids)
    bids = struct(  'qty', [], ...
                    'prc', [], ...
                    'lam', [], ...
                    'total_qty', [] );
end
if nargin < 4 || isempty(limit_prc)
    limit_prc = struct( 'max_offer', [], 'min_bid', [], ...
                        'max_cleared_offer', [], 'min_cleared_bid', [] );
else
    if ~isfield(limit_prc, 'max_offer'),         limit_prc.max_offer = [];         end
    if ~isfield(limit_prc, 'min_bid'),           limit_prc.min_bid = [];           end
    if ~isfield(limit_prc, 'max_cleared_offer'), limit_prc.max_cleared_offer = []; end
    if ~isfield(limit_prc, 'min_cleared_bid'),   limit_prc.min_cleared_bid = [];   end
end
if nargin < 5 || isempty(gtee_prc)
    gtee_prc = struct(  'offer', 1, 'bid', 1    );
else
    if ~isfield(gtee_prc, 'offer'), gtee_prc.offer = 1; end
    if ~isfield(gtee_prc, 'bid'),   gtee_prc.bid = 1;   end
end

[nro, nco] = size(offers.qty);
[nrb, ncb] = size(bids.qty);

%% determine cleared quantities
if isempty(limit_prc.max_offer)
    [co.qty, o.on, o.off] = clear_qty(offers.qty, offers.total_qty);
else
    mask = offers.prc <= limit_prc.max_offer;
    [co.qty, o.on, o.off] = clear_qty(offers.qty, offers.total_qty, mask);
end
if isempty(limit_prc.min_bid)
    [cb.qty, b.on, b.off] = clear_qty(bids.qty, bids.total_qty);
else
    mask = bids.prc <= limit_prc.min_bid;
    [cb.qty, b.on, b.off] = clear_qty(bids.qty, bids.total_qty, mask);
end

%% initialize cleared prices
co.prc = zeros(nro, nco);       %% cleared offer prices
cb.prc = zeros(nrb, ncb);       %% cleared bid prices

%%-----  compute exchange rates to scale lam to get desired pricing  -----
%% The locationally adjusted offer/bid price, when normalized to an arbitrary
%% reference location where lambda is equal to ref_lam, is:
%%      norm_prc = prc * (ref_lam / lam)
%% Then we can define the ratio between the normalized offer/bid prices
%% and the ref_lam as an exchange rate X:
%%      X = norm_prc / ref_lam = prc / lam
%% This X represents the ratio between the marginal unit (setting lambda)
%% and the offer/bid price in question.

if auction_type == 0 || auction_type == 5   %% don't bother scaling anything
    X = struct( 'LAO', 1, ...
                'FRO', 1, ...
                'LAB', 1, ...
                'FRB', 1);
else
    X = compute_exchange_rates(offers, bids, o, b);
end

%% cleared offer/bid prices for different auction types
if auction_type == 0        %% discriminative
    co.prc = offers.prc;
    cb.prc = bids.prc;
elseif auction_type == 1    %% LAO
    co.prc = offers.lam * X.LAO;
    cb.prc = bids.lam   * X.LAO;
elseif auction_type == 2    %% FRO
    co.prc = offers.lam * X.FRO;
    cb.prc = bids.lam   * X.FRO;
elseif auction_type == 3    %% LAB
    co.prc = offers.lam * X.LAB;
    cb.prc = bids.lam   * X.LAB;
elseif auction_type == 4    %% FRB
    co.prc = offers.lam * X.FRB;
    cb.prc = bids.lam   * X.FRB;
elseif auction_type == 5    %% 1st price
    co.prc = offers.lam;
    cb.prc = bids.lam;
elseif auction_type == 6    %% 2nd price
    if abs(1 - X.LAO) < zero_tol
        co.prc = offers.lam * min(X.FRO,X.LAB);
        cb.prc = bids.lam   * min(X.FRO,X.LAB);
    else
        co.prc = offers.lam * max(X.LAO,X.FRB);
        cb.prc = bids.lam   * max(X.LAO,X.FRB);
    end
elseif auction_type == 7    %% split the difference
    co.prc = offers.lam * (X.LAO + X.LAB) / 2;
    cb.prc = bids.lam   * (X.LAO + X.LAB) / 2;
elseif auction_type == 8    %% LAO seller, LAB buyer
    co.prc = offers.lam * X.LAO;
    cb.prc = bids.lam   * X.LAB;
end

%% guarantee that cleared offer prices are >= offers
if gtee_prc.offer
    clip = o.on .* (offers.prc - co.prc);
    co.prc = co.prc + (clip > zero_tol) .* clip;
end

%% guarantee that cleared bid prices are <= bids
if gtee_prc.bid
    clip = b.on .* (bids.prc - cb.prc);
    cb.prc = cb.prc + (clip < -zero_tol) .* clip;
end

%% clip cleared offer prices by limit_prc.max_cleared_offer
if ~isempty(limit_prc.max_cleared_offer)
    co.prc = co.prc + (co.prc > limit_prc.max_cleared_offer) .* ...
        (limit_prc.max_cleared_offer - co.prc);
end

%% clip cleared bid prices by limit_prc.min_cleared_bid
if ~isempty(limit_prc.min_cleared_bid)
    cb.prc = cb.prc + (cb.prc < limit_prc.min_cleared_bid) .* ...
        (limit_prc.min_cleared_bid - cb.prc);
end

%% make prices uniform after clipping (except for discrim auction)
%% since clipping may only affect a single block of a multi-block generator
if auction_type ~= 0
    %% equal to largest price in row
    if nco > 1
        co.prc = diag(max(co.prc')) * ones(nro,nco);
    end
    if ncb > 1
        cb.prc = diag(min(cb.prc')) * ones(nrb,ncb);
    end
end


function X = compute_exchange_rates(offers, bids, o, b, delta)
%COMPUTE_EXCHANGE_RATES Determine the scale factors for LAO, FRO, LAB, FRB
%  Inputs:
%   offers, bids (same as for auction)
%   o, b  - structs with on, off fields, each same dim as qty field of offers
%           or bids, 1 if corresponding block is accepted, 0 otherwise
%   delta - optional prices smaller than this are not used to determine X
%  Outputs:
%   X     - struct with fields LAO, FRO, LAB, FRB containing scale factors
%           to use for each type of auction

if nargin < 5
    delta = 1e-3;       %% prices smaller than this are not used to determine X
end
zero_tol = 1e-5;

%% eliminate terms with lam < delta (X would not be accurate)
olam = offers.lam;
blam = bids.lam;
olam(olam(:) < delta) = NaN;
blam(blam(:) < delta) = NaN;

%% eliminate rows for 0 qty offers/bids
[nro, nco] = size(offers.qty);
[nrb, ncb] = size(bids.qty);
omask = ones(nro,nco);
if nco == 1
    temp = offers.qty;
else
    temp = sum(offers.qty')';
end
omask(temp == 0, :) = NaN;
bmask = ones(nrb,ncb);
if ncb == 1
    temp = bids.qty;
else
    temp = sum(bids.qty')';
end
bmask(temp == 0, :) = NaN;

%% by default, don't scale anything
X.LAO = 1;
X.FRO = 1;
X.LAB = 1;
X.FRB = 1;

%% don't scale if we have any negative lambdas or all are too close to 0
if all(all(offers.lam > -zero_tol))
    %% ratios
    Xo = omask .* offers.prc ./ olam;
    Xb = bmask .* bids.prc   ./ blam;

    %% exchange rate for LAO (X.LAO * lambda == LAO, for corresponding lambda)
    X.LAO = o.on .* Xo;
    X.LAO( o.off(:) ) = NaN;
    X.LAO( X.LAO(:) > 1+zero_tol ) = NaN;   %% don't let gens @ Pmin set price
    X.LAO = max( X.LAO(:) );

    %% exchange rate for FRO (X.FRO * lambda == FRO, for corresponding lambda)
    X.FRO = o.off .* Xo;
    X.FRO( o.on(:) ) = NaN;
    X.FRO = min( X.FRO(:) );

    if nrb
        %% exchange rate for LAB (X.LAB * lambda == LAB, for corresponding lambda)
        X.LAB = b.on .* Xb;
        X.LAB( b.off(:) ) = NaN;
        X.LAB( X.LAB(:) < 1-zero_tol ) = NaN;   %% don't let set price
        X.LAB = min( X.LAB(:) );
        
        %% exchange rate for FRB (X.FRB * lambda == FRB, for corresponding lambda)
        X.FRB = b.off .* Xb;
        X.FRB( b.on(:) ) = NaN;
        X.FRB = max( X.FRB(:) );
    end
end


function [cqty, on, off] = clear_qty(qty, total_cqty, mask)
%CLEAR_QTY  Computed cleared offer/bid quantities from totals.
%  Inputs:
%   qty        - m x n, offer/bid quantities, m offers/bids, n blocks
%   total_cqty - m x 1, total cleared quantity for each offer/bid
%   mask       - m x n, boolean indicating which offers/bids are valid (not withheld)
%  Outputs:
%   cqty       - m x n, cleared offer/bid quantities, m offers/bids, n blocks
%   on         - m x n, 1 if partially or fully accepted, 0 if rejected
%   off        - m x n, 1 if rejected, 0 if partially or fully accepted

[nr, nc] = size(qty);
accept  = zeros(nr,nc);
cqty    = zeros(nr,nc);
for i = 1:nr            %% offer/bid i
    for j = 1:nc        %% block j
        if qty(i, j)    %% ignore zero quantity offers/bids
            %% compute fraction of the block accepted ...
            accept(i, j) = (total_cqty(i) - sum(qty(i, 1:j-1))) / qty(i, j);
            %% ... clipped to the range [0, 1]  (i.e. 0-100%)
            if accept(i, j) > 1
                accept(i, j) = 1;
            elseif accept(i, j) < 1.0e-5
                accept(i, j) = 0;
            end
            cqty(i, j) = qty(i, j) * accept(i, j);
        end
    end
end

if nargin == 3
    accept = mask .* accept;
end

on  = (accept  > 0);
off = (accept == 0);
