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

org.powertac.factoredcustomer.AdaptiveCapacityOriginator 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.powertac.common.Tariff;
import org.powertac.common.TariffSubscription;
import org.powertac.common.Timeslot;
import org.powertac.common.repo.RandomSeedRepo;
import org.powertac.common.repo.TimeslotRepo;
import org.powertac.common.state.Domain;
import org.powertac.factoredcustomer.ProfileOptimizerStructure.ProfileSelectionMethod;
import org.powertac.factoredcustomer.ProfileRecommendation.Opinion;
import org.powertac.factoredcustomer.ProfileRecommendation.ScoringFactor;
import org.powertac.factoredcustomer.utils.SeedIdGenerator;

import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;


/**
 * Extends @code{DefaultCapacityOriginator} to adapt to the learning behavior
 * of @code{LearningUtilityOptimizer}.
 *
 * @author Prashant Reddy
 */
@Domain
final class AdaptiveCapacityOriginator extends DefaultCapacityOriginator
    implements ProfileRecommendation.Listener
{
  private static Logger log = LogManager.getLogger(AdaptiveCapacityOriginator.class);

  private final ProfileOptimizerStructure optimizerStructure;
  private final Random recommendationHandler;
  private Map> forecastCapacitiesPerSub;
  private Map tariff2inconv;
  private TimeslotRepo timeslotRepo;

  public AdaptiveCapacityOriginator (FactoredCustomerService service,
                                     CapacityStructure capacityStructure,
                                     DefaultCapacityBundle bundle)
  {
    super(service, capacityStructure, bundle);

    optimizerStructure = getParentBundle().getOptimizerStructure();

    timeslotRepo = service.getTimeslotRepo();
    RandomSeedRepo randomSeedRepo = service.getRandomSeedRepo();
    recommendationHandler =
        new Random(randomSeedRepo
            .getRandomSeed("factoredcustomer.AdaptiveCapacityOriginator",
                SeedIdGenerator.getId(),
                "RecommendationHandler")
            .getValue());

    forecastCapacitiesPerSub = new HashMap<>();
    tariff2inconv = new HashMap<>();
  }

  @Override
  /** @code{ProfileRecommendation.Listener} **/
  public void handleProfileRecommendation (ProfileRecommendation globalRec)
  {
    double draw1 = recommendationHandler.nextFloat();
    if (draw1 > optimizerStructure.getReactivityFactor()) {
      log.info(logIdentifier + ": Ignoring received profile recommendation");
      return;
    }

    ProfileRecommendation localRec;
    double draw2 = recommendationHandler.nextFloat();
    if (draw2 < optimizerStructure.getReceptivityFactor()) {
      log.info(logIdentifier + ": Adopting profile recommendation as received");
      localRec = globalRec;
    }
    else {
      localRec = new ProfileRecommendation(globalRec.getOpinions());

      Map weights = new HashMap<>();
      weights.put(ScoringFactor.PROFILE_CHANGE, optimizerStructure.getProfileChangeWeight());
      weights.put(ScoringFactor.BUNDLE_VALUE, optimizerStructure.getBundleValueWeight());
      localRec.computeScores(weights);

      localRec.computeUtilities();
      localRec.computeProbabilities(optimizerStructure.getRationalityFactor());
    }
    CapacityProfile chosenProfile;
    if (optimizerStructure.getProfileSelectionMethod() == ProfileSelectionMethod.BEST_UTILITY) {
      chosenProfile = selectBestProfileInRecommendation(localRec);
    }
    else { // LOGIT_CHOICE
      chosenProfile = drawProfileFromRecommendation(localRec);
    }
    overwriteForecastCapacities(timeslotRepo.currentTimeslot(), chosenProfile);
  }

  @Override
  /** @code{ProfileRecommendation.Listener} **/
  public void handleProfileRecommendationPerSub (ProfileRecommendation globalRec,
                                                 TariffSubscription sub,
                                                 CapacityProfile capacityProfile)
  {
    double draw1 = recommendationHandler.nextFloat();
    if (draw1 > optimizerStructure.getReactivityFactor()) {
      log.info(logIdentifier + ": Ignoring received profile recommendation");
      return;
    }

    ProfileRecommendation localRec;
    double draw2 = recommendationHandler.nextFloat();
    if (draw2 < optimizerStructure.getReceptivityFactor()) {
      log.info(logIdentifier + ": Adopting profile recommendation as received");
      localRec = globalRec;
    }
    else {
      localRec = new ProfileRecommendation(globalRec.getOpinions());

      Map weights = new HashMap<>();
      weights.put(ScoringFactor.PROFILE_CHANGE, optimizerStructure.getProfileChangeWeight());
      weights.put(ScoringFactor.BUNDLE_VALUE, optimizerStructure.getBundleValueWeight());
      localRec.computeScores(weights);

      localRec.computeUtilities();
      localRec.computeProbabilities(optimizerStructure.getRationalityFactor());
    }
    CapacityProfile chosenProfile;
    if (optimizerStructure.getProfileSelectionMethod() == ProfileSelectionMethod.BEST_UTILITY) {
      chosenProfile = selectBestProfileInRecommendation(localRec);
    }
    else { // LOGIT_CHOICE
      chosenProfile = drawProfileFromRecommendation(localRec);
    }
    overwriteForecastCapacitiesPerSub(timeslotRepo.currentTimeslot(),
        chosenProfile, sub);
    // record inconv
    // (non-scaled) score = (charge / a) + w x d(e,e') / b
    // so a x score is supposed to be comparable to profile charge,
    // taking inconv into account
    Opinion opinionOnChosenProfile = localRec.getOpinions().get(chosenProfile);
    double originalScore = localRec.getNonScaledScore(chosenProfile);
    // a = charge / normalized-charge
    double costNormalizationConst = (opinionOnChosenProfile.normUsageCharge != 0) ? opinionOnChosenProfile.usageCharge / opinionOnChosenProfile.normUsageCharge : 0;
    // scaled-inconv-factor = |a| x score - charge  = w|a|/b x d(e,e')
    double inconvenienceFactor = Math.abs(costNormalizationConst) * originalScore - opinionOnChosenProfile.usageCharge;
    tariff2inconv.put(sub.getTariff(), inconvenienceFactor);
  }

  private CapacityProfile selectBestProfileInRecommendation (ProfileRecommendation rec)
  {
    double bestUtility = Double.MIN_VALUE;
    CapacityProfile bestProfile = null;
    for (AbstractMap.Entry entry : rec.getUtilities().entrySet()) {
      if (entry.getValue() > bestUtility) {
        bestUtility = entry.getValue();
        bestProfile = entry.getKey();
      }
    }
    if (bestProfile == null) {
      throw new Error("Best profile in recommendation is null!");
    }
    return bestProfile;
  }

  private CapacityProfile drawProfileFromRecommendation (ProfileRecommendation rec)
  {
    double draw = recommendationHandler.nextFloat();
    // sort map entries, for reproducability
    ArrayList> l =
        new ArrayList<>(rec.getProbabilities().entrySet());
    // TODO Refactor once the new visualizer can handle lambdas
    //Collections.sort(l, (o1, o2) -> o1.getValue().compareTo(o2.getValue()));
    Collections.sort(l, new Comparator>(){
      @Override
      public int compare(Map.Entry o1, Map.Entry o2) {
        return o1.getValue().compareTo(o2.getValue());
      }});
    // use the sorted map and the draw to sample an entry
    double sumProb = 0.0;
    for (AbstractMap.Entry entry : l) {
      sumProb += entry.getValue();
      if (draw < sumProb) {
        return entry.getKey();
      }
    }
    throw new Error("Drawing from recommendation resulted in a null profile!");
  }

  private void overwriteForecastCapacities (Timeslot timeslot, CapacityProfile profile)
  {
    Timeslot slider = timeslot;
    for (int i = 0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
      forecastCapacities.put(slider.getSerialNumber(), profile.getCapacity(i));
      slider = timeslotRepo.getNext(slider);
    }
  }

  private void overwriteForecastCapacitiesPerSub (Timeslot timeslot, CapacityProfile profile, TariffSubscription sub)
  {
    Timeslot slider = timeslot;
    for (int i = 0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
      int futureTimeslot = slider.getSerialNumber();
      double futureCapacity = profile.getCapacity(i);
      insertIntoForecastCapacitiesPerSub(sub, futureTimeslot, futureCapacity);
      slider = timeslotRepo.getNext(slider);
    }
  }

  private void insertIntoForecastCapacitiesPerSub (TariffSubscription sub,
                                                   int futureTimeslot,
                                                   double futureCapacity)
  {
    Map ts2capacity = forecastCapacitiesPerSub.get(sub);
    if (null == ts2capacity) {
      ts2capacity = new HashMap<>();
      forecastCapacitiesPerSub.put(sub, ts2capacity);
    }
    ts2capacity.put(futureTimeslot, futureCapacity);
  }

  @Override
  public double getShiftingInconvenienceFactor (Tariff tariff)
  {
    Double inconv = tariff2inconv.get(tariff);
    // shouldn't happen that it is null..
    if (inconv != null) {
      return inconv;
    }
    log.error("How come inconvenience is null?");
    return 0;
  }

  @Override
  public double useCapacity (TariffSubscription subscription)
  {
    int timeslot = timeslotRepo.currentSerialNumber();

    // we don't re-adjust for current weather here;
    // would not be accurate for wind/solar production
    //
    // Daniel: try to get per sub first, if doesn't work get the
    // old, averaged one
    double forecastCapacity = getForecastCapacityPerSub(timeslot, subscription);
    double adjustedCapacity = forecastCapacity;
    adjustedCapacity = adjustCapacityForSubscription(
        timeslot, adjustedCapacity, subscription);
    if (Double.isNaN(adjustedCapacity)) {
      throw new Error("Adjusted capacity is NaN for forecast capacity = "
          + forecastCapacity);
    }

    adjustedCapacity = truncateTo2Decimals(adjustedCapacity);
    actualCapacities.put(timeslot, adjustedCapacity);
    log.info(logIdentifier + ": Adjusted capacity for tariff "
        + subscription.getTariff().getId() + " = " + adjustedCapacity);
    return adjustedCapacity;
  }

  // Daniel: some needed additions
  @Override
  public CapacityProfile getCurrentForecastPerSub (TariffSubscription sub)
  {
    int timeslot = timeslotRepo.currentSerialNumber();
    return getForecastPerSubStartingAt(timeslot, sub);
  }

  @Override
  public CapacityProfile getForecastPerSubStartingAt (int startingTimeslot,
                                                      TariffSubscription subscription)
  {
    int timeslot = startingTimeslot;
    List values = new ArrayList<>();
    for (int i = 0; i < CapacityProfile.NUM_TIMESLOTS; ++i) {
      values.add(getForecastCapacityPerSub(timeslot, subscription));
      timeslot += 1;
    }
    return new CapacityProfile(values);
  }

  private Double getForecastCapacityPerSub (int timeslot,
                                            TariffSubscription subscription)
  {
    Map ts2capacity = forecastCapacitiesPerSub.get(subscription);

    if (null == ts2capacity || null == ts2capacity.get(timeslot)) {
      return getForecastCapacity(timeslot);
    }
    else {
      return ts2capacity.get(timeslot);
    }
  }
}






© 2015 - 2025 Weber Informatics LLC | Privacy Policy