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

org.powertac.factoredcustomer.DefaultUtilityOptimizer 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.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.ArrayList;
import java.util.Random;
import org.apache.log4j.Logger;
import org.powertac.common.Tariff;
import org.powertac.common.TariffSubscription;
import org.powertac.common.TimeService;
import org.powertac.common.Timeslot;
import org.powertac.common.repo.RandomSeedRepo;
import org.powertac.common.repo.TariffSubscriptionRepo;
import org.powertac.common.repo.TimeslotRepo;
import org.powertac.common.interfaces.TariffMarket;
import org.powertac.common.enumerations.PowerType;
import org.powertac.common.spring.SpringApplicationContext;
import org.powertac.common.state.Domain;
import org.powertac.common.state.StateChange;
import org.powertac.factoredcustomer.interfaces.*;
import org.powertac.factoredcustomer.TariffSubscriberStructure.AllocationMethod;

/**
 * Key class responsible for managing the tariff(s) for one customer across 
 * multiple capacity bundles if necessary.
 * 
 * @author Prashant Reddy
 */
@Domain
class DefaultUtilityOptimizer implements UtilityOptimizer
{
    protected Logger log = Logger.getLogger(DefaultUtilityOptimizer.class.getName());

    protected final FactoredCustomerService factoredCustomerService;
    protected final TariffMarket tariffMarketService;
    protected final TariffSubscriptionRepo tariffSubscriptionRepo;
    protected final TimeslotRepo timeslotRepo;
    protected final RandomSeedRepo randomSeedRepo;
    
    protected static final int NUM_HOURS_IN_DAY = 24;
    protected static final long MEAN_TARIFF_DURATION = 5;  // number of days
    
    protected final CustomerStructure customerStructure;
    protected final List capacityBundles;
   
    protected final List ignoredTariffs = new ArrayList();
    protected Random inertiaSampler;
    protected Random tariffSelector;
    
    protected final List allTariffs = new ArrayList();
    protected int tariffEvaluationCounter = 0;
    
    protected List bundlesWithRevokedTariffs = new ArrayList();
    
    
    DefaultUtilityOptimizer(CustomerStructure structure, List bundles) 
    {        
        customerStructure = structure;
        capacityBundles = bundles;
    
        factoredCustomerService = (FactoredCustomerService) SpringApplicationContext.getBean("factoredCustomerService");
        tariffMarketService = (TariffMarket) SpringApplicationContext.getBean("tariffMarketService");
        tariffSubscriptionRepo = (TariffSubscriptionRepo) SpringApplicationContext.getBean("tariffSubscriptionRepo");
        timeslotRepo = (TimeslotRepo) SpringApplicationContext.getBean("timeslotRepo");
        randomSeedRepo = (RandomSeedRepo) SpringApplicationContext.getBean("randomSeedRepo");
    }
    
    @Override
    public void initialize()
    {
        inertiaSampler = new Random(randomSeedRepo.getRandomSeed("factoredcustomer.DefaultUtilityOptimizer", 
                customerStructure.structureId, "InertiaSampler").getValue());
        tariffSelector = new Random(randomSeedRepo.getRandomSeed("factoredcustomer.DefaultUtilityOptimizer", 
                customerStructure.structureId, "TariffSelector").getValue());
        
        subscribeDefault();
    }
  
    ///////////////// TARIFF EVALUATION //////////////////////
    
    @StateChange
    protected void subscribe(Tariff tariff, CapacityBundle bundle, int customerCount, boolean verbose)
    {
      tariffMarketService.subscribeToTariff(tariff, bundle.getCustomerInfo(), customerCount);
      if (verbose) log.info(bundle.getName() + ": Subscribed " + customerCount + " customers to tariff " + tariff.getId() + " successfully");
    }

    @StateChange
    protected void unsubscribe(TariffSubscription subscription, CapacityBundle bundle, int customerCount, boolean verbose)
    {
      subscription.unsubscribe(customerCount);
      if (verbose) log.info(bundle.getName() + ": Unsubscribed " + customerCount + " customers from tariff " + subscription.getTariff().getId() + " successfully");
    }

    /** @Override hook **/
    protected void subscribeDefault() 
    {
        for (CapacityBundle bundle: capacityBundles) {
            PowerType powerType = bundle.getPowerType();
            if (tariffMarketService.getDefaultTariff(powerType) != null) {
                log.info(bundle.getName() + ": Subscribing " + bundle.getPopulation() + " customers to default " + powerType + " tariff");
                subscribe(tariffMarketService.getDefaultTariff(powerType), bundle, bundle.getPopulation(), false);
            } else {
                log.info(bundle.getName() + ": No default tariff for power type " + powerType + "; trying generic type");
                PowerType genericType = powerType.getGenericType();
                if (tariffMarketService.getDefaultTariff(genericType) == null) {
                    log.error(bundle.getName() + ": No default tariff for generic power type " + genericType + " either!");
                } else {
                    log.info(bundle.getName() + ": Subscribing " + bundle.getPopulation() + " customers to default " + genericType + " tariff");
                    subscribe(tariffMarketService.getDefaultTariff(genericType), bundle, bundle.getPopulation(), false);
                } 
            }
        }
    }
    
    @Override
    public void handleNewTariffs (List newTariffs)
    {
        ++tariffEvaluationCounter;
        for (Tariff tariff: newTariffs) {
            allTariffs.add(tariff);
        }               
        for (CapacityBundle bundle: capacityBundles) {
            evaluateTariffs(bundle, newTariffs); 
        }
        bundlesWithRevokedTariffs.clear();
    }
	
    private boolean isTariffApplicable(Tariff tariff, CapacityBundle bundle)
    {
        PowerType bundlePowerType = bundle.getCustomerInfo().getPowerType();
        if (tariff.getPowerType() == bundlePowerType ||
            tariff.getPowerType() == bundlePowerType.getGenericType()) {
            return true;
        }
        return false;
    }
    
    private void evaluateTariffs(CapacityBundle bundle, List newTariffs) 
    {
        if ((tariffEvaluationCounter % bundle.getSubscriberStructure().reconsiderationPeriod) == 0 
             || bundlesWithRevokedTariffs.contains(bundle)) { 
            reevaluateAllTariffs(bundle);
        } 
        else if (! ignoredTariffs.isEmpty()) {
            evaluateCurrentTariffs(bundle, newTariffs);                
        }   
        else if (! newTariffs.isEmpty()) {
            boolean ignoringNewTariffs = true;
            for (Tariff tariff: newTariffs) {
                if (isTariffApplicable(tariff, bundle)) {
                    ignoringNewTariffs = false;
                    evaluateCurrentTariffs(bundle, newTariffs);
                    break;
                }
            }
            if (ignoringNewTariffs) log.info(bundle.getName() + ": New tariffs are not applicable; skipping evaluation");
        }
    }
    
    private void reevaluateAllTariffs(CapacityBundle bundle) 
    {
        log.info(bundle.getName() + ": Reevaluating all tariffs for " + bundle.getPowerType() + " subscriptions");
        
        List evalTariffs = new ArrayList();
        for (Tariff tariff: allTariffs) {
            if (! tariff.isRevoked() && ! tariff.isExpired() && isTariffApplicable(tariff, bundle)) {
                evalTariffs.add(tariff);
            }
        }
        assertNotEmpty(bundle, evalTariffs);
        ignoredTariffs.clear();         
        manageSubscriptions(bundle, evalTariffs);
    }
    
    private void evaluateCurrentTariffs(CapacityBundle bundle, List newTariffs) 
    {
        if (bundle.getSubscriberStructure().inertiaDistribution != null) {
            double inertia = bundle.getSubscriberStructure().inertiaDistribution.drawSample();
            if (inertiaSampler.nextDouble() < inertia) {
                log.info(bundle.getName() + ": Skipping " + bundle.getCustomerInfo().getPowerType() + " tariff reevaluation due to inertia");
                for (Tariff newTariff: newTariffs) {
                    ignoredTariffs.add(newTariff);
                }
                return;
            }
        }
        // Include previously ignored tariffs, currently subscribed tariffs, and default 
        // tariff in evaluation. Use map instead of list to eliminate duplicate tariffs.
        Map currTariffs = new HashMap();
	for (Tariff ignoredTariff: ignoredTariffs) {
	    currTariffs.put(ignoredTariff.getId(), ignoredTariff);
	}      
	ignoredTariffs.clear();		
	List subscriptions = tariffSubscriptionRepo.findSubscriptionsForCustomer(bundle.getCustomerInfo());
        for (TariffSubscription subscription: subscriptions) {
            Tariff subTariff = subscription.getTariff();
            if (!(subTariff.isExpired() || subTariff.isExpired())) {
                currTariffs.put(subTariff.getId(), subTariff);
            }
        }
        for (Tariff newTariff: newTariffs) {
            currTariffs.put(newTariff.getId(), newTariff);
        }
        Tariff defaultTariff = getDefaultTariff(bundle);
        if (! currTariffs.containsKey(defaultTariff.getId())) {
            currTariffs.put(defaultTariff.getId(), defaultTariff);
        }

        List evalTariffs = new ArrayList();
        for (Tariff tariff: currTariffs.values()) {
            if (isTariffApplicable(tariff, bundle)) {
                evalTariffs.add(tariff);
            }
        }
        assertNotEmpty(bundle, evalTariffs);
        manageSubscriptions(bundle, evalTariffs);	
    }

    private void assertNotEmpty(CapacityBundle bundle, List evalTariffs) 
    {
        if (evalTariffs.isEmpty()) {
            throw new Error(bundle.getName() + ": The evaluation tariffs list is unexpectedly empty!");
        }
    }
    
    private void manageSubscriptions(CapacityBundle bundle, List evalTariffs)
    {
        Collections.shuffle(evalTariffs);
        
        PowerType powerType = bundle.getCustomerInfo().getPowerType();        
        List tariffIds = new ArrayList(evalTariffs.size());
        for (Tariff tariff: evalTariffs) tariffIds.add(tariff.getId());
        logAllocationDetails(bundle.getName() + ": " + powerType + " tariffs for evaluation: " + tariffIds);

	List estimatedPayments = estimatePayments(bundle, evalTariffs);
	logAllocationDetails(bundle.getName() + ": Estimated payments for tariffs: " + estimatedPayments);
        
	List allocations = determineAllocations(bundle, evalTariffs, estimatedPayments);
	logAllocationDetails(bundle.getName() + ": Allocations for tariffs: " + allocations);
		
	int overAllocations = 0;
	for (int i=0; i < evalTariffs.size(); ++i) {
	    Tariff evalTariff = evalTariffs.get(i);
	    int allocation = allocations.get(i);
	    TariffSubscription subscription = tariffSubscriptionRepo.findSubscriptionForTariffAndCustomer(evalTariff, bundle.getCustomerInfo()); // could be null
	    int currentCommitted = (subscription != null) ? subscription.getCustomersCommitted() : 0;
	    int numChange = allocation - currentCommitted; 
			
	    log.debug(bundle.getName() + ": evalTariff = " + evalTariff.getId() + ", numChange = " + numChange +
	                  ", currentCommitted = " + currentCommitted + ", allocation = " + allocation);
			
	    if (numChange == 0) {
	        if (currentCommitted > 0) {
	            log.info(bundle.getName() + ": Maintaining " + currentCommitted + " " + powerType + " customers in tariff " + evalTariff.getId());
	        } else {
                    log.info(bundle.getName() + ": Not allocating any " + powerType + " customers to tariff " + evalTariff.getId());
	        }
	    } else if (numChange > 0) {
	        if (evalTariff.isExpired()) {
	            overAllocations += numChange;
	            if (currentCommitted > 0) {
	                log.info(bundle.getName() + ": Maintaining " + currentCommitted + " " + powerType + " customers in expired tariff " + evalTariff.getId());
	            }
	            log.info(bundle.getName() + ": Reallocating " + numChange + " " + powerType + " customers from expired tariff " + evalTariff.getId() + " to other tariffs");
	        } else { 
                    log.info(bundle.getName() + ": Subscribing " + numChange + " " + powerType + " customers to tariff " + evalTariff.getId());
                    subscribe(evalTariff, bundle, numChange, false);
	        }
	    } else if (numChange < 0) {
	        log.info(bundle.getName() + ": Unsubscribing " + -numChange + " " + powerType + " customers from tariff " + evalTariff.getId());
                unsubscribe(subscription, bundle, -numChange, false);
	    }
	}
	if (overAllocations > 0) {
	    int minIndex = 0;
	    double minEstimate = Double.POSITIVE_INFINITY;
	    for (int i=0; i < estimatedPayments.size(); ++i) {
	        if (estimatedPayments.get(i) < minEstimate && ! evalTariffs.get(i).isExpired()) {
	            minIndex = i;
	            minEstimate = estimatedPayments.get(i);
	        }
	    }
	    log.info(bundle.getName() + ": Subscribing " + overAllocations + " over-allocated customers to tariff " + evalTariffs.get(minIndex).getId());
	    subscribe(evalTariffs.get(minIndex), bundle, overAllocations, false);
	}
    }
	
    private List estimatePayments(CapacityBundle bundle, List evalTariffs) 
    {
        List estimatedPayments = new ArrayList(evalTariffs.size());
        for (int i=0; i < evalTariffs.size(); ++i) {
            Tariff tariff = evalTariffs.get(i);
            double fixedPayments = estimateFixedTariffPayments(tariff);
            double variablePayment = forecastDailyUsageCharge(bundle, tariff);
            double totalPayment = truncateTo2Decimals(fixedPayments + variablePayment);    
            double adjustedPayment = adjustForInterruptibility(bundle, tariff, totalPayment);
            estimatedPayments.add(adjustedPayment); 
        }      
        return estimatedPayments;
    }
    
    private double estimateFixedTariffPayments(Tariff tariff)
    {
        double lifecyclePayment = tariff.getEarlyWithdrawPayment() + tariff.getSignupPayment();
        double minDuration;
        if (tariff.getMinDuration() == 0) minDuration = MEAN_TARIFF_DURATION;
        else minDuration = tariff.getMinDuration() / TimeService.DAY;
        double dailyLifecyclePayment = lifecyclePayment / minDuration;  
        double dailyPeriodicPayment = tariff.getPeriodicPayment() * NUM_HOURS_IN_DAY;
        return dailyLifecyclePayment + dailyPeriodicPayment;
    }
  
    private double forecastDailyUsageCharge(CapacityBundle bundle, Tariff tariff)
    {
        Timeslot hourlyTimeslot = timeslotRepo.currentTimeslot();
        double totalUsage = 0.0;
        double totalCharge = 0.0;            
        for (CapacityOriginator capacityOriginator: bundle.getCapacityOriginators()) {
            CapacityProfile forecast = capacityOriginator.getCurrentForecast();            
            for (int i=0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
                double usageSign = bundle.getPowerType().isConsumption() ? +1 : -1;  
                double hourlyUsage = usageSign * forecast.getCapacity(i);
                totalCharge += tariff.getUsageCharge(hourlyTimeslot.getStartInstant(), hourlyUsage, totalUsage);
                totalUsage += hourlyUsage;
            }
        }        
        double realizedPrice = tariff.getRealizedPrice();
        if (Math.abs(realizedPrice) > 0.01) {  // != 0.0
            double estHourlyPrice = totalCharge / totalUsage;           
            if (Math.abs(estHourlyPrice - realizedPrice) > 0.05 * Math.abs(estHourlyPrice)) {
                double realizedPriceWeight = bundle.getSubscriberStructure().realizedPriceWeight;
                totalCharge = realizedPriceWeight * (realizedPrice * totalUsage) + (1 - realizedPriceWeight) * totalCharge;
            }
        }
        return totalCharge;
    }
    
    private double adjustForInterruptibility(CapacityBundle bundle, Tariff tariff, double totalPayment) 
    {
        double interruptibilityDiscount = bundle.getSubscriberStructure().interruptibilityDiscount;
        if (interruptibilityDiscount > 0.0 && tariff.getPowerType().isInterruptible()) {
            double effectSign = tariff.getPowerType().isConsumption() ? -1 : +1;
            double adjustedPayment = (1.0 + effectSign * interruptibilityDiscount) * totalPayment;
            return adjustedPayment;
        } else {
            return totalPayment;
        }
    }

    private List determineAllocations(CapacityBundle bundle, List evalTariffs, 
                                               List estimatedPayments) 
    {
        List allocations = new ArrayList();
        if (evalTariffs.size() == 1) {
            allocations.add(bundle.getPopulation());
            return allocations;
        }
        List validTariffsIndex = enforceTariffConstraints(bundle, evalTariffs, estimatedPayments);
        List checkedTariffs = new ArrayList();
        List checkedPayments = new ArrayList();
        for (int i=0; i < evalTariffs.size(); ++i) {
            if (validTariffsIndex.get(i)) {
                checkedTariffs.add(evalTariffs.get(i));
                checkedPayments.add(estimatedPayments.get(i));
            }
        }
        List checkedAllocs = bundle.getSubscriberStructure().allocationMethod == AllocationMethod.TOTAL_ORDER ?
            determineTotalOrderAllocations(bundle, checkedTariffs, checkedPayments) :
            determineLogitChoiceAllocations(bundle, checkedTariffs, checkedPayments);
        int checkedCounter = 0;
        for (int i=0; i < evalTariffs.size(); ++i) {
            allocations.add(validTariffsIndex.get(i) ? checkedAllocs.get(checkedCounter++) : 0);
        }
        return allocations;
    }
    
    private List enforceTariffConstraints(CapacityBundle bundle, List evalTariffs, 
                                                   List estimatedPayments)
   {
        List validityIndex = new ArrayList();
        
        Tariff defaultTariff = getDefaultTariff(bundle);
        if (defaultTariff == null) throw new Error("Default tariff not found amongst eval tariffs!");
        double defaultPayment = estimatedPayments.get(evalTariffs.indexOf(defaultTariff));
        
        boolean benchmarkRiskEnabled = bundle.getSubscriberStructure().benchmarkRiskEnabled;
        boolean tariffThrottlingEnabled = bundle.getSubscriberStructure().tariffThrottlingEnabled;
        
        Map brokerBests = new HashMap();  // brokerId -> tariffIndex
        
        for (int i=0; i < evalTariffs.size(); ++i) {
            Tariff evalTariff = evalTariffs.get(i);           
            // #1: Default tariff is always valid.
            if (evalTariff == defaultTariff) {
                validityIndex.add(true); continue;
            }
            // #2: If tariff is expired, don't consider it.
            if (evalTariff.isExpired() || evalTariff.isRevoked()) {
                log.info("Tariff " + evalTariff.getId() + " has expired or been revoked; being ignored.");
                validityIndex.add(false); continue;
            }
            // #3: Tariff payments cannot be too much worse than those of default tariff.
            if (benchmarkRiskEnabled) {
                double evalRatio = estimatedPayments.get(i) / defaultPayment;
                if ((bundle.getPowerType().isConsumption() && evalRatio > bundle.getSubscriberStructure().benchmarkRiskRatio) ||
                    (bundle.getPowerType().isProduction() && evalRatio < bundle.getSubscriberStructure().benchmarkRiskRatio)) {
                    logAllocationDetails(bundle.getName() + ": Tariff " + evalTariff.getId() + " has a worse than constrained benchmark risk at: " + evalRatio);
                    validityIndex.add(false); continue;
                }
            }
            // #4: Only include best N tariffs per broker.
            if (tariffThrottlingEnabled) {
                Long brokerId = evalTariff.getBroker().getId();
                if (! brokerBests.containsKey(brokerId)) {
                    brokerBests.put(brokerId, i);
                } else {
                    TariffSubscription evalSub = tariffSubscriptionRepo.findSubscriptionForTariffAndCustomer(evalTariff, bundle.getCustomerInfo());
                    if (evalSub == null || evalSub.getCustomersCommitted() == 0) {
                        double evalPayment = estimatedPayments.get(i);
                        double bestPayment = estimatedPayments.get(brokerBests.get(brokerId));
                        if ((bundle.getPowerType().isConsumption() && evalPayment <= bestPayment) ||
                            (bundle.getPowerType().isProduction() && evalPayment >= bestPayment)) {                           
                            logAllocationDetails(bundle.getName() + ": Tariff " + evalTariff.getId() + " is no better than " 
                                    + evalTariffs.get(brokerBests.get(brokerId)).getId());
                            validityIndex.add(false); continue;
                        } else {
                            // reevaluate previous brokerBest
                            TariffSubscription sub = tariffSubscriptionRepo.findSubscriptionForTariffAndCustomer(evalTariff, bundle.getCustomerInfo());
                            if (sub == null || sub.getCustomersCommitted() == 0) {
                                validityIndex.set(brokerBests.get(brokerId), false);
                                brokerBests.put(brokerId, i);
                            }
                        }
                    }
                }
            }
            validityIndex.add(true);
        }
        logAllocationDetails(bundle.getName() + ": Tariff constraint validation index: " + validityIndex);
        return validityIndex;
   }
    
    private List determineTotalOrderAllocations(CapacityBundle bundle, List checkedTariffs, 
                                                         List estimatedPayments) 
    {
        int numTariffs = checkedTariffs.size();
        List allocationRule;
        if (bundle.getSubscriberStructure().totalOrderRules.isEmpty()) {
            allocationRule = new ArrayList(numTariffs);
            allocationRule.add(1.0);
            for (int i=1; i < numTariffs; ++i) {
                allocationRule.add(0.0);
            }
        } else if (numTariffs <= bundle.getSubscriberStructure().totalOrderRules.size()) {
            allocationRule = bundle.getSubscriberStructure().totalOrderRules.get(numTariffs - 1);
        } else {
            allocationRule = new ArrayList(numTariffs);
            List largestRule = bundle.getSubscriberStructure().totalOrderRules.get(bundle.getSubscriberStructure().totalOrderRules.size() - 1);
            for (int i=0; i < numTariffs; ++i) {
                if (i < largestRule.size()) {
                    allocationRule.add(largestRule.get(i));
                } else { 
                    allocationRule.add(0.0);
                }
            }
        }               
        // payments are positive for production, so sorting is still valid
        List sortedPayments = new ArrayList(numTariffs);
        for (double estimatedPayment: estimatedPayments) {
            sortedPayments.add(estimatedPayment);
        }
        Collections.sort(sortedPayments, Collections.reverseOrder()); // we want descending order

        List allocations = new ArrayList(numTariffs);
        for (int i=0; i < numTariffs; ++i) {
            if (allocationRule.get(i) > 0) {
                double nextBest = sortedPayments.get(i);
                for (int j=0; j < numTariffs; ++j) {
                    if (estimatedPayments.get(j) == nextBest) {
                        allocations.add((int) Math.round(bundle.getCustomerInfo().getPopulation() * allocationRule.get(i)));
                    }
                }
            }	
            else allocations.add(0);
        }
        return allocations;
    }
    
    private List determineLogitChoiceAllocations(CapacityBundle bundle, List checkedTariffs, 
                                                          List estimatedPayments) 
    {
        // logit choice model:  p_i = e^(lambda * utility_i) / sum_i(e^(lambda * utility_i))

        int numTariffs = checkedTariffs.size();
        double bestPayment = Collections.max(estimatedPayments);
        double worstPayment = Collections.min(estimatedPayments);
        
        List probabilities = new ArrayList(numTariffs);
        if (bestPayment - worstPayment < 0.01) { // i.e., approximately zero
            for (int i=0; i < numTariffs; ++i) {
                probabilities.add(1.0 / numTariffs);
            }               
        } else {
            double midPayment = (worstPayment + bestPayment) / 2.0;           
            double basis = Math.max((bestPayment - midPayment), (midPayment - worstPayment));

            double absMin = Double.MAX_VALUE;
            double absMax = Double.MIN_VALUE;
            for (int i=0; i < numTariffs; ++i) {
                double absPayment = Math.abs(estimatedPayments.get(i));
                absMin = Math.min(absPayment, absMin);
                absMax = Math.max(absPayment, absMax);
            }   
            double kappa = Math.min(10.0, Math.max(1.0, absMax / absMin));  // utility curve shape factor
            double lambda = bundle.getSubscriberStructure().logitChoiceRationality;  // [0.0 = irrational, 1.0 = perfectly rational] 

            List numerators = new ArrayList(numTariffs);
            double denominator = 0.0;
            for (int i=0; i < numTariffs; ++i) {  
                double utility = ((estimatedPayments.get(i) - midPayment) / basis) * kappa;  // [-kappa, +kappa] 
                //System.out.println("***** utility for tariff[" + i + "] = " + utility);
                double numerator = Math.exp(lambda * utility);
                if (Double.isNaN(numerator)) numerator = 0.0;
                numerators.add(numerator);
                denominator += numerator;
            }
            for (int i=0; i < numTariffs; ++i) {
                probabilities.add(numerators.get(i) / denominator);
            }   
            //System.out.println("***** allocation probabilities: " + probabilities);
        }
        // Now determine allocations based on above probabilities
        List allocations = new ArrayList(numTariffs);
        int population = bundle.getPopulation();
        if (bundle.getCustomerInfo().isMultiContracting())
        {
            int sumAllocations = 0;
            for (int i=0; i < numTariffs; ++i) {
                int allocation;
                if (sumAllocations == population) {
                    allocation = 0;
                } else if (i < (numTariffs - 1)) {
                    allocation = (int) Math.round(population * probabilities.get(i));
                    if ((sumAllocations + allocation) > population) {
                        allocation = population - sumAllocations;
                    }
                    sumAllocations += allocation;
                } else {
                    allocation = population - sumAllocations;
                }
                allocations.add(allocation);
            }
        } else {
            double r = ((double) tariffSelector.nextInt(100) / 100.0); // [0.0, 1.0]
            double cumProbability = 0.0;
            for (int i=0; i < numTariffs; ++i) {
                cumProbability += probabilities.get(i); 
                if (r <= cumProbability) {
                    allocations.add(population);
                    for (int j=i+1; j < numTariffs; ++j) {
                        allocations.add(0);
                    }
                    break;
                } else {
                    allocations.add(0);
                }
            }
        }
        return allocations;
    }

    private Tariff getDefaultTariff(CapacityBundle bundle)
    {
        Tariff defaultTariff;
        defaultTariff = tariffMarketService.getDefaultTariff(bundle.getPowerType());
        if (defaultTariff == null) {
            defaultTariff = tariffMarketService.getDefaultTariff(bundle.getPowerType().getGenericType()); 
        }
        if (defaultTariff == null) {
            throw new Error(bundle.getName() + ": There is no default tariff for bundle type or it's generic type!");
        }
        return defaultTariff;
    }
    

    ///////////////// TIMESLOT ACTIVITY //////////////////////

    @Override
    public void handleNewTimeslot(Timeslot timeslot)
    {
        checkRevokedSubscriptions();
        usePower(timeslot);
    }
	
    private void checkRevokedSubscriptions()
    {
        for (CapacityBundle bundle: capacityBundles) {
            List revoked = tariffSubscriptionRepo.getRevokedSubscriptionList(bundle.getCustomerInfo());
            for (TariffSubscription revokedSubscription : revoked) {
                revokedSubscription.handleRevokedTariff();
                bundlesWithRevokedTariffs.add(bundle);
            }
        }
    }

    private void usePower(Timeslot timeslot) 
    {        
        for (CapacityBundle bundle: capacityBundles) {
            List subscriptions = tariffSubscriptionRepo.findActiveSubscriptionsForCustomer(bundle.getCustomerInfo());
            double totalCapacity = 0.0; 
            double totalUsageCharge = 0.0;
            for (TariffSubscription subscription: subscriptions) {
                double usageSign = bundle.getPowerType().isConsumption() ? +1 : -1;  
                double currCapacity = usageSign * useCapacity(subscription, bundle); 
                if (factoredCustomerService.getUsageChargesLogging() == true) {
                    double charge = subscription.getTariff().getUsageCharge(currCapacity, subscription.getTotalUsage(), false);
                    totalUsageCharge += charge;
                }
                subscription.usePower(currCapacity);
                totalCapacity += currCapacity;
            }
            log.info(bundle.getName() + ": Total " + bundle.getPowerType() + " capacity for timeslot " + timeslot.getSerialNumber() + " = " + totalCapacity);
            logUsageCharges(bundle.getName() + ": Total " + bundle.getPowerType() + " usage charge for timeslot " + timeslot.getSerialNumber() + " = " + totalUsageCharge);     
        }
    }

    public double useCapacity(TariffSubscription subscription, CapacityBundle bundle)
    {
        double capacity = 0;
        for (CapacityOriginator capacityOriginator: bundle.getCapacityOriginators()) {
            capacity += capacityOriginator.useCapacity(subscription);
        }
        return capacity;
    }
    
    protected String getCustomerName()
    {
        return customerStructure.name;
    }
    
    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 logAllocationDetails(String msg) 
    {
        if (factoredCustomerService.getAllocationDetailsLogging() == true) {
            log.info(msg);
        }
    }
    
    private void logUsageCharges(String msg) 
    {
        if (factoredCustomerService.getUsageChargesLogging() == true) {
            log.info(msg);
        }
    }
    
    @Override
    public String toString() 
    {
	return this.getClass().getCanonicalName() + ":" + getCustomerName();
    }
	
} // end class






© 2015 - 2025 Weber Informatics LLC | Privacy Policy