edu.cuny.cat.market.matching.FourHeapShoutEngine.java Source code

Java tutorial

Introduction

Here is the source code for edu.cuny.cat.market.matching.FourHeapShoutEngine.java

Source

/*
 * JCAT - TAC Market Design Competition Platform
 * Copyright (C) 2006-2010 Jinzhong Niu, Kai Cai
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 */
/*
 * JASA Java Auction Simulator API
 * Copyright (C) 2001-2005 Steve Phelps
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 */

package edu.cuny.cat.market.matching;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import org.apache.commons.collections15.buffer.PriorityBuffer;
import org.apache.commons.collections15.iterators.CollatingIterator;
import org.apache.log4j.Logger;

import edu.cuny.cat.core.AuctionError;
import edu.cuny.cat.core.Shout;
import edu.cuny.cat.market.DuplicateShoutException;

/**
 * <p>
 * This class provides auction shout management services using the 4-Heap
 * algorithm. See:
 * </p>
 * 
 * <p>
 * "Flexible Double Auctions for Electronic Commerce: Theory and Implementation"
 * by Wurman, Walsh and Wellman 1998.
 * </p>
 * 
 * <p>
 * All status is maintained in memory resident data structures and no crash
 * recovery is provided.
 * </p>
 * 
 * @author Steve Phelps
 * @version $Revision: 1.16 $
 */

public class FourHeapShoutEngine extends ShoutEngine implements Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 1L;

    /**
     * Matched bids in ascending order
     */
    protected PriorityBuffer<Shout> bIn = null;

    /**
     * Unmatched bids in descending order
     */
    protected PriorityBuffer<Shout> bOut = null;

    /**
     * Matched asks in descending order
     */
    protected PriorityBuffer<Shout> sIn = null;

    /**
     * Unmatched asks in ascending order
     */
    protected PriorityBuffer<Shout> sOut = null;

    static Logger logger = Logger.getLogger(FourHeapShoutEngine.class);

    public FourHeapShoutEngine() {
        bIn = new PriorityBuffer<Shout>(ShoutEngine.AscendingOrder);
        bOut = new PriorityBuffer<Shout>(ShoutEngine.DescendingOrder);
        sIn = new PriorityBuffer<Shout>(ShoutEngine.DescendingOrder);
        sOut = new PriorityBuffer<Shout>(ShoutEngine.AscendingOrder);
    }

    @Override
    public synchronized void reset() {
        super.reset();

        bIn.clear();
        bOut.clear();
        sIn.clear();
        sOut.clear();
    }

    @Override
    public synchronized void removeShout(final Shout shout) {
        preRemovalProcessing();
        if (shout.isAsk()) {
            removeAsk(shout);
        } else {
            removeBid(shout);
        }
        postRemovalProcessing();
    }

    protected void removeAsk(final Shout shout) {
        if (sIn.remove(shout)) {
            reinsert(bIn, shout.getQuantity());
        } else {
            sOut.remove(shout);
        }
    }

    protected void removeBid(final Shout shout) {
        if (bIn.remove(shout)) {
            reinsert(sIn, shout.getQuantity());
        } else {
            bOut.remove(shout);
        }
    }

    @Override
    public String toString() {
        return getClass().getSimpleName();
    }

    /**
     * Log the current status of the auction.
     */
    @Override
    public void printState() {
        FourHeapShoutEngine.logger.info("Auction status:\n");
        prettyPrint("Matched bids", bIn);
        prettyPrint("Matched asks", sIn);
        prettyPrint("Runner-up bids", bOut);
        prettyPrint("Runner-up asks", sOut);
    }

    public void prettyPrint(final String title, final PriorityBuffer<Shout> shouts) {
        FourHeapShoutEngine.logger.info(title);
        FourHeapShoutEngine.logger.info("--------------");
        final Iterator<Shout> i = shouts.iterator();
        while (i.hasNext()) {
            final Shout shout = i.next();
            FourHeapShoutEngine.logger.info(shout.toPrettyString());
        }
        FourHeapShoutEngine.logger.info("");
    }

    /**
     * Insert a shout into a binary heap.
     * 
     * @param heap
     *          The heap to insert into
     * @param shout
     *          The shout to insert
     * 
     */
    private static void insertShout(final PriorityBuffer<Shout> heap, final Shout shout)
            throws DuplicateShoutException {
        try {
            heap.add(shout);
        } catch (final IllegalArgumentException e) {
            FourHeapShoutEngine.logger.error(e.toString());
            e.printStackTrace();
            throw new DuplicateShoutException("Duplicate shout: " + shout.toString());
        }
    }

    /**
     * Insert an unmatched ask into the approriate heap.
     */
    public void insertUnmatchedAsk(final Shout ask) throws DuplicateShoutException {
        FourHeapShoutEngine.insertShout(sOut, ask);
    }

    /**
     * Insert an unmatched bid into the approriate heap.
     */
    public void insertUnmatchedBid(final Shout bid) throws DuplicateShoutException {
        FourHeapShoutEngine.insertShout(bOut, bid);
    }

    /**
     * Get the highest unmatched bid.
     */
    @Override
    public Shout getHighestUnmatchedBid() {
        if (bOut.isEmpty()) {
            return null;
        }
        return bOut.get();
    }

    /**
     * Get the lowest matched bid
     */
    @Override
    public Shout getLowestMatchedBid() {
        if (bIn.isEmpty()) {
            return null;
        }
        return bIn.get();
    }

    /**
     * Get the lowest unmatched ask.
     */
    @Override
    public Shout getLowestUnmatchedAsk() {
        if (sOut.isEmpty()) {
            return null;
        }
        return sOut.get();
    }

    /**
     * Get the highest matched ask.
     */
    @Override
    public Shout getHighestMatchedAsk() {
        if (sIn.isEmpty()) {
            return null;
        }
        return sIn.get();
    }

    /**
     * Unify the shout at the top of the heap with the supplied shout, so that
     * quantity(shout) = quantity(top(heap)). This is achieved by splitting the
     * supplied shout or the shout at the top of the heap.
     * 
     * @param shout
     *          The shout.
     * @param heap
     *          The heap.
     * 
     * @return A reference to the, possibly modified, shout.
     * 
     */
    protected static Shout unifyShout(Shout shout, final PriorityBuffer<Shout> heap) {

        final Shout top = heap.get();

        if (shout.getQuantity() > top.getQuantity()) {
            shout = shout.splat(shout.getQuantity() - top.getQuantity());
        } else {
            if (top.getQuantity() > shout.getQuantity()) {
                final Shout remainder = top.split(top.getQuantity() - shout.getQuantity());
                heap.add(remainder);
            }
        }

        return shout;
    }

    protected int displaceShout(Shout shout, final PriorityBuffer<Shout> from, final PriorityBuffer<Shout> to)
            throws DuplicateShoutException {
        shout = FourHeapShoutEngine.unifyShout(shout, from);
        to.add(from.remove());
        FourHeapShoutEngine.insertShout(from, shout);
        return shout.getQuantity();
    }

    public int promoteShout(Shout shout, final PriorityBuffer<Shout> from, final PriorityBuffer<Shout> to,
            final PriorityBuffer<Shout> matched) throws DuplicateShoutException {

        shout = FourHeapShoutEngine.unifyShout(shout, from);
        FourHeapShoutEngine.insertShout(matched, shout);
        to.add(from.remove());
        return shout.getQuantity();
    }

    public int displaceHighestMatchedAsk(final Shout ask) throws DuplicateShoutException {
        return displaceShout(ask, sIn, sOut);
    }

    public int displaceLowestMatchedBid(final Shout bid) throws DuplicateShoutException {
        return displaceShout(bid, bIn, bOut);
    }

    public int promoteHighestUnmatchedBid(final Shout ask) throws DuplicateShoutException {
        return promoteShout(ask, bOut, bIn, sIn);
    }

    public int promoteLowestUnmatchedAsk(final Shout bid) throws DuplicateShoutException {
        return promoteShout(bid, sOut, sIn, bIn);
    }

    @Override
    public void newShout(final Shout shout) throws DuplicateShoutException {
        if (shout.isBid()) {
            newBid(shout);
        } else {
            newAsk(shout);
        }
    }

    protected void newBid(final Shout bid) throws DuplicateShoutException {

        final double bidVal = bid.getPrice();

        int uninsertedUnits = bid.getQuantity();

        while (uninsertedUnits > 0) {

            final Shout sOutTop = getLowestUnmatchedAsk();
            final Shout bInTop = getLowestMatchedBid();

            if ((sOutTop != null) && (bidVal >= sOutTop.getPrice())
                    && ((bInTop == null) || (bInTop.getPrice() >= sOutTop.getPrice()))) {

                // found match
                uninsertedUnits -= promoteLowestUnmatchedAsk(bid);

            } else if ((bInTop != null) && (bidVal > bInTop.getPrice())) {

                uninsertedUnits -= displaceLowestMatchedBid(bid);

            } else {
                insertUnmatchedBid(bid);
                uninsertedUnits -= bid.getQuantity();
            }

        }
    }

    protected void newAsk(final Shout ask) throws DuplicateShoutException {

        final double askVal = ask.getPrice();

        int uninsertedUnits = ask.getQuantity();

        while (uninsertedUnits > 0) {

            final Shout sInTop = getHighestMatchedAsk();
            final Shout bOutTop = getHighestUnmatchedBid();

            if ((bOutTop != null) && (askVal <= bOutTop.getPrice())
                    && ((sInTop == null) || (sInTop.getPrice() <= bOutTop.getPrice()))) {

                uninsertedUnits -= promoteHighestUnmatchedBid(ask);

            } else if ((sInTop != null) && (askVal <= sInTop.getPrice())) {

                uninsertedUnits -= displaceHighestMatchedAsk(ask);

            } else {

                insertUnmatchedAsk(ask);
                uninsertedUnits -= ask.getQuantity();

            }
        }
    }

    @Override
    public Iterator<Shout> askIterator() {
        return new CollatingIterator<Shout>(ShoutEngine.AscendingOrder, sIn.iterator(), sOut.iterator());
    }

    @Override
    public Iterator<Shout> bidIterator() {
        return new CollatingIterator<Shout>(ShoutEngine.DescendingOrder, bIn.iterator(), bOut.iterator());
    }

    /**
     * <p>
     * Return a list of matched bids and asks. The list is of the form
     * </p>
     * <br>
     * ( b0, a0, b1, a1 .. bn, an )<br>
     * 
     * <p>
     * where bi is the ith bid and a0 is the ith ask. A typical auctioneer would
     * clear by matching bi with ai for all i at some price.
     * </p>
     */
    @Override
    public List<Shout> getMatchedShouts() {
        final ArrayList<Shout> result = new ArrayList<Shout>(sIn.size() + bIn.size());
        while (!sIn.isEmpty()) {
            final Shout sInTop = sIn.remove();
            final Shout bInTop = bIn.remove();
            final int nS = sInTop.getQuantity();
            final int nB = bInTop.getQuantity();
            if (nS < nB) {
                // split the bid
                final Shout remainder = bInTop.split(nB - nS);
                bIn.add(remainder);
            } else if (nB < nS) {
                // split the ask
                final Shout remainder = sInTop.split(nS - nB);
                sIn.add(remainder);
            }
            result.add(bInTop);
            result.add(sInTop);
        }
        return result;
    }

    /**
     * Sub-classes should override this method if they wish to check auction
     * status integrity before shout removal. This is useful for
     * testing/debugging.
     */
    protected void preRemovalProcessing() {
        // Do nothing
    }

    /**
     * Sub-classes should override this method if they wish to check auction
     * status integrity after shout removal. This is useful for testing/debugging.
     */
    protected void postRemovalProcessing() {
        // Do nothing
    }

    /**
     * Remove, possibly several, shouts from heap such that quantity(heap) is
     * reduced by the supplied quantity and reinsert the shouts using the standard
     * insertion logic. quantity(heap) is defined as the total quantity of every
     * shout in the heap.
     * 
     * @param heap
     *          The heap to remove shouts from.
     * @param quantity
     *          The total quantity to remove.
     */
    protected void reinsert(final PriorityBuffer<Shout> heap, int quantity) {

        while (quantity > 0) {

            final Shout top = heap.remove();

            if (top.getQuantity() > quantity) {
                heap.add(top.split(top.getQuantity() - quantity));
            }

            quantity -= top.getQuantity();

            try {
                if (top.isBid()) {
                    newBid(top);
                } else {
                    newAsk(top);
                }
            } catch (final DuplicateShoutException e) {
                throw new AuctionError("Invalid auction status");
            }
        }
    }
}