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

org.powertac.balancemkt.BalancingMarketService Maven / Gradle / Ivy

There is a newer version: 1.9.0
Show newest version
/*
 * 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.balancemkt;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager;
import org.joda.time.Instant;
import org.powertac.common.config.ConfigurableValue;
import org.powertac.common.interfaces.Accounting;
import org.powertac.common.interfaces.BalancingMarket;
import org.powertac.common.interfaces.BrokerProxy;
import org.powertac.common.interfaces.CapacityControl;
import org.powertac.common.interfaces.InitializationService;
import org.powertac.common.interfaces.ServerConfiguration;
import org.powertac.common.Broker;
import org.powertac.common.Competition;
import org.powertac.common.Orderbook;
import org.powertac.common.RandomSeed;
import org.powertac.common.Timeslot;
import org.powertac.common.interfaces.TimeslotPhaseProcessor;
import org.powertac.common.msg.BalanceReport;
import org.powertac.common.msg.BalancingOrder;
import org.powertac.common.repo.BrokerRepo;
import org.powertac.common.repo.OrderbookRepo;
import org.powertac.common.repo.RandomSeedRepo;
import org.powertac.common.repo.TariffRepo;
import org.powertac.common.repo.TimeslotRepo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class BalancingMarketService
extends TimeslotPhaseProcessor
implements BalancingMarket, SettlementContext, InitializationService
{
  static Logger log = LogManager.getLogger(BalancingMarketService.class.getSimpleName());

  @Autowired
  private BrokerRepo brokerRepo;

  @Autowired
  private TimeslotRepo timeslotRepo;

  @Autowired
  private OrderbookRepo orderbookRepo;
  
  @Autowired
  private TariffRepo tariffRepo;

  @Autowired
  private BrokerProxy brokerProxyService;

  @Autowired
  private Accounting accountingService;

  @Autowired
  private CapacityControl capacityControlService;

  @Autowired
  private ServerConfiguration serverProps;

  @Autowired
  private RandomSeedRepo randomSeedService;
  private RandomSeed randomGen;

  @ConfigurableValue(valueType = "Double",
      description = "Low end of balancing cost range for simple settlement processor")
  private double balancingCostMin = -0.01;

  @ConfigurableValue(valueType = "Double",
      description = "High end of balancing cost range for simple settlement processor")
  private double balancingCostMax = -0.02;

  @ConfigurableValue(valueType = "Double",
      publish = true,
      description = "Balancing cost for simple settlement processor: overrides random value selection")
  private Double balancingCost = null;

  @ConfigurableValue(valueType = "Double",
          publish = true,
          description = "Slope of up-regulation cost /kwh")
  private double pPlusPrime = 0.0; // .00002/kwh

  @ConfigurableValue(valueType = "Double",
          publish = true,
          description = "Slope of down-regulation cost /kwh")
  private double pMinusPrime = 0.0; // -.00002/kwh

  @ConfigurableValue(valueType = "Double",
          publish = true,
          description = "Ratio of regulating-market price to spot price")
  private double rmPremium = 1.1; // 10% premium

  @ConfigurableValue(valueType = "Double",
      publish = true,
      description = "Spot price/mwh used if unavailable from wholesale market")
  private double defaultSpotPrice = 30.0; // per mwh
  
  @ConfigurableValue(valueType = "String",
          publish = true,
          description = "Balancing settlement processing: blank for no controllable capacity, "
          + "\"static\" for per-timeslot processing of balancing orders")
  private String settlementProcess = "";
  
  // map settlement process to strategy instances
  @SuppressWarnings("serial")
  private Map> settlementMap =
          new HashMap> () {{
            put("simple", SimpleSettlementProcessor.class);
            put("static", StaticSettlementProcessor.class);
            put("dynamic", DynamicSettlementProcessor.class);
          }};

  private Map balancingResults = null;

  /**
   * Computes actual distribution and balancing costs by random selection
   */
  @Override
  public String initialize (Competition competition, List completedInits)
  {
    super.init();
    balancingCost = null;

    serverProps.configureMe(this);

    // compute randomly-generated values if not overridden
    randomGen = randomSeedService.getRandomSeed("BalancingMarketService",
                                                0, "model");
    if (null == balancingCost)
      balancingCost = (balancingCostMin + randomGen.nextDouble()
                       * (balancingCostMax - balancingCostMin));
    log.info("Configured BM: balancing cost = " + balancingCost
             + ", (pPlus',pMinus') = (" + pPlusPrime + "," + pMinusPrime + ")");
    
    serverProps.publishConfiguration(this);
    return "BalancingMarket";
  }

  @Override
  public void activate (Instant time, int phaseNumber)
  {
    log.info("Activate");
    List brokerList = brokerRepo.findRetailBrokers();
    if (brokerList == null) {
      log.error("Failed to retrieve retail broker list");
      return;
    }

    // create the BalanceReport to carry the total imbalance
    Timeslot current = timeslotRepo.currentTimeslot();
    DoubleWrapper sum = makeDoubleWrapper();

    // Run the balancing market
    // Transactions are posted to the Accounting Service and Brokers are
    // notified of balancing transactions
    balancingResults = balanceTimeslot(brokerList, sum);

    // Send the balance report
    BalanceReport report = new BalanceReport(current.getSerialNumber(),
                                             sum.getValue());
    brokerProxyService.broadcastMessage(report);
  }

  /**
   * Generates a list of Transactions that balance the overall market.
   * Transactions are generated on a per-broker basis depending on the broker's
   * balance within its own market.
   * 
   * @return List of ChargeInfo instances
   */
  public Map balanceTimeslot (List brokerList,
                                                  DoubleWrapper report)
  {
    Map chargeInfoMap = new LinkedHashMap<>();

    // create the ChargeInfo instances for each broker
    for (Broker broker : brokerList) {
      double imbalance = getMarketBalance(broker);
      ChargeInfo info = new ChargeInfo(broker, imbalance);
      report.add(imbalance);
      chargeInfoMap.put(broker, info);
    }
    
    // retrieve and allocate the balancing orders
    Collection boc = tariffRepo.getBalancingOrders();
    for (BalancingOrder order : boc) {
      ChargeInfo info = chargeInfoMap.get(order.getBroker());
      info.addBalancingOrder(order);
    }

    // gather up the list of ChargeInfo instances and settle
    log.info("balancing prices: pPlus=" + getPPlus()
             + ", pMinus=" + getPMinus());
    List brokerData = new ArrayList<>(chargeInfoMap.values());
    getSettlementProcessor().settle(this, brokerData);
    
    // add balancing transactions - note that debits/credits for balancing
    // orders (p2 values) will already have been posted in the process of
    // exercising orders.
    for (ChargeInfo info : brokerData) {
      double balanceCharge = info.getBalanceChargeP1();
      if (balanceCharge != 0.0) {
        accountingService.addBalancingTransaction(info.getBroker(),
                                                  info.getNetLoadKWh(),
                                                  balanceCharge);
      }
    }
    return chargeInfoMap;
  }

  /**
   * Returns the difference between a broker's current market position and its
   * net load. Note: market position is computed in MWh and net load is computed
   * in kWh, conversion is needed to compute the difference in kWh.
   * 
   * @return a broker's current energy balance within its market. Pos for
   *         over-production, neg for under-production
   */
  @Override
  public double getMarketBalance (Broker broker)
  {
    double result = accountingService.getCurrentMarketPosition(broker) * 1000.0
                    + accountingService.getCurrentNetLoad(broker);
    log.info("market balance for " + broker.getUsername() + ": " + result);
    return result;
  }

  /**
   * Returns the net balancing result for a given broker. Valid only after
   * service activation within a given timeslot.
   */
  @Override
  public double getRegulation (Broker broker)
  {
    ChargeInfo ci = balancingResults.get(broker);
    if (null == ci) {
      log.error("Null balancing result for broker " + broker.getUsername());
      return 0.0;
    }
    return ci.getCurtailment();
  }

  /**
   * Returns the spot market price per kWh -
   * This is the most recent valid clearing price for the current timeslot.
   */
  double getSpotPrice ()
  {
    Double result = defaultSpotPrice;
    // most recent trade is determined by Competition parameters
    // orderbooks have timeslot and execution time
    Orderbook ob =
        orderbookRepo.findSpotByTimeslot(timeslotRepo.currentTimeslot());
    if (ob != null) {
      result = ob.getClearingPrice();
    }
    else {
      log.info("null Orderbook");
    }
    return result / 1000.0; // convert to kwh
  }

  /**
   * Returns the zero-quantity price for up-regulation energy
   * in the current timeslot. Units are per kWh
   */
  @Override
  public double getPPlus ()
  {
    double result = defaultSpotPrice;
    List obs = 
        orderbookRepo.findAllByTimeslot(timeslotRepo.currentTimeslot());
    if (obs != null && obs.size() > 0) {
      Double max = null;
      for (Orderbook ob : obs) {
        Double price = ob.getClearingPrice();
        if (price != null && (max == null || price > max))
          max = price;
      }
      if (max != null)
        result = max;
    }
    return result * rmPremium / 1000.0;
  }

  /**
   * Returns the zero-quantity price for down-regulation energy
   * in the current timeslot. Price is per kWh.
   */
  @Override
  public double getPMinus ()
  {
    double result = defaultSpotPrice;
    List obs = 
        orderbookRepo.findAllByTimeslot(timeslotRepo.currentTimeslot());
    if (obs != null && obs.size() > 0) {
      Double min = null;
      for (Orderbook ob : obs) {
        Double price = ob.getClearingPrice();
        if (price != null && (min == null || price < min))
          min = price;
      }
      if (min != null)
        result = min;
    }
    return -result / rmPremium / 1000.0;
  }

  /**
   * Returns the slope of up-regulation energy purchased through the
   * wholesale ancillary services market.
   */
  @Override
  public double getPPlusPrime ()
  {
    return pPlusPrime;
  }

  /**
   * Returns the slope of down-regulation energy sold through the
   * wholesale ancillary services market.
   */
  @Override
  public double getPMinusPrime ()
  {
    return pMinusPrime;
  }

  // ---------- Getters and setters for settlement processsors ---------

  double getBalancingCostMin ()
  {
    return balancingCostMin;
  }

  double getBalancingCostMax ()
  {
    return balancingCostMax;
  }

  @Override
  public Double getBalancingCost ()
  {
    return balancingCost;
  }

  @Override
  public double getDefaultSpotPrice ()
  {
    return defaultSpotPrice;
  }

  private SettlementProcessor getSettlementProcessor ()
  {
    // determine and record settlement process
    if (settlementProcess.equals(""))
      settlementProcess = "simple";
    Class processor = settlementMap.get(settlementProcess);
    if (null == processor) {
      log.error("Null settlement processor for " + settlementProcess);
      processor = settlementMap.get("simple");
    }
    SettlementProcessor result = null;
    try {
      Constructor constructor =
              processor.getDeclaredConstructor(TariffRepo.class,
                                               CapacityControl.class);
      result = (SettlementProcessor) constructor.newInstance(tariffRepo, capacityControlService);
    }
    catch (Exception e) {
      log.error("cannot create settlement processor: " + e.toString());
    }
    return result;
  }

  // test support
  double getRmPremium ()
  {
    return rmPremium;
  }

  void setRmPremium (double value)
  {
    rmPremium = value;
  }

  /**
   * Mutable double to support computation of total imbalance
   * @author jcollins
   */
  class DoubleWrapper
  {
    double value = 0.0;

    DoubleWrapper()
    {
      super();
    }

    double add (double addend)
    {
      value += addend;
      return value;
    }

    double getValue ()
    {
      return value;
    }
  }

  // needed for testing
  DoubleWrapper makeDoubleWrapper()
  {
    return new DoubleWrapper();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy