org.powertac.common.Competition.java Source code

Java tutorial

Introduction

Here is the source code for org.powertac.common.Competition.java

Source

/*
 * Copyright 2009-2011 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.common;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
import org.joda.time.Instant;
import org.joda.time.format.DateTimeFormat;
import org.joda.time.format.DateTimeFormatter;
import org.powertac.common.config.ConfigurableValue;
import org.powertac.common.state.Domain;
import org.powertac.common.state.StateChange;
import org.powertac.common.xml.FullCustomerConverter;

import com.thoughtworks.xstream.annotations.XStreamAlias;
import com.thoughtworks.xstream.annotations.XStreamAsAttribute;
import com.thoughtworks.xstream.annotations.XStreamConverter;
import com.thoughtworks.xstream.annotations.XStreamImplicit;
import com.thoughtworks.xstream.annotations.XStreamOmitField;

/**
 * A competition instance represents a single PowerTAC competition and
 * at the same time serves as the place for all competition properties that can be
 * adjusted during competition setup (i.e. during server runtime but before competition start).
 * This is an immutable value type.
 * @author Carsten Block, KIT; John Collins, U of Minnesota
 */
@Domain
@XStreamAlias("competition")
public class Competition //implements Serializable 
{
    @XStreamAsAttribute
    private long id = IdGenerator.createId();

    /** The competition's name */
    @XStreamAsAttribute
    private String name = "default";

    /** Optional text that further describes the competition    */
    private String description = "";

    /** POM ID from server.properties */
    @XStreamAsAttribute
    private String pomId = "unknown";

    /** length of a timeslot in simulation minutes    */
    @XStreamAsAttribute
    private int timeslotLength = 60;

    /** Number of timeslots in initialization data dump */
    @XStreamAsAttribute
    private int bootstrapTimeslotCount = 336; // 14 days

    /** Number of extra timeslots at start of bootstrap before data collection starts */
    @XStreamAsAttribute
    private int bootstrapDiscardedTimeslots = 24;

    /** Minimum number of timeslots, aka competition length    */
    @XStreamOmitField
    private int minimumTimeslotCount = 480;

    @XStreamOmitField
    private int expectedTimeslotCount = 600;

    @XStreamOmitField
    private Integer fixedTimeslotCount = null;

    /** concurrently open timeslots, i.e. time window in which broker actions like trading are allowed   */
    @XStreamAsAttribute
    private int timeslotsOpen = 24;

    /** # timeslots a timeslot gets deactivated ahead of the now timeslot (default: 1 timeslot, which (given default length of 60 min) means that e.g. trading is disabled 60 minutes ahead of time    */
    @XStreamAsAttribute
    private int deactivateTimeslotsAhead = 1;

    /** Minimum order quantity */
    @XStreamAsAttribute
    private double minimumOrderQuantity = 0.01; // MWh

    // Tariff evaluation parameters
    /** Above this ratio, regulation is discounted. */
    @XStreamAsAttribute
    private double maxUpRegulationPaymentRatio = -4.0;

    @XStreamAsAttribute
    private double upRegulationDiscount = 0.5;

    /** Above this ratio, customer will discount down-regulation,
        either during evaluation nor at runtime. */
    @XStreamAsAttribute
    private double maxDownRegulationPaymentRatio = 1.5;

    @XStreamAsAttribute
    private double downRegulationDiscount = 0.4;

    /** Brokers typically pay less for production than they charge for
        consumption. This ratio is an estimate of that margin that is used
        to modify the constraints on up- and down- regulation behavior. */
    @XStreamAsAttribute
    private double estimatedConsumptionPremium = 2.0;

    /** the start time of the simulation scenario, in sim time. */
    @XStreamAsAttribute
    private Instant simulationBaseTime = new DateTime(2010, 6, 21, 0, 0, 0, 0, DateTimeZone.UTC).toInstant();

    /** timezone offset for scenario locale */
    @XStreamAsAttribute
    private int timezoneOffset = 0;

    /** approximate latitude in degrees north for scenario locale */
    @XStreamAsAttribute
    private int latitude = 45;

    /** the time-compression ratio for the simulation. So if we are running one-hour timeslots every 5 seconds, the rate would be 720 (=default).    */
    @XStreamAsAttribute
    private long simulationRate = 720l;

    /** controls the values of simulation time values reported. If
     *  we are running one-hour timeslots, then the modulo should be one hour, expressed
     *  in milliseconds. If we are running one-hour timeslots but want to update time every
     *  30 minutes of simulated time, then the modulo would be 30*60*1000. Note that
     *  this will not work correctly unless the calls to updateTime() are made at
     *  modulo/rate intervals. Also note that the reported time is computed as
     *  rawTime - rawTime % modulo, which means it will never be ahead of the raw
     *  simulation time. Note that values other than the length of a timeslot have
     *  not been tested. */
    @XStreamAsAttribute
    private long simulationModulo = 60 * 60 * 1000;

    // include the list of broker usernames
    @XStreamImplicit(itemFieldName = "broker")
    private ArrayList<String> brokers;

    @XStreamImplicit(itemFieldName = "customer")
    @XStreamConverter(FullCustomerConverter.class)
    private ArrayList<CustomerInfo> customers;

    // singleton instance
    private static Competition theCompetition;

    public static Competition newInstance(String name) {
        Competition result = new Competition(name);
        //theCompetition = result;
        return result;
    }

    /**
     * Returns the current Competition instance. There should always be either
     * zero or one of these.
     */
    public static Competition currentCompetition() {
        return theCompetition;
    }

    /**
     * Makes a Competition instance be the "current" competition - this is 
     * needed in a broker when the Competition instance arrives from the
     * server.
     */
    public static void setCurrent(Competition newCurrent) {
        theCompetition = newCurrent;
    }

    /**
     * Constructor replaces current competition instance. It is up to the
     * caller to ensure that this is done at the correct time.
     */
    private Competition(String name) {
        super();
        this.name = name;
        brokers = new ArrayList<String>();
        customers = new ArrayList<CustomerInfo>();
        theCompetition = this;
    }

    public long getId() {
        return id;
    }

    /** Returns the competition name */
    public String getName() {
        return name;
    }

    /** Uninterpreted text that further describes the competition. */
    public String getDescription() {
        return description;
    }

    /**
     * Fluent setter for competition description.
     */
    @ConfigurableValue(description = "user-readable description of the Competition", valueType = "String")
    @StateChange
    public Competition withDescription(String description) {
        this.description = description;
        return this;
    }

    /**
     * Returns the pom version id from the server on which this Competition
     * was created.
     */
    public String getPomId() {
        return pomId;
    }

    /** Fluent setter for Pom ID.
     */
    @ConfigurableValue(description = "maven version identifier from server", valueType = "String")
    @StateChange
    public Competition withPomId(String id) {
        this.pomId = id;
        return this;
    }

    /**
     * Returns the length of a timeslot in minutes (sim time).
     */
    public int getTimeslotLength() {
        return timeslotLength;
    }

    /**
     * Returns the duration of a timeslot in milliseconds sim-time.
     */
    public long getTimeslotDuration() {
        return timeslotLength * TimeService.MINUTE;
    }

    /**
     * Fluent setter for timeslot length, interpreted as minutes in sim time.
     */
    @ConfigurableValue(name = "timeslotLength", description = "length of timeslot in minutes sim time", valueType = "Integer")
    @StateChange
    public Competition withTimeslotLength(int timeslotLength) {
        this.timeslotLength = timeslotLength;
        return this;
    }

    /**
     * Minimum number of timeslots for this competition. The actual number is
     * randomized by CompetitionControl at sim start time.
     */
    public int getMinimumTimeslotCount() {
        return minimumTimeslotCount;
    }

    /**
     * Fluent setter for minimumTimeslotCount.
     */
    @ConfigurableValue(valueType = "Integer", description = "minimum number of timeslots in simulation run")
    @StateChange
    public Competition withMinimumTimeslotCount(int minimumTimeslotCount) {
        this.minimumTimeslotCount = minimumTimeslotCount;
        return this;
    }

    /**
     * Expected value of timeslot count for a normal sim session.
     */
    public int getExpectedTimeslotCount() {
        return expectedTimeslotCount;
    }

    /**
     * Fluent setter for the expected length of a normal sim session.
     */
    @ConfigurableValue(valueType = "Integer", description = "expected number of timeslots in simulation run")
    @StateChange
    public Competition withExpectedTimeslotCount(int expectedTimeslotCount) {
        this.expectedTimeslotCount = expectedTimeslotCount;
        return this;
    }

    /**
     * Fixed value for timeslot count, allows external tools such as tournament scheduler to
     * compute game lengths externally.
     */
    public Integer getFixedTimeslotCount() {
        return fixedTimeslotCount;
    }

    /**
     * Fluent setter for the expected length of a normal sim session.
     */
    @ConfigurableValue(valueType = "Integer", description = "If given, overrides min and expected timeslot count values")
    @StateChange
    public Competition withFixedTimeslotCount(Integer fixedTimeslotCount) {
        this.fixedTimeslotCount = fixedTimeslotCount;
        return this;
    }

    /**
     * Number of timeslots simultaneously open for trading.
     */
    public int getTimeslotsOpen() {
        return timeslotsOpen;
    }

    /**
     * Fluent setter for the open timeslot count. Default value is 24.
     */
    @ConfigurableValue(valueType = "Integer", description = "expected number of timeslots in simulation run")
    @StateChange
    public Competition withTimeslotsOpen(int timeslotsOpen) {
        this.timeslotsOpen = timeslotsOpen;
        return this;
    }

    /**
     * Number of timeslots, starting with the current timeslot, that are closed
     * for trading. 
     */
    public int getDeactivateTimeslotsAhead() {
        return deactivateTimeslotsAhead;
    }

    /**
     * Fluent setter for number of timeslots, starting with the current timeslot,
     * that are closed for trading. Default value is 1. 
     */
    @ConfigurableValue(valueType = "Integer", description = "expected number of timeslots in simulation run")
    @StateChange
    public Competition withDeactivateTimeslotsAhead(int deactivateTimeslotsAhead) {
        this.deactivateTimeslotsAhead = deactivateTimeslotsAhead;
        return this;
    }

    /**
     * Minimum order quantity in MWh.
     */
    public double getMinimumOrderQuantity() {
        return minimumOrderQuantity;
    }

    /**
     * Customers assume up-regulation will never clear if the regulation price
     * is higher than the consumption price times this ratio, and so up-regulation
     * will be ignored during tariff evaluation.
     */
    public double getMaxUpRegulationPaymentRatio() {
        return maxUpRegulationPaymentRatio;
    }

    /**
     * Fluent setter for the maximum ratio between consumption price and up-regulation
     * price for which customers will include up-regulation in tariff evaluation.
     */
    @ConfigurableValue(valueType = "Double", description = "Limit on up-regulation payment ratio")
    public Competition withMaxUpRegulationPaymentRatio(double value) {
        maxUpRegulationPaymentRatio = value;
        return this;
    }

    /**
     * Discount rate for overpriced up-regulation.
     */
    public double getUpRegulationDiscount() {
        return upRegulationDiscount;
    }

    /**
     * Fluent setter for overpriced up-regulation discount rate.
     */
    @ConfigurableValue(valueType = "Double", description = "Discount rate on overpriced up-regulation")
    public Competition withUpRegulationDiscount(double value) {
        upRegulationDiscount = value;
        return this;
    }

    /**
     * If a tariff offers a down-regulation price larger (more negative) than the
     * consumption price times this ratio, customers will not offer down-regulation,
     * and will ignore down-regulation during tariff evaluation.
     */
    public double getMaxDownRegulationPaymentRatio() {
        return maxDownRegulationPaymentRatio;
    }

    /**
     * Fluent setter for the maximum down-regulation payment ratio.
     */
    @ConfigurableValue(valueType = "Double", description = "Limit on down-regulation payment")
    public Competition withMaxDownRegulationPaymentRatio(double value) {
        maxDownRegulationPaymentRatio = value;
        return this;
    }

    /**
     * Discount rate for overpriced down-regulation.
     */
    public double getDownRegulationDiscount() {
        return downRegulationDiscount;
    }

    /**
     * Fluent setter for overpriced down-regulation discount rate.
     */
    @ConfigurableValue(valueType = "Double", description = "Discount rate on overpriced down-regulation")
    public Competition withDownRegulationDiscount(double value) {
        downRegulationDiscount = value;
        return this;
    }

    /**
     * Brokers typically pay less for production than they charge for consumption.
     * This ratio is an estimate of that margin that is used to modify the
     * constraints on up- and down-regulation behavior.
     */
    public double getEstimatedConsumptionPremium() {
        return estimatedConsumptionPremium;
    }

    /**
     * Fluent setter for the estimated consumption price premium.
     */
    @ConfigurableValue(valueType = "Double", description = "Estimated ratio of consumption prices over production prices")
    public Competition withEstimatedConsumptionPremium(double value) {
        estimatedConsumptionPremium = value;
        return this;
    }

    /**
     * Fluent setter for minimum order quantity. Default is 0.01 MWh.
     */
    @ConfigurableValue(valueType = "Double", description = "Minimum order quantity in MWh")
    @StateChange
    public Competition withMinimumOrderQuantity(double minOrderQty) {
        this.minimumOrderQuantity = minOrderQty;
        return this;
    }

    /**
     * Start time of a sim session in the sim world. This is actually the start
     * of the bootstrap session, which is typically 15 days before the start of
     * a normal sim session.
     */
    public Instant getSimulationBaseTime() {
        return simulationBaseTime;
    }

    /**
     * Fluent setter for simulation base time that takes a String, interpreted
     * as a standard DateTimeFormat as yyyy-MM-dd. If that fails, try to parse
     * the string as a regular (long) timestamp.
     */
    @ConfigurableValue(valueType = "String", description = "Scenario start time of the bootstrap portion of a simulation")
    public Competition withSimulationBaseTime(String baseTime) {
        Instant instant;
        try {
            DateTimeZone.setDefault(DateTimeZone.UTC);
            DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd");
            instant = fmt.parseDateTime(baseTime).toInstant();
        } catch (IllegalArgumentException e) {
            // Try to interpret the string as a long timestamp instead
            instant = new Instant(Long.parseLong(baseTime));
        }
        return withSimulationBaseTime(instant);
    }

    /**
     * Fluent setter for simulation base time. This is the start of a simulation
     * scenario, in the sim world, at the beginning of a bootstrap session. So if
     * the bootstrap session collects data for 14 days, with an addional day of 
     * discarded data at the beginning, it is 15 days before the start of a
     * normal sim. 
     */
    public Competition withSimulationBaseTime(Instant simulationBaseTime) {
        return withSimulationBaseTime(simulationBaseTime.getMillis());
    }

    /**
     * Fluent setter for simulation base time that takes a long.
     */
    //  @ConfigurableValue(valueType = "Long",
    //    description = "Scenario start time of the bootstrap portion of a simulation")
    @StateChange
    public Competition withSimulationBaseTime(long baseTime) {
        this.simulationBaseTime = new Instant(baseTime);
        return this;
    }

    /**
     * Returns timezone offset for sim locale.
     */
    public int getTimezoneOffset() {
        return timezoneOffset;
    }

    /**
     * Fluent setter for timezone offset
     */
    @ConfigurableValue(valueType = "Integer", description = "Timezone offset from UTC for sim locale")
    @StateChange
    public Competition withTimezoneOffset(int offset) {
        this.timezoneOffset = offset;
        return this;
    }

    /**
     * Returns approximate latitude in degrees for sim locale.
     */
    public int getLatitude() {
        return latitude;
    }

    /**
     * Fluent setter for latitude value
     */
    @ConfigurableValue(valueType = "Integer", description = "Approximate latitude of sim locale")
    @StateChange
    public Competition withLatitude(int latitude) {
        this.latitude = latitude;
        return this;
    }

    /**
     * Number of timeslots in the bootstrap data report for a normal sim.
     */
    public int getBootstrapTimeslotCount() {
        return bootstrapTimeslotCount;
    }

    /**
     * Fluent setter for the bootstrap timeslot count. It only makes sense to
     * change this before running a bootstrap session.
     */
    @ConfigurableValue(valueType = "Integer", description = "Number of timeslots in bootstrap session during which data is collected")
    @StateChange
    public Competition withBootstrapTimeslotCount(int bootstrapTimeslotCount) {
        this.bootstrapTimeslotCount = bootstrapTimeslotCount;
        return this;
    }

    /**
     * Length of bootstrap interval in msec. Add this to the simulation base
     * time, and you get the start time for a normal sim session.
     */
    public int getBootstrapDiscardedTimeslots() {
        return bootstrapDiscardedTimeslots;
    }

    /**
     * Fluent setter for bootstrap interval.
     */
    @ConfigurableValue(valueType = "Integer", description = "Number of timeslots in bootstrap session that are discarded"
            + "before data collection begins")
    @StateChange
    public Competition withBootstrapDiscardedTimeslots(int count) {
        this.bootstrapDiscardedTimeslots = count;
        return this;
    }

    /**
     * The time-compression factor for the simulation.
     */
    public long getSimulationRate() {
        return simulationRate;
    }

    /**
     * Fluent setter for time compression ratio. Default value is 720, which
     * runs 1-hour timeslots in 5 real-time seconds.
     */
    @ConfigurableValue(valueType = "Integer", description = "Time compression ratio for simulation clock")
    @StateChange
    public Competition withSimulationRate(long simulationRate) {
        this.simulationRate = simulationRate;
        return this;
    }

    /**
     * Returns the number of seconds in wall-clock time per timeslot, truncated
     * to an integer.
     */
    public int getSimulationTimeslotSeconds() {
        return timeslotLength * 60 / (int) simulationRate;
    }

    /**
     * Fluent setter for controlling simulation rate by setting the number of
     * wall-clock seconds per timeslot. Only integer values are allowed.
     * Results may be strange if timeslotLength is changed after this is set.
     */
    @ConfigurableValue(valueType = "Integer", description = "Time compression ratio for simulation clock")
    public Competition withSimulationTimeslotSeconds(int seconds) {
        return withSimulationRate((long) (timeslotLength * 60 / seconds));
    }

    /**
     * Minimum value in milliseconds by which time advances in a simulation,
     * or in other words, the size of a clock tick. Normally it's
     * one timeslot. In the sim world, time is always at the beginning of
     * a clock tick.
     */
    public long getSimulationModulo() {
        return simulationModulo;
    }

    /**
     * Fluent setter for simulation modulo. Most likely, most server components
     * will not respond properly for values that are different from a timeslot
     * length. Default value is 3600000 msec.
     */
    @ConfigurableValue(valueType = "Long", description = "Size, in milliseconds, of a simulation clock tick."
            + "Normally, this is the same as a timeslot.")
    @StateChange
    public Competition withSimulationModulo(long simulationModulo) {
        this.simulationModulo = simulationModulo;
        return this;
    }

    /**
     * Returns the clock parameters for the start of a normal sim session
     * as a simple Map, to simplify code that
     * must mediate between Competition and TimeService instances. The computed
     * base time will be the base time of the bootstrap period plus the length
     * of the bootstrap period.
     */
    public Map<String, Long> getClockParameters() {
        Map<String, Long> result = new TreeMap<String, Long>();
        //long bootstrapOffset = getTimeslotDuration() *
        //                       (getBootstrapDiscardedTimeslots() +
        //                        getBootstrapTimeslotCount());
        //Instant simBase = 
        //    getSimulationBaseTime().plus(bootstrapOffset);
        result.put("base", getSimulationBaseTime().getMillis());
        result.put("rate", getSimulationRate());
        result.put("modulo", getSimulationModulo());
        return result;
    }

    /**
     * The Brokers who are participating in this Competion.
     */
    public List<String> getBrokers() {
        return brokers;
    }

    /**
     * Adds a broker to the Competition. This only makes sense in the server
     * environment.
     */
    @StateChange
    public Competition addBroker(String brokerUsername) {
        brokers.add(brokerUsername);
        return this;
    }

    /**
     * The list of customers (or more precisely, customer models) in the 
     * simulation environment.
     */
    public List<CustomerInfo> getCustomers() {
        return customers;
    }

    /**
     * Adds a customer to the Competition. This only makes sense in the server
     * environment.
     */
    @StateChange
    public Competition addCustomer(CustomerInfo customer) {
        customers.add(customer);
        return this;
    }

    /**
     * Updates selected fields of this Competition from a template. This is
     * designed to be used to copy attributes from a bootstrap run into a
     * normal sim run.
     */
    public void update(Competition template) {
        withBootstrapTimeslotCount(template.getBootstrapTimeslotCount());
        withDeactivateTimeslotsAhead(template.getDeactivateTimeslotsAhead());
        withSimulationBaseTime(template.getSimulationBaseTime());
        withBootstrapDiscardedTimeslots(template.getBootstrapDiscardedTimeslots());
        withSimulationModulo(template.getSimulationModulo());
        withTimeslotLength(template.getTimeslotLength());
        withTimeslotsOpen(template.getTimeslotsOpen());
    }

    @Override
    public String toString() {
        return name;
    }
}