
org.powertac.factoredcustomer.DefaultCapacityOriginator Maven / Gradle / Ivy
/*
* Copyright 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.factoredcustomer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.joda.time.DateTime;
import org.powertac.common.Tariff;
import org.powertac.common.TariffSubscription;
import org.powertac.common.TimeService;
import org.powertac.common.WeatherForecast;
import org.powertac.common.WeatherForecastPrediction;
import org.powertac.common.WeatherReport;
import org.powertac.common.enumerations.PowerType;
import org.powertac.common.repo.TimeslotRepo;
import org.powertac.common.repo.WeatherForecastRepo;
import org.powertac.common.repo.WeatherReportRepo;
import org.powertac.common.state.Domain;
import org.powertac.factoredcustomer.CapacityStructure.BaseCapacityType;
import org.powertac.factoredcustomer.CapacityStructure.InfluenceKind;
import org.powertac.factoredcustomer.interfaces.CapacityBundle;
import org.powertac.factoredcustomer.interfaces.CapacityOriginator;
import org.powertac.factoredcustomer.interfaces.StructureInstance;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Key class responsible for drawing from a base capacity and ajusting that
* capacity in response to various static and dynamic factors for each timeslot.
*
* @author Prashant Reddy
*/
@Domain
class DefaultCapacityOriginator implements CapacityOriginator
{
private static Logger log = LogManager.getLogger(DefaultCapacityOriginator.class);
private TimeService timeService;
private TimeslotRepo timeslotRepo;
private WeatherReportRepo weatherReportRepo;
private WeatherForecastRepo weatherForecastRepo;
private final double SMOOTHING_WEIGHT = 0.4; // 0.0 => ignore previous value
private final TimeseriesGenerator tsGenerator;
private final CapacityStructure capacityStructure;
private final CapacityBundle parentBundle;
protected final String logIdentifier;
protected final Map baseCapacities = new HashMap<>();
protected final Map forecastCapacities = new HashMap<>();
protected final Map actualCapacities = new HashMap<>();
protected final Map curtailedCapacities = new HashMap<>();
protected final Map shiftedCurtailments = new HashMap<>();
public DefaultCapacityOriginator (FactoredCustomerService service,
CapacityStructure capacityStructure,
CapacityBundle bundle)
{
this.timeService = service.getTimeService();
this.timeslotRepo = service.getTimeslotRepo();
this.weatherReportRepo = service.getWeatherReportRepo();
this.weatherForecastRepo = service.getWeatherForecastRepo();
this.capacityStructure = capacityStructure;
this.parentBundle = bundle;
logIdentifier = this.capacityStructure.getName().isEmpty()
? bundle.getName()
: bundle.getName() + "#" + this.capacityStructure.getName();
if (capacityStructure.getBaseCapacityType() == BaseCapacityType.TIMESERIES) {
Map map =
Config.getInstance().getStructures().get("TimeseriesGenerator");
tsGenerator = (TimeseriesGenerator)
map.get(capacityStructure.getName() + "Population");
if (tsGenerator != null) {
tsGenerator.initialize(service);
}
}
else {
tsGenerator = null;
}
}
@Override
public CapacityProfile getCurrentForecast ()
{
int timeslot = timeslotRepo.currentSerialNumber();
return getForecastForTimeslot(timeslot);
}
@Override
public CapacityProfile getForecastForNextTimeslot ()
{
int timeslot = timeslotRepo.currentSerialNumber();
return getForecastForTimeslot(timeslot + 1);
}
private CapacityProfile getForecastForTimeslot (int timeslot)
{
List values = new ArrayList<>();
for (int i = 0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
Double forecastCapacity = forecastCapacities.get(timeslot);
if (forecastCapacity != null) {
values.add(forecastCapacity);
}
else {
values.add(getForecastCapacity(timeslot));
}
timeslot += 1;
}
return new CapacityProfile(values);
}
@Override
public CapacityProfile getCurrentForecastPerSub (TariffSubscription sub)
{
// DefaultCapacityOriginator doesn't track subscriptions, so:
return getCurrentForecast();
}
@Override
public CapacityProfile getForecastPerSubStartingAt (
int startingTimeslot, TariffSubscription subscription)
{
return getForecastForTimeslot(startingTimeslot);
}
protected double getForecastCapacity (int timeslot)
{
Double ret = forecastCapacities.get(timeslot);
if (ret == null) {
ret = computeForecastCapacity(timeslot);
}
return ret;
}
private double computeForecastCapacity (int future)
{
int now = timeslotRepo.currentSerialNumber();
int timeToFuture = future - now;
Weather weather = null;
if (timeToFuture == 0) {
weather =
new Weather(weatherReportRepo.currentWeatherReport());
}
else {
WeatherForecast forecast = weatherForecastRepo.currentWeatherForecast();
List predictions = forecast.getPredictions();
for (WeatherForecastPrediction prediction : predictions) {
if (prediction.getForecastTime() == timeToFuture) {
weather = new Weather(prediction);
}
}
}
if (weather == null) {
throw new Error("Could not find weather forecast for timeslot " + future);
}
double baseCapacity = getBaseCapacity(future);
if (Double.isNaN(baseCapacity)) {
throw new Error("Base capacity is NaN!");
}
// Compute for full population ignoring current tariff rates
double forecastCapacity = baseCapacity;
forecastCapacity =
adjustCapacityForPeriodicSkew(forecastCapacity,
timeslotRepo.getDateTimeForIndex(future),
false);
forecastCapacity =
adjustCapacityForWeather(forecastCapacity, weather, false);
if (Double.isNaN(forecastCapacity)) {
throw new Error("Adjusted capacity is NaN for base capacity = "
+ baseCapacity);
}
forecastCapacity = truncateTo2Decimals(forecastCapacity);
forecastCapacities.put(future, forecastCapacity);
log.debug(logIdentifier + ": Daniel Forecast capacity for timeslot " + future
+ " = " + forecastCapacity);
return forecastCapacity;
}
private double getBaseCapacity (int future)
{
Double ret = baseCapacities.get(future);
if (ret == null) {
ret = drawBaseCapacitySample(future);
}
return ret;
}
private double drawBaseCapacitySample (int timeslot)
{
double baseCapacity = 0.0;
switch (capacityStructure.getBaseCapacityType()) {
case POPULATION:
baseCapacity = capacityStructure.getBasePopulationCapacity().drawSample();
break;
case INDIVIDUAL:
for (int i = 0; i < parentBundle.getPopulation(); ++i) {
baseCapacity +=
capacityStructure.getBaseIndividualCapacity().drawSample();
}
break;
case TIMESERIES:
baseCapacity = getBaseCapacityFromTimeseries(timeslot);
break;
default:
throw new Error(logIdentifier + ": Unexpected base capacity type: "
+ capacityStructure.getBaseCapacityType());
}
Double prevCapacity = baseCapacities.get(timeslot - 1);
if (prevCapacity != null) {
baseCapacity =
SMOOTHING_WEIGHT * prevCapacity + (1 - SMOOTHING_WEIGHT) * baseCapacity;
}
baseCapacity = truncateTo2Decimals(baseCapacity);
baseCapacities.put(timeslot, baseCapacity);
return baseCapacity;
}
private double getBaseCapacityFromTimeseries (int timeslot)
{
try {
return tsGenerator.generateNext(timeslot);
}
catch (ArrayIndexOutOfBoundsException e) {
log.error(logIdentifier
+ ": Tried to get base capacity from time series at index beyond maximum!");
throw e;
}
}
@Override
public double getShiftingInconvenienceFactor (Tariff tariff)
{
return 0; // not shifting should take place in DefaultCapacityOriginator
}
@Override
public double useCapacity (TariffSubscription subscription)
{
int timeslot = timeslotRepo.currentSerialNumber();
double baseCapacity = getBaseCapacity(timeslot);
if (Double.isNaN(baseCapacity)) {
throw new Error("Base capacity is NaN!");
}
logCapacityDetails(logIdentifier + ": Base capacity for timeslot "
+ timeslot + " = " + baseCapacity);
// total adjusted capacity
double adjustedCapacity = baseCapacity;
if (parentBundle.getPowerType().isInterruptible()) {
adjustedCapacity =
adjustCapacityForCurtailments(timeslot, adjustedCapacity, subscription);
}
adjustedCapacity =
adjustCapacityForPeriodicSkew(adjustedCapacity,
timeService.getCurrentDateTime(), true);
adjustedCapacity = adjustCapacityForCurrentWeather(adjustedCapacity, true);
// capacity for this subscription
adjustedCapacity =
adjustCapacityForSubscription(timeslot, adjustedCapacity, subscription);
if (Double.isNaN(adjustedCapacity)) {
throw new Error("Adjusted capacity is NaN for base capacity = "
+ baseCapacity);
}
adjustedCapacity = truncateTo2Decimals(adjustedCapacity);
actualCapacities.put(timeslot, adjustedCapacity);
log.info(logIdentifier + ": Adjusted capacity for tariff "
+ subscription.getTariff().getId() + " = " + adjustedCapacity);
return adjustedCapacity;
}
private double adjustCapacityForCurtailments (int timeslot, double capacity,
TariffSubscription subscription)
{
double lastCurtailment = subscription.getCurtailment();
if (Math.abs(lastCurtailment) > 0.01) { // != 0
curtailedCapacities.put(timeslot - 1, lastCurtailment);
List shifts = capacityStructure.getCurtailmentShifts();
for (int i = 0; i < shifts.size(); ++i) {
double shiftingFactor = Double.parseDouble(shifts.get(i));
double shiftedCapacity = lastCurtailment * shiftingFactor;
Double previousShifts = shiftedCurtailments.get(timeslot + i);
shiftedCapacity += (previousShifts != null) ? previousShifts : 0;
shiftedCurtailments.put(timeslot + i, shiftedCapacity);
}
}
Double currentShift = shiftedCurtailments.get(timeslot);
return (currentShift == null) ? capacity : capacity + currentShift;
}
private double adjustCapacityForPeriodicSkew (double capacity, DateTime when,
boolean verbose)
{
int day = when.getDayOfWeek(); // 1=Monday, 7=Sunday
int hour = when.getHourOfDay(); // 0-23
double periodicSkew = capacityStructure.getPeriodicSkew(day, hour);
if (verbose) {
logCapacityDetails(logIdentifier + ": periodic skew = " + periodicSkew);
}
return capacity * periodicSkew;
}
private double adjustCapacityForCurrentWeather (double capacity,
boolean verbose)
{
WeatherReport weatherReport = weatherReportRepo.currentWeatherReport();
return adjustCapacityForWeather(capacity, new Weather(weatherReport),
verbose);
}
private double adjustCapacityForWeather (double capacity, Weather weather,
boolean verbose)
{
if (verbose) {
logCapacityDetails(logIdentifier + ": weather = ("
+ weather.getTemperature() + ", "
+ weather.getWindSpeed() + ", "
+ weather.getWindDirection() + ", "
+ weather.getCloudCover() + ")");
}
double weatherFactor = 1.0;
if (capacityStructure.getTemperatureInfluence() == InfluenceKind.DIRECT) {
int temperature = (int) Math.round(weather.getTemperature());
weatherFactor =
weatherFactor * capacityStructure.getTemperatureFactor(temperature);
}
else if (capacityStructure.getTemperatureInfluence() == InfluenceKind.DEVIATION) {
int curr = (int) Math.round(weather.getTemperature());
int ref = (int) Math.round(capacityStructure.getTemperatureReference());
double deviationFactor = 1.0;
if (curr > ref) {
for (int t = ref + 1; t <= curr; ++t) {
deviationFactor += capacityStructure.getTemperatureFactor(t);
}
}
else if (curr < ref) {
for (int t = curr; t < ref; ++t) {
deviationFactor += capacityStructure.getTemperatureFactor(t);
}
}
weatherFactor = weatherFactor * deviationFactor;
}
if (capacityStructure.getWindSpeedInfluence() == InfluenceKind.DIRECT) {
int windSpeed = (int) Math.round(weather.getWindSpeed());
weatherFactor = weatherFactor *
capacityStructure.getWindspeedFactor(windSpeed);
if (windSpeed > 0.0
&& capacityStructure.getWindDirectionInfluence() == InfluenceKind.DIRECT) {
int windDirection = (int) Math.round(weather.getWindDirection());
weatherFactor = weatherFactor *
capacityStructure.getWindDirectionFactor(windDirection);
}
}
if (capacityStructure.getCloudCoverInfluence() == InfluenceKind.DIRECT) {
int cloudCover = (int) Math.round(100 * weather.getCloudCover()); // [0,1]
// to
// ##%
weatherFactor =
weatherFactor * capacityStructure.getCloudCoverFactor(cloudCover);
}
if (verbose) {
logCapacityDetails(logIdentifier + ": weather factor = " + weatherFactor);
}
return capacity * weatherFactor;
}
@Override
public double adjustCapacityForSubscription (int timeslot,
double totalCapacity,
TariffSubscription subscription)
{
double subCapacity =
adjustCapacityForPopulationRatio(totalCapacity, subscription);
return adjustCapacityForTariffRates(timeslot, subCapacity, subscription);
}
private double adjustCapacityForPopulationRatio (
double capacity, TariffSubscription subscription)
{
double popRatio =
getPopulationRatio(subscription.getCustomersCommitted(),
parentBundle.getPopulation());
logCapacityDetails(logIdentifier + ": population ratio = " + popRatio);
return capacity * popRatio;
}
private double getPopulationRatio (int customerCount, int population)
{
return ((double) customerCount) / ((double) population);
}
private double adjustCapacityForTariffRates (
int timeslot, double baseCapacity, TariffSubscription subscription)
{
if ((baseCapacity - 0.0) < 0.01) {
return baseCapacity;
}
double chargeForBase =
subscription.getTariff().getUsageCharge(
timeslotRepo.getTimeForIndex(timeslot),
baseCapacity,
subscription.getTotalUsage());
double rateForBase = chargeForBase / baseCapacity;
double benchmarkRate =
capacityStructure.getBenchmarkRate(timeService.getHourOfDay());
double rateRatio = rateForBase / benchmarkRate;
double tariffRatesFactor = determineTariffRatesFactor(rateRatio);
logCapacityDetails(logIdentifier + ": tariff rates factor = "
+ tariffRatesFactor);
return baseCapacity * tariffRatesFactor;
}
private double determineTariffRatesFactor (double rateRatio)
{
switch (capacityStructure.getElasticityModelType()) {
case CONTINUOUS:
return capacityStructure.determineContinuousElasticityFactor(rateRatio);
case STEPWISE:
return determineStepwiseElasticityFactor(rateRatio);
default:
throw new Error("Unexpected elasticity model type: "
+ capacityStructure.getElasticityModelType());
}
}
private double determineStepwiseElasticityFactor (double rateRatio)
{
double[][] elasticity = capacityStructure.getElasticity();
if (Math.abs(rateRatio - 1) < 0.01 || elasticity.length == 0) {
return 1.0;
}
PowerType powerType = parentBundle.getPowerType();
if (powerType.isConsumption() && rateRatio < 1.0) {
return 1.0;
}
if (powerType.isProduction() && rateRatio > 1.0) {
return 1.0;
}
final int RATE_RATIO_INDEX = 0;
final int CAPACITY_FACTOR_INDEX = 1;
double rateLowerBound = Double.NEGATIVE_INFINITY;
double rateUpperBound = Double.POSITIVE_INFINITY;
double lowerBoundCapacityFactor = 1.0;
double upperBoundCapacityFactor = 1.0;
for (double[] anElasticity : elasticity) {
double r = anElasticity[RATE_RATIO_INDEX];
if (r <= rateRatio && r > rateLowerBound) {
rateLowerBound = r;
lowerBoundCapacityFactor = anElasticity[CAPACITY_FACTOR_INDEX];
}
if (r >= rateRatio && r < rateUpperBound) {
rateUpperBound = r;
upperBoundCapacityFactor = anElasticity[CAPACITY_FACTOR_INDEX];
}
}
return (rateRatio < 1) ? upperBoundCapacityFactor : lowerBoundCapacityFactor;
}
@Override
public String getCapacityName ()
{
return capacityStructure.getName();
}
@Override
public CapacityBundle getParentBundle ()
{
return parentBundle;
}
protected double truncateTo2Decimals (double x)
{
double fract, whole;
if (x > 0) {
whole = Math.floor(x);
fract = Math.floor((x - whole) * 100) / 100;
}
else {
whole = Math.ceil(x);
fract = Math.ceil((x - whole) * 100) / 100;
}
return whole + fract;
}
private void logCapacityDetails (String msg)
{
if (Config.getInstance().isCapacityDetailsLogging()) {
log.info(msg);
}
}
@Override
public String toString ()
{
return this.getClass().getCanonicalName() + ":" + logIdentifier;
}
// Convenience class to unify the interface to
// WeatherReport and WeatherForecastPrediction.
private class Weather
{
final double temperature;
final double windSpeed;
final double windDirection;
final double cloudCover;
Weather (WeatherReport report)
{
temperature = report.getTemperature();
windSpeed = report.getWindSpeed();
windDirection = report.getWindDirection();
cloudCover = report.getCloudCover();
}
Weather (WeatherForecastPrediction prediction)
{
temperature = prediction.getTemperature();
windSpeed = prediction.getWindSpeed();
windDirection = prediction.getWindDirection();
cloudCover = prediction.getCloudCover();
}
double getTemperature ()
{
return temperature;
}
double getWindSpeed ()
{
return windSpeed;
}
double getWindDirection ()
{
return windDirection;
}
double getCloudCover ()
{
return cloudCover;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy