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

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 java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.HashMap;
import org.apache.log4j.Logger;
import org.joda.time.DateTime;
import org.powertac.common.TariffSubscription;
import org.powertac.common.TimeService;
import org.powertac.common.Timeslot;
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.factoredcustomer.interfaces.*;
import org.powertac.common.spring.SpringApplicationContext;
import org.powertac.common.state.Domain;
import org.powertac.factoredcustomer.CapacityStructure.BaseCapacityType;
import org.powertac.factoredcustomer.CapacityStructure.InfluenceKind;

/**
 * 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
{
    protected Logger log = Logger.getLogger(DefaultCapacityOriginator.class.getName());

    protected final FactoredCustomerService factoredCustomerService;
    protected final TimeService timeService;
    protected final TimeslotRepo timeslotRepo;
    protected final WeatherReportRepo weatherReportRepo;
    protected final 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;
    
    private 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();
    
    
    DefaultCapacityOriginator(CapacityStructure structure, CapacityBundle bundle) 
    {
        capacityStructure = structure;
        parentBundle = bundle;
        
        logIdentifier = capacityStructure.capacityName.isEmpty() ? 
                bundle.getName() : bundle.getName() + "#" + capacityStructure.capacityName;
        
        factoredCustomerService = (FactoredCustomerService) SpringApplicationContext.getBean("factoredCustomerService");
        timeService = (TimeService) SpringApplicationContext.getBean("timeService");
        timeslotRepo = (TimeslotRepo) SpringApplicationContext.getBean("timeslotRepo");
        weatherReportRepo = (WeatherReportRepo) SpringApplicationContext.getBean("weatherReportRepo");
        weatherForecastRepo = (WeatherForecastRepo) SpringApplicationContext.getBean("weatherForecastRepo");
        
        if (capacityStructure.baseCapacityType == BaseCapacityType.TIMESERIES) {
            tsGenerator = new TimeseriesGenerator(capacityStructure.baseTimeseriesStructure);
        } 
        else tsGenerator = null; 
    }
    
    @Override
    public CapacityProfile getCurrentForecast() 
    {
        Timeslot timeslot = timeslotRepo.currentTimeslot();
        List values = new ArrayList();
        for (int i=0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
            Double forecastCapacity = forecastCapacities.get(timeslot.getSerialNumber());
            if (forecastCapacity != null) {
                values.add(forecastCapacity);
            }
            else {
                values.add(getForecastCapacity(timeslot));
            }
            timeslot = timeslot.getNext();
        }
        return new CapacityProfile(values);
    }
    
    protected double getForecastCapacity(Timeslot timeslot)
    {
        Double ret = forecastCapacities.get(timeslot.getSerialNumber());
        if (ret == null) ret = computeForecastCapacity(timeslot);
        return ret;
    }
    
    private double computeForecastCapacity(Timeslot future)
    {
        Timeslot now = timeslotRepo.currentTimeslot();
        int timeToFuture = future.getSerialNumber() - now.getSerialNumber();
        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.getSerialNumber());
        
        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, future.getStartInstant().toDateTime(), 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.getSerialNumber(), forecastCapacity);
        log.debug(logIdentifier + ": Forecast capacity for timeslot " + future.getSerialNumber() + " = " + forecastCapacity);        
        return forecastCapacity;
    }
    
    private double getBaseCapacity(Timeslot timeslot) 
    {
        Double ret = baseCapacities.get(timeslot.getSerialNumber());
        if (ret == null) ret = drawBaseCapacitySample(timeslot);
        return ret;
    }
    
    private double drawBaseCapacitySample(Timeslot timeslot) 
    {    
        double baseCapacity = 0.0;
        switch (capacityStructure.baseCapacityType) {
        case POPULATION:
            baseCapacity = capacityStructure.basePopulationCapacity.drawSample();
            break;
        case INDIVIDUAL:
            for (int i=0; i < parentBundle.getPopulation(); ++i) {
                double draw = capacityStructure.baseIndividualCapacity.drawSample();
                baseCapacity += draw;
            }
            break;
        case TIMESERIES:
            baseCapacity = getBaseCapacityFromTimeseries(timeslot);
            break;            
        default: throw new Error(logIdentifier + ": Unexpected base capacity type: " + capacityStructure.baseCapacityType);
        }
        Double prevCapacity = baseCapacities.get(timeslot.getSerialNumber() - 1);
        if (prevCapacity != null) {
            baseCapacity = SMOOTHING_WEIGHT * prevCapacity + (1 - SMOOTHING_WEIGHT) * baseCapacity;
        }
        baseCapacity = truncateTo2Decimals(baseCapacity);
        baseCapacities.put(timeslot.getSerialNumber(), baseCapacity);
        return baseCapacity;
    }
	
    private double getBaseCapacityFromTimeseries(Timeslot 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 useCapacity(TariffSubscription subscription)
    {
        Timeslot timeslot = timeslotRepo.currentTimeslot();
        
        double baseCapacity = getBaseCapacity(timeslot);
        if (Double.isNaN(baseCapacity)) throw new Error("Base capacity is NaN!");
        logCapacityDetails(logIdentifier + ": Base capacity for timeslot " + timeslot.getSerialNumber() + " = " + 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.getSerialNumber(), adjustedCapacity);        
        log.info(logIdentifier + ": Adjusted capacity for tariff " + subscription.getTariff().getId() + " = " + adjustedCapacity);        
        return adjustedCapacity;
    }

    private double adjustCapacityForCurtailments(Timeslot timeslot, double capacity, TariffSubscription subscription)
    {
        double lastCurtailment = subscription.getCurtailment();
        if (Math.abs(lastCurtailment) > 0.01) {  // != 0
            curtailedCapacities.put(timeslot.getSerialNumber() - 1, lastCurtailment);
            if (capacityStructure.curtailmentShifts != null) {
                for (int i=0; i < capacityStructure.curtailmentShifts.length; ++i) {
                    double shiftingFactor = capacityStructure.curtailmentShifts[i];
                    double shiftedCapacity = lastCurtailment * shiftingFactor; 
                    Double previousShifts = shiftedCurtailments.get(timeslot.getSerialNumber() + i); 
                    if (previousShifts == null) {
                        shiftedCurtailments.put(timeslot.getSerialNumber() + i, shiftedCapacity);
                    } else {
                        shiftedCurtailments.put(timeslot.getSerialNumber() + i, previousShifts + shiftedCapacity);
                    }
                }
            }
        }
        Double currentShift = shiftedCurtailments.get(timeslot.getSerialNumber());
        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.dailySkew[day-1] * capacityStructure.hourlySkew[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.temperatureInfluence == InfluenceKind.DIRECT) {
            int temperature = (int) Math.round(weather.getTemperature());
            weatherFactor = weatherFactor * capacityStructure.temperatureMap.get(temperature);
        }
        else if (capacityStructure.temperatureInfluence == InfluenceKind.DEVIATION) {
            int curr = (int) Math.round(weather.getTemperature());
            int ref = (int) Math.round(capacityStructure.temperatureReference);
            double deviationFactor = 1.0;
            if (curr > ref) {
                for (int t = ref+1; t <= curr; ++t) {
                    deviationFactor += capacityStructure.temperatureMap.get(t);
                }
            } else if (curr < ref) {
                for (int t = curr; t < ref; ++t) {
                    deviationFactor += capacityStructure.temperatureMap.get(t);
                }                
            }
            weatherFactor = weatherFactor * deviationFactor;
        }
        if (capacityStructure.windSpeedInfluence == InfluenceKind.DIRECT) {
            int windSpeed = (int) Math.round(weather.getWindSpeed());
            weatherFactor = weatherFactor * capacityStructure.windSpeedMap.get(windSpeed);
            if (windSpeed > 0.0 && capacityStructure.windDirectionInfluence == InfluenceKind.DIRECT) {
                int windDirection = (int) Math.round(weather.getWindDirection());
                weatherFactor = weatherFactor * capacityStructure.windDirectionMap.get(windDirection);
            }
        }
        if (capacityStructure.cloudCoverInfluence == InfluenceKind.DIRECT) {
            int cloudCover = (int) Math.round(100 * weather.getCloudCover());  // [0,1] to ##%
            weatherFactor = weatherFactor * capacityStructure.cloudCoverMap.get(cloudCover);
        }
        if (verbose) logCapacityDetails(logIdentifier + ": weather factor = " + weatherFactor);
        return capacity * weatherFactor;
    }
    
    public double adjustCapacityForSubscription(Timeslot 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);
    }
    
    protected double adjustCapacityForTariffRates(Timeslot timeslot, double baseCapacity, TariffSubscription subscription)
    {
        if ((baseCapacity - 0.0) < 0.01) return baseCapacity;
        
        double chargeForBase = subscription.getTariff().getUsageCharge(timeslot.getStartInstant(), 
                                                                       baseCapacity, subscription.getTotalUsage());
        double rateForBase = chargeForBase / baseCapacity;
        
        double benchmarkRate = capacityStructure.benchmarkRates.get(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.elasticityModelType) {
        case CONTINUOUS:
            return determineContinuousElasticityFactor(rateRatio);
        case STEPWISE:
            return determineStepwiseElasticityFactor(rateRatio);
        default: throw new Error("Unexpected elasticity model type: " + capacityStructure.elasticityModelType);
        }
    }
    
    private double determineContinuousElasticityFactor(double rateRatio)
    {
        double percentChange = (rateRatio - 1.0) / 0.01;
        double elasticityRatio = Double.parseDouble(capacityStructure.elasticityModelXml.getAttribute("ratio"));
        
        String range = capacityStructure.elasticityModelXml.getAttribute("range");
        String[] minmax = range.split("~");
        double low = Double.parseDouble(minmax[0]);
        double high = Double.parseDouble(minmax[1]);
        
        return Math.max(low, Math.min(high, 1.0 + (percentChange * elasticityRatio)));
    }
    
    private double determineStepwiseElasticityFactor(double rateRatio)
    {
        double[][] elasticity = null;
        if (elasticity == null) {
            elasticity = ParserFunctions.parseMapToDoubleArray(capacityStructure.elasticityModelXml.getAttribute("map"));
        }
        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 (int i=0; i < elasticity.length; ++i) {
            double r = elasticity[i][RATE_RATIO_INDEX];
            if (r <= rateRatio && r > rateLowerBound) {
                rateLowerBound = r;
                lowerBoundCapacityFactor = elasticity[i][CAPACITY_FACTOR_INDEX];
            }
            if (r >= rateRatio && r < rateUpperBound) {
                rateUpperBound = r;
                upperBoundCapacityFactor = elasticity[i][CAPACITY_FACTOR_INDEX];
            }
        }	
        return (rateRatio < 1) ? upperBoundCapacityFactor : lowerBoundCapacityFactor;
    }
      
    @Override
    public String getCapacityName()
    {
        return capacityStructure.capacityName;
    }

    @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;
    }

    protected void logCapacityDetails(String msg) 
    {
        if (factoredCustomerService.getCapacityDetailsLogging() == true) {
            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; }
        
    }
    
} // end class







© 2015 - 2025 Weber Informatics LLC | Privacy Policy