All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.powertac.genco.CpGenco Maven / Gradle / Ivy

/*
 * 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 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 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 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 newCoeff = new ArrayList();
    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 getCoefficients ()
  {
    ArrayList result = new ArrayList();
    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 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;

    final double x0 = 25; //20; // 10; // 30; // 0;// 35; //50;

    boolean validateCoefficients (List 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)
    {
      // store
      double aOrig = a;
      double bOrig = b;
      double cOrig = c;
      double priceIntervalOrig = priceInterval;

      if (startX >= x0) {
        scaleCoefficients(); 
      }


      double endX =
        -b / (2.0 * a)
            + Math.sqrt(b * b
                        + 4.0 * a * (a * startX * startX
                                     + b * startX
                                     + priceInterval)) / (2.0 * a);

      
      // restore
      a = aOrig;
      b = bOrig;
      c = cOrig;
      priceInterval = priceIntervalOrig;

      return (endX - startX);
    }

    // returns a price given a qty
    double getY (double x)
    {
      // store
      double aOrig = a;
      double bOrig = b;
      double cOrig = c;
      double priceIntervalOrig = priceInterval;
    
      if (x >= x0) {
        scaleCoefficients(); 
      }
    
      double result = (a * x * x + b * x + c);
    
      // restore
      a = aOrig;
      b = bOrig;
      c = cOrig;
      priceInterval = priceIntervalOrig;
      return result;
    }

    private void scaleCoefficients() {
      double y0 = a * x0 * x0 + b * x0 + c;
      a *= 5; // 4; // 3; // 4; // 3; // 2;
      b *= 5; // 4; // 3; // 4; // 3; // 2;
      c = y0 - a * x0 * x0 - b * x0;
      priceInterval *= 2;
    }
  }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy