Java tutorial
/* * Copyright 2014 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.powertac.genco; import org.apache.commons.math3.distribution.NormalDistribution; import org.apache.log4j.Logger; import org.joda.time.Instant; import org.powertac.common.*; import org.powertac.common.config.ConfigurableInstance; import org.powertac.common.config.ConfigurableValue; import org.powertac.common.interfaces.BrokerProxy; import org.powertac.common.interfaces.ServerConfiguration; import org.powertac.common.repo.RandomSeedRepo; import org.powertac.common.repo.TimeslotRepo; import org.powertac.common.state.Domain; import org.powertac.common.state.StateChange; import java.util.ArrayList; import java.util.Arrays; import java.util.List; /** * Represents a set of bulk producers distributed across the transmission * domain. * The assumption is that there will be exactly one of these operating in * the wholesale side of the Power TAC day-ahead market. * * This instance submits bids in a way that is intended to mimic the price * curve at a load node subject to congestion pricing. The function is a * polynomial. Its * coefficients are configurable, as are the nominal interval between bid prices * (to create the piecewise-linear supply curve) and the variability of * price and quantity per bid. * * @author John Collins */ @Domain @ConfigurableInstance public class CpGenco extends Broker { static private Logger log = Logger.getLogger(CpGenco.class.getName()); /** Price and quantity variability. The value is the ratio of sigma to mean */ private double pSigma = 0.1; private double qSigma = 0.1; /** Price interval between bids */ private double priceInterval = 4.0; /** Minimum total offered quantity in MWh */ private double minQuantity = 120.0; /** curve generating coefficients as a comma-separated list */ private List<String> coefficients = Arrays.asList(".007", ".1", "16.0"); private double[] coefficientArray = null; private double[][] timeslotCoefficients; // ring buffer private int ringOffset = -1; // uninitialized private int lastTsGenerated = -1; /** random-walk parameters */ private double rwaSigma = 0.004; private double rwaOffset = 0.0025; private double rwcSigma = 0.005; private double rwcOffset = 0.002; protected BrokerProxy brokerProxyService; protected RandomSeed seed; // needed for saving bootstrap state private TimeslotRepo timeslotRepo; private NormalDistribution normal01; private QuadraticFunction function = new QuadraticFunction(); public CpGenco(String username) { super(username, true, true); } public void init(BrokerProxy proxy, int seedId, RandomSeedRepo randomSeedRepo, TimeslotRepo timeslotRepo) { log.info("init(" + seedId + ") " + getUsername()); this.brokerProxyService = proxy; this.timeslotRepo = timeslotRepo; // set up the random generator this.seed = randomSeedRepo.getRandomSeed(CpGenco.class.getName(), seedId, "bid"); normal01 = new NormalDistribution(0.0, 1.0); normal01.reseedRandomGenerator(seed.nextLong()); // set up the supply-curve generating function if (!function.validateCoefficients(coefficients)) log.error("wrong number of coefficients for quadratic"); int to = Competition.currentCompetition().getTimeslotsOpen(); timeslotCoefficients = new double[to][getCoefficients().size()]; } /** * Generates Orders in the market to sell remaining available capacity. */ public void generateOrders(Instant now, List<Timeslot> openSlots) { log.info("Generate orders for " + getUsername()); for (Timeslot slot : openSlots) { function.setCoefficients(getTsCoefficients(slot)); MarketPosition posn = findMarketPositionByTimeslot(slot.getSerialNumber()); double start = 0.0; if (posn != null) { // posn.overallBalance is negative if we have sold power in this slot start = -posn.getOverallBalance(); } // make offers up to minQuantity while (start < minQuantity) { log.debug("start qty = " + start); double[] ran = normal01.sample(2); double price = function.getY(start); price += ran[0] * getPSigma(); double dx = function.getDeltaX(start); double std = dx * getQSigma(); dx = Math.max(0.0, ran[1] * std + dx); // don't go backward Order offer = new Order(this, slot.getSerialNumber(), -dx, price); log.debug("new order (ts, qty, price): (" + slot.getSerialNumber() + ", " + (-dx) + ", " + price + ")"); brokerProxyService.routeMessage(offer); start += dx; } } } // Converts a timeslot to its index in the coefficient ring buffer, // filling the correct slot in the ring if needed. private double[] getTsCoefficients(Timeslot slot) { int horizon = timeslotCoefficients.length; if (-1 == ringOffset) { // first encounter ringOffset = slot.getSerialNumber(); lastTsGenerated = slot.getSerialNumber(); walkCoefficients(getCoefficientArray(), timeslotCoefficients[0]); logCoefficients(slot.getSerialNumber(), timeslotCoefficients[0]); } int index = (slot.getSerialNumber() - ringOffset) % horizon; if (slot.getSerialNumber() > lastTsGenerated) { int prev = (slot.getSerialNumber() - ringOffset - 1) % horizon; walkCoefficients(timeslotCoefficients[prev], timeslotCoefficients[index]); logCoefficients(slot.getSerialNumber(), timeslotCoefficients[index]); lastTsGenerated = slot.getSerialNumber(); } return timeslotCoefficients[index]; } // log the coefficients for ts private void logCoefficients(int serialNumber, double[] ds) { log.info("Coefficients for ts " + serialNumber + ": [" + ds[0] + ", " + ds[1] + ", " + ds[2] + "]"); } // String to array conversion double[] extractCoefficients(List<String> coeff) { double[] result = new double[coeff.size()]; try { for (int i = 0; i < coeff.size(); i++) { result[i] = (Double.parseDouble((String) coeff.get(i))); } return result; } catch (NumberFormatException nfe) { log.error("Cannot parse " + coeff + " into a number array"); return new double[0]; } } // Runs one step of the per-timeslot random-walk private void walkCoefficients(double[] s0, double[] s1) { double[] ran = normal01.sample(2); // two samples double[] coef = getCoefficientArray(); s1[0] = s0[0] + ran[0] * rwaSigma * coef[0] + (coef[0] - s0[0]) * rwaOffset; s1[1] = s0[1]; s1[2] = s0[2] + ran[1] * rwcSigma * coef[2] + (coef[2] - s0[2]) * rwcOffset; } /** * Saves coefficients for the current timeslot in the form needed for * configuration at the start of the sim session, then adds them to the * bootstrap state. */ public void saveBootstrapState(ServerConfiguration serverConfig) { int horizon = timeslotCoefficients.length; int index = (timeslotRepo.currentSerialNumber() - ringOffset) % horizon; ArrayList<String> newCoeff = new ArrayList<String>(); for (Double coeff : timeslotCoefficients[index]) { newCoeff.add(coeff.toString()); } coefficients = newCoeff; serverConfig.saveBootstrapState(this); } // ------------ getters & setters ----------------- /** * Returns function coefficients as an array of Strings */ public List<String> getCoefficients() { ArrayList<String> result = new ArrayList<String>(); for (Object thing : coefficients) result.add((String) thing); return result; } /** * Returns coefficients as a array. */ public double[] getCoefficientArray() { if (null == coefficientArray) { coefficientArray = extractCoefficients(coefficients); } return coefficientArray; } /** * Fluent setter for coefficient array */ @ConfigurableValue(valueType = "List", bootstrapState = true, description = "Coefficients for the specified function type") @StateChange public CpGenco withCoefficients(List<String> coeff) { if (function.validateCoefficients(coeff)) { coefficients = coeff; } else { log.error("incorrect number of coefficients"); } return this; } /** * Std deviation ratio for bid price. */ public double getPSigma() { return pSigma; } /** * Fluent setter for price variability. The value is ratio of the standard * deviation to the nominal bid price for a given bid. */ @ConfigurableValue(valueType = "Double", description = "Standard Deviation ratio for bid price") @StateChange public CpGenco withPSigma(double var) { this.pSigma = var; return this; } /** * Std deviation ratio for bid quantity. */ public double getQSigma() { return qSigma; } /** * Fluent setter for price variability. The value is ratio of the standard * deviation to the nominal bid quantity for a given bid. */ @ConfigurableValue(valueType = "Double", description = "Standard Deviation ratio for bid quantity") @StateChange public CpGenco withQSigma(double var) { this.qSigma = var; return this; } /** * Random-walk sigma for the quadratic coefficient */ public double getRwaSigma() { return rwaSigma; } /** * Fluent setter for the random-walk sigma value applied to the * quadratic coefficient. */ @ConfigurableValue(valueType = "Double", description = "Random-walk std dev ratio for quadratic coefficient") @StateChange public CpGenco withRwaSigma(double var) { this.rwaSigma = var; return this; } /** * Random-walk offset for the quadratic coefficient */ public double getRwaOffset() { return rwaOffset; } /** * Fluent setter for the random-walk offset value applied to the * quadratic coefficient. */ @ConfigurableValue(valueType = "Double", description = "Random-walk offset ratio for quadratic coefficient") @StateChange public CpGenco withRwaOffset(double var) { this.rwaOffset = var; return this; } /** * Random-walk sigma for the constant coefficient */ public double getRwcSigma() { return rwcSigma; } /** * Fluent setter for the random-walk sigma value applied to the * constant coefficient. */ @ConfigurableValue(valueType = "Double", description = "Random-walk std dev ratio for constant coefficient") @StateChange public CpGenco withRwcSigma(double var) { this.rwcSigma = var; return this; } /** * Random-walk offset for the constant coefficient */ public double getRwcOffset() { return rwcOffset; } /** * Fluent setter for the random-walk offset value applied to the * constant coefficient. */ @ConfigurableValue(valueType = "Double", description = "Random-walk offset ratio for constant coefficient") @StateChange public CpGenco withRwcOffset(double var) { this.rwcOffset = var; return this; } /** * Difference between sequential nominal bid prices */ public double getPriceInterval() { return priceInterval; } /** * Fluent setter for price interval. Bigger values create a more coarse * piecewise approximation of the supply curve. */ @ConfigurableValue(valueType = "Double", description = "Nominal price interval between successive bids") @StateChange public CpGenco withPriceInterval(double interval) { this.priceInterval = interval; return this; } /** * Minimum total quantity to offer. The generation function will be run * until it hits this value. */ public double getMinQuantity() { return minQuantity; } /** * Fluent setter for minimum total quantity. */ @ConfigurableValue(valueType = "Double", description = "minimum leadtime for first commitment, in hours") @StateChange public CpGenco withMinQuantity(double qty) { this.minQuantity = qty; return this; } /** * Function of the form price = a*qty^2 + b*qty + c * Probably this should be done with Newton-Rapson, but it's a pain * to re-define the polynomial for each bid. */ class QuadraticFunction { // coefficients double a = 1.0; double b = 1.0; double c = 1.0; boolean validateCoefficients(List<String> coefficients) { double[] test = extractCoefficients(coefficients); if (test.length != 3) return false; return true; } void setCoefficients(double[] coef) { a = coef[0]; b = coef[1]; c = coef[2]; } // returns the delta-x for a given x value and the nominal y-interval // given by priceInterval. This is the quadratic formula with // c = as^2 + bs + p where s is startX and p is priceInterval double getDeltaX(double startX) { double endX = -b / (2.0 * a) + Math.sqrt(b * b + 4.0 * a * (a * startX * startX + b * startX + priceInterval)) / (2.0 * a); return (endX - startX); } // returns a price given a qty double getY(double x) { return (a * x * x + b * x + c); } } }