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

org.powertac.factoredcustomer.LearningUtilityOptimizer Maven / Gradle / Ivy

/*
* Copyright 2011-2014 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.powertac.common.TariffSubscription;
import org.powertac.common.state.Domain;
import org.powertac.factoredcustomer.CapacityProfile.PermutationRule;
import org.powertac.factoredcustomer.ProfileRecommendation.Opinion;
import org.powertac.factoredcustomer.interfaces.CapacityBundle;
import org.powertac.factoredcustomer.interfaces.CapacityOriginator;
import org.powertac.factoredcustomer.utils.SeedIdGenerator;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

//import org.apache.commons.math3.stat.descriptive.moment.Variance;
//import org.powertac.common.RandomSeed;

/**
 * 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 Logger log = LogManager.getLogger(LearningUtilityOptimizer.class);

  public LearningUtilityOptimizer (CustomerStructure customerStructure,
                                   List bundles)
  {
    super(customerStructure, bundles);
  }

  @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");

    subscribeDefault();
  }

  @Override
  public void evaluateTariffs ()
  {
    super.evaluateTariffs();
    // moved recommendProfilesToBundles() to run after subscription
    // repo is updated
  }

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

  private void recommendProfilesToBundles ()
  {
    // 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 subscriptions = getBundleSubscriptions(bundle);
      if (bundle.getOptimizerStructure().isReceiveRecommendations()) {
        recommendProfilesToBundle(bundle, subscriptions);
      }
    }
  }

  private void recommendProfilesToBundle (CapacityBundle bundle, List subscriptions)
  {
    /**
     * 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
     **/

    Map>> permsPerSub =
        new HashMap<>();
    Map> recsPerSub =
        new HashMap<>();

    for (CapacityOriginator capacityOriginator : bundle.getCapacityOriginators()) {
      PermutationRule permutationRule =
          bundle.getOptimizerStructure().getPermutationRule();
      if (permutationRule == null) {
        permutationRule = PermutationRule.ALL_SHIFTS;
      }

      // new code - just for useCapacity - per-sub
      permsPerSub.put(capacityOriginator, new HashMap>());
      for (TariffSubscription sub : subscriptions) {
        // create record per sub
        CapacityProfile forecastPerSub = capacityOriginator.getCurrentForecastPerSub(sub);
        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));
      }
    }

    for (CapacityOriginator capacityOriginator : bundle.getCapacityOriginators()) {
      if (capacityOriginator instanceof ProfileRecommendation.Listener) {
        for (TariffSubscription sub : subscriptions) {
          ProfileRecommendation rec = recsPerSub.get(capacityOriginator).get(sub);
          if (!rec.isEmpty()) {
            log.info(bundle.getName() + ": Submitting "
                + rec.getOpinions().size() + " profile suggestions to "
                + bundle.getCustomerInfo().getPowerType()
                + " capacity originator: "
                + capacityOriginator.getCapacityName());
            ((ProfileRecommendation.Listener) capacityOriginator)
                .handleProfileRecommendationPerSub(rec, sub, capacityOriginator.getCurrentForecast());
          }
          else {
            log.info(bundle.getName()
                + ": No beneficial profile permutations for "
                + bundle.getCustomerInfo().getPowerType()
                + " capacity originator: "
                + capacityOriginator.getCapacityName());
          }
        }
      }
    }
  }

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

  /**
   * Predict how record will be shifted. Computed for one customer.
   * Sent to TariffEvaluationHelper as a part of evaluateTariffs().
   *
   * @param usageForecast
   * @return
   */
  @Override
  public double[] adjustForecastPerTariff (HashMap originator2usage, TariffSubscription dummySubscription, CapacityBundle bundle)
  {
    // create a dummy subscription for the 'tariff' parameter
    List subscriptions = new ArrayList<>();
    subscriptions.add(dummySubscription);
    recommendProfilesToBundle(bundle, subscriptions);

    int nextTimeslot = service.getTimeslotRepo().currentSerialNumber() + 1;

    HashMap originator2shiftedUsage = new HashMap<>();
    for (CapacityOriginator capacityOriginator : bundle.getCapacityOriginators()) {
      if (capacityOriginator instanceof ProfileRecommendation.Listener) {

        // Daniel: here I changed the code that used to call handleProfileRecommendations
        for (TariffSubscription sub : subscriptions) { // THERE SHOULD ONLY BE 1 (dummy) subscription

          CapacityProfile chosenProfile = capacityOriginator.getForecastPerSubStartingAt(nextTimeslot, sub);

          // converting profile to array
          double[] usage = new double[CapacityProfile.NUM_TIMESLOTS];
          for (int i = 0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
            usage[i] = chosenProfile.getCapacity(i);
          }

          originator2shiftedUsage.put(capacityOriginator, usage);
          //// add usage profile to total bundle usage
          //for (int i=0; i < result.length; ++i) {
          //  result[i] += chosenProfile.getCapacity(i);
          //}

          // RESTORE: MUST REMOVE TO AVOID MEMORY LEAK
          //forecastPerSub.remove(sub)
        }
      }
    }
    return super.adjustForecastPerTariff(originator2shiftedUsage, dummySubscription, bundle);
  }

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

  private ProfileRecommendation
  getProfileRecommendationPerSub (CapacityOriginator capacityOriginator,
                                  CapacityBundle bundle,
                                  ForecastRecord forecastRecord,
                                  Map>> permsPerSub,
                                  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);
      if (isPermutationAcceptable(capacityOriginator,
          bundle.getOptimizerStructure(), usageCharge,
          forecastRecord.usageCharge)) {
        Opinion opinion = rec.new Opinion();
        // avoid duplication
        opinion.usageCharge = usageCharge;
        opinion.profileChange = forecastRecord.capacityProfile.distanceTo(perm);
        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.getProfileChangeWeight(),
        optimizerStructure.getBundleValueWeight());
    rec.computeUtilities();
    rec.computeProbabilities(optimizerStructure.getRationalityFactor());
  }

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

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

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

  private class ForecastRecord
  {
    CapacityProfile capacityProfile;
    double usageCharge;

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






© 2015 - 2025 Weber Informatics LLC | Privacy Policy