
org.powertac.distributionutility.StaticSettlementProcessor Maven / Gradle / Ivy
/*
* Copyright (c) 2012 by the original author
*
* 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.distributionutility;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.powertac.common.Tariff;
import org.powertac.common.interfaces.CapacityControl;
import org.powertac.common.msg.BalancingOrder;
import org.powertac.common.repo.TariffRepo;
import org.powertac.util.Predicate;
/**
* DU settlement processor for Scenario 2 - controllable capacities,
* one-shot static solution
* @author
*/
public class StaticSettlementProcessor extends SettlementProcessor
{
private double epsilon = 1e-6; // 1 milliwatt-hour
//private SettlementContext service;
double pPlus, pMinus;
double pPlusPrime, pMinusPrime;
StaticSettlementProcessor (TariffRepo tariffRepo, CapacityControl capacityControl)
{
super(tariffRepo, capacityControl);
}
/* (non-Javadoc)
* @see org.powertac.distributionutility.SettlementProcessor#settle(java.util.Collection)
*/
@Override
public void settle (SettlementContext service,
List brokerData)
{
//this.service = service;
pPlus = service.getPPlus();
pPlusPrime = service.getPPlusPrime();
pMinus = service.getPMinus();
pMinusPrime = service.getPMinusPrime();
// find total imbalance
double totalImbalance = 0.0;
double totalQty = 0.0;
for (ChargeInfo info : brokerData) {
totalImbalance += info.getNetLoadKWh();
totalQty += Math.abs(info.getNetLoadKWh());
}
log.debug("totalImbalance=" + totalImbalance);
// fudge to prevent divide-by-zero errors
if (Math.abs(totalImbalance) < epsilon) {
if (totalQty < epsilon)
// nothing to settle; just return
return;
totalImbalance = epsilon * totalQty;
}
// get balancing orders on correct side of imbalance, sort by price.
// Negative total imbalance means we want to curtail consumption.
SortedSet candidates =
filterAndSort(brokerData, totalImbalance);
// get curtailable usage for each order.
for (BOWrapper bo : candidates) {
bo.availableCapacity =
capacityControlService.getCurtailableUsage(bo.balancingOrder);
}
// insert dummy orders to represent available balancing power through
// the wholesale regulating market.
insertDummyOrders(candidates, totalImbalance);
// determine the set that will be exercised.
double satisfied = determineExerciseSet(totalImbalance, candidates);
BOWrapper lastExercised = candidates.first();
for (BOWrapper bow : candidates) {
if (0.0 == bow.exercisedCapacity)
break;
lastExercised = bow;
if (Math.abs(bow.availableCapacity - bow.exercisedCapacity) > 0.0)
// this one is partially exercised
break;
}
// lastExercised should not be null
if (null == lastExercised) {
log.warn("unable to settle: lastExercised is null");
return;
}
// compute VCG charges (p_2) by broker.
//SortedSet nonExercised = candidates.tailSet(lastExercised);
computeVcgCharges(brokerData, totalImbalance, candidates);
// Determine imbalance payments (p_1) for each broker.
computeImbalanceCharges(brokerData, totalImbalance, candidates);
}
// Produces the sorted list of balancing orders that are candidates
// to be exercised.
private SortedSet filterAndSort (List brokerData,
double totalImbalance)
{
TreeSet orders =
new TreeSet (new Comparator () {
@Override
public int compare (BOWrapper b0,
BOWrapper b1) {
if (b0 == b1)
return 0;
if (b0.price < b1.price)
return -1;
return 1;
}
});
Predicate tester;
if (totalImbalance < 0.0) {
tester = new Predicate() {
@Override
public boolean apply (BalancingOrder bo)
{
Tariff tariff = tariffRepo.findTariffById(bo.getTariffId());
return (tariff.getPowerType().isConsumption());
}
};
}
else {
tester = new Predicate() {
@Override
public boolean apply (BalancingOrder bo)
{
Tariff tariff = tariffRepo.findTariffById(bo.getTariffId());
return (tariff.getPowerType().isProduction());
}
};
}
for (ChargeInfo info : brokerData) {
List balancingOrders = info.getBalancingOrders();
if (null != balancingOrders && balancingOrders.size() > 0) {
for (BalancingOrder bo : info.getBalancingOrders()) {
if (tester.apply(bo)) {
BOWrapper bow = new BOWrapper(info, bo);
orders.add(bow);
}
}
}
}
return orders;
}
// Inserts orders into the candidate list derived from the regulating
// market. This requires orders for both shortage and surplus
private void insertDummyOrders (SortedSet orders,
double totalImbalance)
{
// first, make a single dummy order and insert it
double price = pPlus;
double slope = pPlusPrime;
if (totalImbalance >= 0.0) {
// we are in surplus; exercise production curtailments
price = pMinus;
slope = pMinusPrime;
}
BOWrapper dummy =
new BOWrapper(-totalImbalance, price, slope);
orders.add(dummy);
// split the dummy order around the following order, if there is one
// and if the slope is non-zero
if (dummy.slope != 0.0) {
splitDummyOrder(orders, orders.tailSet(dummy));
}
}
// Splits the dummy order around a higher-priced following order
private void splitDummyOrder (SortedSet orders,
SortedSet tail)
{
if (tail.size() <= 1)
// we're done -- dummy order is last
return;
// at this point, we have a dummy order at some price, followed by
// at least one "real" order. The dummy order must be split in two at
// the point where its price matches the price of the following order.
Iterator bos = tail.iterator();
BOWrapper dummy = bos.next();
BOWrapper nextBO = bos.next();
double capacity = (nextBO.price - dummy.price) / dummy.slope;
// there are now three possibilities:
// - capacity has the opposite sign from remaining capacity, which is
// an error; or
if (Math.signum(capacity) != Math.signum(dummy.availableCapacity)) {
log.error("Sign of needed capacity " + capacity +
" != sign of dummy avail capacity " + dummy.availableCapacity);
}
// - capacity is at least as large as remaining capacity of dummy order,
// in which case we are finished; or
else if (Math.abs(capacity) >= Math.abs(dummy.availableCapacity)) {
return;
}
// - capacity is smaller than remaining capacity of the dummy order,
// in which case we split the dummy around the following order.
else {
BOWrapper newDummy = new BOWrapper(dummy.availableCapacity - capacity,
nextBO.price + epsilon,
dummy.slope);
dummy.availableCapacity = capacity;
orders.add(newDummy);
splitDummyOrder(orders, orders.tailSet(newDummy));
}
}
// Finds the set of balancing orders to be exercised, fills in their
// exercised capacity, returns the total imbalance that is satisfied by
// the balancing orders.
private double determineExerciseSet (double totalImbalance,
SortedSet candidates)
{
double remainingImbalance = totalImbalance;
double sgn = Math.signum(totalImbalance);
for (BOWrapper bo : candidates) {
if (sgn * remainingImbalance <= 0.0)
break;
double exercise = Math.min(sgn * remainingImbalance,
-sgn * bo.availableCapacity);
bo.exercisedCapacity = -sgn * exercise;
log.debug("exercising order " + bo.toString()
+ " for " + bo.exercisedCapacity);
remainingImbalance -= sgn * exercise;
}
return totalImbalance - remainingImbalance;
}
// Computes VCG charge (p_2) for each broker.
private void computeVcgCharges (List brokerData,
double totalImbalance,
SortedSet candidates)
{
HashSet nonParticipants = new HashSet();
for (ChargeInfo info : brokerData) {
nonParticipants.add(info);
double newMC = computeMarginalPrice(totalImbalance,
candidates,
nonParticipants);
nonParticipants.remove(info);
info.setBalanceChargeP2(exerciseControls(info, candidates, newMC));
}
}
// Computes imbalance costs for each broker. This is
// VCG(C,X)/X * x
// where
// X is the total imbalance,
// VCG(C,X) is the sum of VCG payments to other brokers,
// except that for brokers whose imbalance has the same sign as the
// total imbalance it excludes balancing orders from brokers whose
// imbalance has the opposite sign, plus the additional cost of
// external regulating power.
// x is the broker's individual imbalance.
private void computeImbalanceCharges (List brokerData,
double totalImbalance,
SortedSet candidates)
{
HashSet contributors = new HashSet();
HashSet nonContributors = new HashSet();
double sgn = Math.signum(totalImbalance);
for (ChargeInfo info : brokerData) {
if (sgn != Math.signum(info.getNetLoadKWh())) {
// broker is on the other side of the balance
nonContributors.add(info);
}
else
contributors.add(info);
}
// get the quantity of regulating power
double rpQty = 0.0;
for (BOWrapper bid : candidates) {
if (bid.isDummy())
rpQty += bid.exercisedCapacity;
}
// Do the contributors - the brokers on the imbalance side
for (ChargeInfo info : contributors) {
nonContributors.add(info);
double originalMC = computeMarginalPrice(totalImbalance,
candidates,
nonContributors);
double p1 = originalMC * (-rpQty - getExercisedCapacity(info, candidates));
for (ChargeInfo other : contributors) {
if (other == info)
continue;
nonContributors.add(other);
double newMC = computeMarginalPrice(totalImbalance,
candidates,
nonContributors);
nonContributors.remove(other);
p1 -= newMC * getExercisedCapacity(other, candidates);
}
nonContributors.remove(info);
info.setBalanceChargeP1(p1 * info.getNetLoadKWh() / totalImbalance);
}
// handle the no-imbalance case
// if (Math.abs(totalImbalance) < epsilon) {
// double penalty = pPlus;
// if (totalImbalance < 0.0)
// penalty = pMinus;
// for (ChargeInfo info : nonContributors) {
// info.setBalanceChargeP1(penalty * info.getNetLoadKWh());
// }
// }
// else {
// do the non-contributors
HashSet excludes = new HashSet();
for (ChargeInfo info : nonContributors) {
excludes.add(info);
double originalMC = computeMarginalPrice(totalImbalance,
candidates,
excludes);
double p1 = originalMC * (-rpQty - getExercisedCapacity(info, candidates));
for (ChargeInfo other : brokerData) {
if (other == info)
continue;
excludes.add(other);
double newMC = computeMarginalPrice(totalImbalance,
candidates,
nonContributors);
excludes.remove(other);
p1 -= newMC * getExercisedCapacity(other, candidates);
}
excludes.remove(info);
info.setBalanceChargeP1(p1 * info.getNetLoadKWh() / totalImbalance);
}
// }
}
private double computeMarginalPrice (double imbalance,
SortedSet candidates,
Set exclude)
{
double result = 0.0;
double sgn = Math.signum(imbalance); // neg for deficit
double remaining = -imbalance;
for (BOWrapper bow : candidates) {
if ((null == exclude) || !(exclude.contains(bow.info))) {
if (sgn * bow.getCapacity() < (sgn * remaining + epsilon)) {
// this is the last one
result = bow.getMarginalPrice(remaining);
break;
}
else {
remaining -= bow.getCapacity();
}
}
}
return result;
}
private double exerciseControls (ChargeInfo broker,
SortedSet candidates,
double marginalPrice)
{
double result = 0.0;
for (BOWrapper candidate: candidates) {
if (candidate.info == broker && 0.0 != candidate.exercisedCapacity) {
capacityControlService.exerciseBalancingControl(candidate.balancingOrder,
candidate.exercisedCapacity,
candidate.exercisedCapacity * marginalPrice);
result += candidate.exercisedCapacity * marginalPrice;
}
}
return result;
}
private double getExercisedCapacity (ChargeInfo broker,
SortedSet candidates)
{
double result = 0.0;
for (BOWrapper candidate: candidates) {
if (candidate.info == broker) {
result += candidate.exercisedCapacity;
}
}
return result;
}
// wrapper class for tracking order status
class BOWrapper
{
ChargeInfo info= null;
BalancingOrder balancingOrder = null;
double availableCapacity = 0.0;
double exercisedCapacity = 0.0;
double price = 0.0;
double slope = 0.0;
// construct one from a BalancingOrder
BOWrapper (ChargeInfo info, BalancingOrder bo)
{
super();
this.info = info;
this.balancingOrder = bo;
this.price = bo.getPrice();
}
// construct an intermediate dummy
BOWrapper (double availableCapacity, double price)
{
super();
this.availableCapacity = availableCapacity;
this.price = price;
}
// construct a final dummy, with a non-zero slope
BOWrapper (double availableCapacity, double price, double slope)
{
super();
this.availableCapacity = availableCapacity;
this.price = price;
this.slope = slope;
}
// Dummy orders don't wrap balancing orders.
boolean isDummy ()
{
return (null == balancingOrder);
}
// Returns the total capacity
double getCapacity ()
{
return availableCapacity;
}
// Returns the marginal price for using qty from the order
double getMarginalPrice (double qty)
{
return price + slope * qty;
}
@Override
public String toString ()
{
if (null == balancingOrder)
return "Dummy";
else
return (balancingOrder.getBroker().getUsername()
+ ":" + balancingOrder.getTariffId());
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy