edu.utexas.cs.tactex.servercustomers.factoredcustomer.LearningUtilityOptimizer.java Source code

Java tutorial

Introduction

Here is the source code for edu.utexas.cs.tactex.servercustomers.factoredcustomer.LearningUtilityOptimizer.java

Source

/*
 * TacTex - a power trading agent that competed in the Power Trading Agent Competition (Power TAC) www.powertac.org
 * Copyright (c) 2013-2016 Daniel Urieli and Peter Stone {urieli,pstone}@cs.utexas.edu               
 *
 *
 * This file is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This file is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * This file incorporates work covered by the following copyright and  
 * permission notice:  
 *
*     Copyright 2011-2013 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 edu.utexas.cs.tactex.servercustomers.factoredcustomer;

import java.util.AbstractMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.math.stat.descriptive.moment.Variance;
import org.apache.log4j.Logger;
import org.powertac.common.state.Domain;

import edu.utexas.cs.tactex.servercustomers.common.RandomSeed;
import edu.utexas.cs.tactex.servercustomers.common.TariffSubscription;
import edu.utexas.cs.tactex.servercustomers.factoredcustomer.CapacityProfile.PermutationRule;
import edu.utexas.cs.tactex.servercustomers.factoredcustomer.ProfileRecommendation.Opinion;
import edu.utexas.cs.tactex.servercustomers.factoredcustomer.interfaces.*;
import edu.utexas.cs.tactex.servercustomers.factoredcustomer.utils.SeedIdGenerator;

/**
 * Key class responsible for managing the tariff(s) for one customer across
 * multiple capacity bundles if necessary.
 * 
 * @author Prashant Reddy
 */
@Domain
class LearningUtilityOptimizer extends DefaultUtilityOptimizer {
    private static final double NUM_SAMPLING_ITERATIONS = 30;

    private RandomSeed recommendationMaker;

    LearningUtilityOptimizer(CustomerStructure structure, List<CapacityBundle> bundles) {
        super(structure, bundles);
        log = Logger.getLogger(LearningUtilityOptimizer.class.getName());
    }

    @Override
    public void initialize(FactoredCustomerService service) {
        this.service = service;
        inertiaSampler = getRandomSeedRepo().getRandomSeed("factoredcustomer.LearningUtilityOptimizer",
                SeedIdGenerator.getId(), "InertiaSampler");
        tariffSelector = getRandomSeedRepo().getRandomSeed("factoredcustomer.LearningUtilityOptimizer",
                SeedIdGenerator.getId(), "TariffSelector");
        recommendationMaker = getRandomSeedRepo().getRandomSeed("factoredcustomer.LearningUtilityOptimizer",
                SeedIdGenerator.getId(), "RecommendationMaker");

        //    subscribeDefault();
    }

    //  @Override
    //  public void evaluateTariffs ()
    //  {
    //    super.evaluateTariffs();
    //
    //    recommendProfilesToBundles();
    //  }

    //  /**
    //   * calls recommendProfilesToBundles() which needs an updated
    //   * tariffSubscriptionRepo. 
    //   */
    //  @Override
    //  public void updatedSubscriptionRepo() {
    //    recommendProfilesToBundles();  
    //  }

    @Override
    public void recommendProfilesToBundles(int currentTimeslot) {
        // Ignore cross-bundle optimization for now. For example, we could
        // produce more locally when we have higher local demand, but we don't
        // currently support net-metering in PowerTAC, so it won't do anything.
        for (CapacityBundle bundle : capacityBundles) {
            List<TariffSubscription> subscriptions = getBundleSubscriptions(bundle);
            if (bundle.getOptimizerStructure().receiveRecommendations == true) {
                recommendProfilesToBundle(bundle, subscriptions, currentTimeslot);
            }
        }
    }

    private void recommendProfilesToBundle(CapacityBundle bundle, List<TariffSubscription> subscriptions,
            int currentTimeslot) {
        /**
         * find all subscriptions
         * for each subscription get forecast from each capacity originator in
         * bundle
         * for each forecast, get permutations
         * + use learned history to determine which permutations are more likely
         * + learn value for receptivityIndex and weights for recommendationFactors
         * + weight permutations by learned history
         * compute weight of each permutation as inverse of sum of normalized usage
         * charge and normalized distance from forecast
         * assign weights to each permutation for each CM to make a recommendation
         * + optimize weights of permutations over all capacity originators
         * (anti-herding)
         * make weighted recommendations to each capacity originator
         * + monitor deviation of actual from forecast and also from recommended
         **/

        //List<TariffSubscription> subscriptions = getBundleSubscriptions(bundle);

        Map<CapacityOriginator, ForecastRecord> forecasts = new HashMap<CapacityOriginator, ForecastRecord>();
        Map<CapacityOriginator, List<CapacityProfile>> perms = new HashMap<CapacityOriginator, List<CapacityProfile>>();
        Map<CapacityOriginator, Map<TariffSubscription, List<CapacityProfile>>> permsPerSub = new HashMap<CapacityOriginator, Map<TariffSubscription, List<CapacityProfile>>>();
        Map<CapacityOriginator, ProfileRecommendation> recs = new HashMap<CapacityOriginator, ProfileRecommendation>();
        Map<CapacityOriginator, Map<TariffSubscription, ProfileRecommendation>> recsPerSub = new HashMap<CapacityOriginator, Map<TariffSubscription, ProfileRecommendation>>();

        for (CapacityOriginator capacityOriginator : bundle.getCapacityOriginators()) {
            //CapacityProfile forecast = capacityOriginator.getCurrentForecast(currentTimeslot);
            //double charge =
            //  computeProfileUsageCharge(forecast, subscriptions, capacityOriginator);
            //ForecastRecord forecastRecord = new ForecastRecord(forecast, charge);
            //forecasts.put(capacityOriginator, forecastRecord);
            PermutationRule permutationRule = bundle.getOptimizerStructure().permutationRule;
            if (permutationRule == null)
                permutationRule = PermutationRule.ALL_SHIFTS;
            //perms.put(capacityOriginator, forecast.getPermutations(permutationRule));
            //log.info(bundle.getName() + ": Evaluating "
            //         + perms.get(capacityOriginator).size()
            //         + " profile permutations for "
            //         + bundle.getCustomerInfo().getPowerType()
            //         + " capacity originator: "
            //         + capacityOriginator.getCapacityName());
            // old code - avg over all subs
            //recs.put(capacityOriginator,
            //         getProfileRecommendation(capacityOriginator, bundle,
            //                                  forecastRecord, perms, subscriptions));

            // new code - just for useCapacity - per-sub
            permsPerSub.put(capacityOriginator, new HashMap<TariffSubscription, List<CapacityProfile>>());
            for (TariffSubscription sub : subscriptions) {
                // create record per sub
                CapacityProfile forecastPerSub = capacityOriginator.getCurrentForecastPerSub(currentTimeslot, sub);
                //log.info("srv basis for perms: " + sub.getCustomer().getName() + " " +  sub.getTariff().getId() + " " + forecastPerSub.toString());
                double charge = computeProfileUsageChargePerSub(forecastPerSub, sub, capacityOriginator);
                ForecastRecord forecastRecordPerSub = new ForecastRecord(forecastPerSub, charge);
                permsPerSub.get(capacityOriginator).put(sub, forecastPerSub.getPermutations(permutationRule));
                insertToRecsMap(recsPerSub, capacityOriginator, sub, getProfileRecommendationPerSub(
                        capacityOriginator, bundle, forecastRecordPerSub, permsPerSub, sub));
            }
        }
        //    if (bundle.getOptimizerStructure().raconcileRecommendations == true) {
        //      reconcileRecommendations(subscriptions, forecasts, perms, recs);
        //    }
        for (CapacityOriginator capacityOriginator : bundle.getCapacityOriginators()) {
            if (capacityOriginator instanceof ProfileRecommendation.Listener) {
                ProfileRecommendation rec = recs.get(capacityOriginator);
                //        if (!rec.isEmpty()) {
                //          log.info(bundle.getName() + ": Submitting "
                //                   + rec.getOpinions().size() + " profile suggestions to "
                //                   + bundle.getCustomerInfo().getPowerType()
                //                   + " capacity originator: "
                //                   + capacityOriginator.getCapacityName());
                // Daniel: do not overwrite forecastCapacities - leave as default
                //          ((ProfileRecommendation.Listener) capacityOriginator)
                //                  .handleProfileRecommendation(rec);
                //        }
                //        else {
                //          log.info(bundle.getName()
                //                   + ": No beneficial profile permutations for "
                //                   + bundle.getCustomerInfo().getPowerType()
                //                   + " capacity originator: "
                //                   + capacityOriginator.getCapacityName());
                //        }
                for (TariffSubscription sub : subscriptions) {
                    rec = recsPerSub.get(capacityOriginator).get(sub);
                    if (!rec.isEmpty()) {
                        //            log.debug(bundle.getName() + ": Submitting "
                        //                     + rec.getOpinions().size() + " profile suggestions to "
                        //                     + bundle.getCustomerInfo().getPowerType()
                        //                     + " capacity originator: "
                        //                     + capacityOriginator.getCapacityName());
                        ((ProfileRecommendation.Listener) capacityOriginator).handleProfileRecommendationPerSub(rec,
                                sub, currentTimeslot, capacityOriginator.getCurrentForecast(currentTimeslot));
                    } else {
                        //            log.debug(bundle.getName()
                        //                     + ": No beneficial profile permutations for "
                        //                     + bundle.getCustomerInfo().getPowerType()
                        //                     + " capacity originator: "
                        //                     + capacityOriginator.getCapacityName());
                    }
                }
            }
        }
    }

    private void insertToRecsMap(Map<CapacityOriginator, Map<TariffSubscription, ProfileRecommendation>> recs,
            CapacityOriginator capacityOriginator, TariffSubscription sub,
            ProfileRecommendation profileRecommendation) {
        Map<TariffSubscription, ProfileRecommendation> sub2rec = recs.get(capacityOriginator);
        if (null == sub2rec) {
            sub2rec = new HashMap<TariffSubscription, ProfileRecommendation>();
            recs.put(capacityOriginator, sub2rec);
        }
        sub2rec.put(sub, profileRecommendation);
    }

    private List<TariffSubscription> getBundleSubscriptions(CapacityBundle bundle) {
        return getTariffSubscriptionRepo().findSubscriptionsForCustomer(bundle.getCustomerInfo());
    }

    private ProfileRecommendation getProfileRecommendation(CapacityOriginator capacityOriginator,
            CapacityBundle bundle, ForecastRecord forecastRecord,
            Map<CapacityOriginator, List<CapacityProfile>> perms, List<TariffSubscription> subscriptions) {
        logRecommendationDetails("getProfileRecommendation() Forecast " + forecastRecord.capacityProfile
                + " usage charge = " + forecastRecord.usageCharge);

        ProfileRecommendation rec = new ProfileRecommendation();
        for (CapacityProfile perm : perms.get(capacityOriginator)) {
            double usageCharge = computeProfileUsageCharge(perm, subscriptions, capacityOriginator);
            logRecommendationDetails(
                    "getProfileRecommendation() Permutation " + perm + " usage charge = " + usageCharge);
            if (isPermutationAcceptable(capacityOriginator, bundle.getOptimizerStructure(), usageCharge,
                    forecastRecord.usageCharge)) {
                Opinion opinion = rec.new Opinion();
                opinion.usageCharge =
                        // Daniel: remove duplicate computation
                        usageCharge;
                //computeProfileUsageCharge(perm, subscriptions, capacityOriginator);
                opinion.profileChange = forecastRecord.capacityProfile.distanceTo(perm);
                logRecommendationDetails("profile distance: " + opinion.profileChange);
                rec.setOpinion(perm, opinion);
            }
        }
        if (!rec.isEmpty())
            computeDerivedValues(rec, bundle.getOptimizerStructure());
        return rec;
    }

    private ProfileRecommendation getProfileRecommendationPerSub(CapacityOriginator capacityOriginator,
            CapacityBundle bundle, ForecastRecord forecastRecord,
            Map<CapacityOriginator, Map<TariffSubscription, List<CapacityProfile>>> permsPerSub,
            //List<TariffSubscription> subscriptions)
            TariffSubscription sub) {
        logRecommendationDetails("getProfileRecommendationPerSub(" + sub.getCustomer().getName() + ", "
                + sub.getTariff().getId() + ") Forecast " + forecastRecord.capacityProfile + " usage charge = "
                + forecastRecord.usageCharge);

        ProfileRecommendation rec = new ProfileRecommendation();
        for (CapacityProfile perm : permsPerSub.get(capacityOriginator).get(sub)) {
            double usageCharge = computeProfileUsageChargePerSub(perm, sub, capacityOriginator);
            //logRecommendationDetails("getProfileRecommendationPerSub(" + sub.getTariff.getId() + ") Permutation " + perm + " usage charge = " + usageCharge);
            if (isPermutationAcceptable(capacityOriginator, bundle.getOptimizerStructure(), usageCharge,
                    forecastRecord.usageCharge)) {
                Opinion opinion = rec.new Opinion();
                opinion.usageCharge =
                        // avoid duplication
                        usageCharge;
                //computeProfileUsageChargePerSub(perm, sub, capacityOriginator);
                opinion.profileChange = forecastRecord.capacityProfile.distanceTo(perm);
                logRecommendationDetails("getProfileRecommendationPerSub(" + sub.getCustomer().getName() + ", "
                        + sub.getTariff().getId() + ") Permutation " + perm + " usage charge = " + usageCharge
                        + " distanceTo=" + opinion.profileChange);
                rec.setOpinion(perm, opinion);
            }
        }
        if (!rec.isEmpty())
            computeDerivedValues(rec, bundle.getOptimizerStructure());
        return rec;
    }

    private void computeDerivedValues(ProfileRecommendation rec, ProfileOptimizerStructure optimizerStructure) {
        rec.normalizeOpinions();
        rec.computeScores(optimizerStructure.profileChangeWeight, optimizerStructure.bundleValueWeight);
        rec.computeUtilities();
        rec.computeProbabilities(optimizerStructure.rationalityFactor);
    }

    private double computeProfileUsageCharge(CapacityProfile profile, List<TariffSubscription> subscriptions,
            CapacityOriginator capacityOriginator) {
        int timeslot = getTimeslotRepo().currentSerialNumber();
        double totalCharge = 0.0;
        for (int i = 0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
            double totalTimeslotUsage = profile.getCapacity(i);
            // System.out.println("timeslot usage total = " + totalTimeslotUsage);
            double timeslotCharge = 0.0;
            for (TariffSubscription subscription : subscriptions) {
                double subTimeslotUsage = capacityOriginator.adjustCapacityForSubscription(timeslot,
                        totalTimeslotUsage, subscription);
                // System.out.println("timeslot usage for subscription = " +
                // totalTimeslotUsage);
                timeslotCharge += subscription.getTariff()
                        .getUsageCharge(getTimeslotRepo().getTimeForIndex(timeslot), subTimeslotUsage, 0.0);
                // System.out.println("timeslot charge = " + timeslotCharge);
            }
            totalCharge += timeslotCharge;
            timeslot += 1;
        }
        return totalCharge;
    }

    private double computeProfileUsageChargePerSub(CapacityProfile profile, TariffSubscription subscription,
            CapacityOriginator capacityOriginator) {
        int timeslot = getTimeslotRepo().currentSerialNumber();
        double totalCharge = 0.0;
        //logRecommendationDetails("computeProfileUsageCharge(), CapacityProfile.NUM_TIMESLOTS=" + CapacityProfile.NUM_TIMESLOTS);
        for (int i = 0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
            double totalTimeslotUsage = profile.getCapacity(i);
            //logRecommendationDetails("totalTimeslotUsage=" + totalTimeslotUsage);
            // System.out.println("timeslot usage total = " + totalTimeslotUsage);
            double timeslotCharge = 0.0;
            //for (TariffSubscription subscription: subscriptions) {
            double subTimeslotUsage = capacityOriginator.adjustCapacityForSubscription(timeslot, totalTimeslotUsage,
                    subscription);
            //logRecommendationDetails("subTimeslotUsage=" + subTimeslotUsage);
            // System.out.println("timeslot usage for subscription = " +
            // totalTimeslotUsage);
            timeslotCharge += subscription.getTariff().getUsageCharge(getTimeslotRepo().getTimeForIndex(timeslot),
                    subTimeslotUsage, 0.0); // TODO: why cumulative usage is 0?
            //logRecommendationDetails("timeslotCharge=" + timeslotCharge);
            // System.out.println("timeslot charge = " + timeslotCharge);
            //}
            totalCharge += timeslotCharge;
            timeslot += 1;
        }
        return totalCharge;
    }

    private boolean isPermutationAcceptable(CapacityOriginator capacityOriginator,
            ProfileOptimizerStructure optimizerStructure, double permCharge, double forecastCharge) {
        Double threshold = null;
        switch (optimizerStructure.usageChargeStance) {
        case NEUTRAL:
            return true;
        case BENEFIT:
            if (capacityOriginator.getParentBundle().getCustomerInfo().getPowerType().isConsumption()) {
                // less negative is better
                threshold = (1.0 - optimizerStructure.usageChargePercentBenefit) * forecastCharge;
            } else {
                // PRODUCTION or STORAGE -- more positive is better
                threshold = (1.0 + optimizerStructure.usageChargePercentBenefit) * forecastCharge;
            }
            // fall through
        case THRESHOLD:
            if (threshold == null) {
                threshold = optimizerStructure.usageChargeThreshold;
            }
            return permCharge > threshold;
        default:
            throw new Error("Unexpected case in usage charge stance: " + optimizerStructure.usageChargeStance);
        }
    }

    private void reconcileRecommendations(List<TariffSubscription> subscriptions,
            Map<CapacityOriginator, ForecastRecord> forecasts, Map<CapacityOriginator, List<CapacityProfile>> perms,
            Map<CapacityOriginator, ProfileRecommendation> recs) {
        // TODO: adjust for accumulation towards tiered rates across capacity
        // originators

        for (AbstractMap.Entry<CapacityOriginator, ProfileRecommendation> targetEntry : recs.entrySet()) {
            CapacityOriginator targetOriginator = targetEntry.getKey();
            ProfileRecommendation targetRec = targetEntry.getValue();
            if (targetRec.isEmpty() || targetOriginator.getParentBundle().getCapacityOriginators().size() == 1) {
                continue;
            }
            double[] othersCapacities = new double[CapacityProfile.NUM_TIMESLOTS];
            for (int s = 0; s < NUM_SAMPLING_ITERATIONS; ++s) {
                for (AbstractMap.Entry<CapacityOriginator, ProfileRecommendation> otherEntry : recs.entrySet()) {
                    ProfileRecommendation otherRec = otherEntry.getValue();
                    CapacityProfile otherProfile;
                    if (otherRec.isEmpty()) {
                        otherProfile = forecasts.get(otherEntry.getKey()).capacityProfile;
                    } else {
                        otherProfile = drawProfileFromRecommendation(otherRec);
                    }
                    for (int i = 0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
                        othersCapacities[i] += otherProfile.getCapacity(i);
                        if (s == NUM_SAMPLING_ITERATIONS) {
                            othersCapacities[i] = othersCapacities[i] / (double) NUM_SAMPLING_ITERATIONS;
                        }
                    }
                }
            }
            CapacityProfile forecastProfile = forecasts.get(targetOriginator).capacityProfile;
            double forecastVariance = computeAggregateVariance(forecastProfile, othersCapacities);
            for (AbstractMap.Entry<CapacityProfile, Opinion> opinionEntry : targetRec.getOpinions().entrySet()) {
                CapacityProfile targetProfile = opinionEntry.getKey();
                double targetVariance = computeAggregateVariance(targetProfile, othersCapacities);
                double bundleValue = forecastVariance / targetVariance;
                opinionEntry.getValue().bundleValue = bundleValue;
            }
            computeDerivedValues(targetRec, targetOriginator.getParentBundle().getOptimizerStructure()); // TODO use local opt-structure
        }
    }

    private double computeAggregateVariance(CapacityProfile profile, double[] otherCapacities) {
        double[] aggCapacities = new double[CapacityProfile.NUM_TIMESLOTS];
        for (int i = 0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
            aggCapacities[i] = profile.getCapacity(i) + otherCapacities[i];
        }
        return new Variance().evaluate(aggCapacities);
    }

    private CapacityProfile drawProfileFromRecommendation(ProfileRecommendation rec) {
        double draw = recommendationMaker.nextFloat();
        double sumProb = 0.0;
        for (AbstractMap.Entry<CapacityProfile, Double> entry : rec.getProbabilities().entrySet()) {
            sumProb += entry.getValue();
            if (draw < sumProb) {
                return entry.getKey();
            }
        }
        throw new Error("Drawing from recommendation resulted in a null profile!");
    }

    private void logRecommendationDetails(String msg) {
        // log.info(msg);
        log.debug(msg);
    }

    // INNER CLASS

    private class ForecastRecord {
        CapacityProfile capacityProfile;
        double usageCharge;

        ForecastRecord(CapacityProfile p, double c) {
            capacityProfile = p;
            usageCharge = c;
        }
    }

} // end class