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

org.powertac.factoredcustomer.ProfileRecommendation 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.powertac.common.TariffSubscription;
import org.powertac.common.state.Domain;
import org.powertac.common.state.StateChange;

import java.util.AbstractMap;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;


/**
 * Contains maps of opinions, scores, utilities, and choice probabilities for each CapacityProfile.
 *
 * @author Prashant Reddy
 */
@Domain
public class ProfileRecommendation
{
  private static final int SCORE_SCALING_FACTOR = 10000;

  enum ScoringFactor
  {
    USAGE_CHARGE, PROFILE_CHANGE, BUNDLE_VALUE
  }

  private static final double UTILITY_RANGE_MAX_VALUE = 3.0;  // range = [-3.0, +3.0]

  private final Map opinions;
  private final Map scores = new LinkedHashMap<>();
  private final Map utilities = new LinkedHashMap<>();
  private final Map probabilities = new LinkedHashMap<>();

  ProfileRecommendation ()
  {
    opinions = new LinkedHashMap<>();
  }

  ProfileRecommendation (Map map)
  {
    opinions = map;
  }

  @StateChange
  public void setOpinion (CapacityProfile profile, Opinion opinion)
  {
    opinions.put(profile, opinion);
  }

  public Map getOpinions ()
  {
    return opinions;
  }

  @StateChange
  public void setScore (CapacityProfile profile, Double score)
  {
    scores.put(profile, score);
  }

  public Map getScores ()
  {
    return scores;
  }

  public Map getUtilities ()
  {
    return utilities;
  }

  public Map getProbabilities ()
  {
    return probabilities;
  }

  public boolean isEmpty ()
  {
    return opinions.size() == 0;
  }

  @StateChange
  public void normalizeOpinions ()
  {
    double sumUsageCharge = 0.0;
    double sumProfileChange = 0.0;
    double sumBundleValue = 0.0;

    for (Opinion opinion : opinions.values()) {
      sumUsageCharge += opinion.usageCharge;
      sumProfileChange += opinion.profileChange;
      sumBundleValue += opinion.bundleValue;
    }
    for (Opinion opinion : opinions.values()) {
      opinion.normUsageCharge = sumUsageCharge == 0.0
          ? 0.0
          : opinion.usageCharge / sumUsageCharge;
      opinion.normProfileChange = sumProfileChange == 0.0
          ? 0.0 :
          opinion.profileChange / sumProfileChange;
      opinion.normBundleValue = sumBundleValue == 0.0
          ? 0.0
          : opinion.bundleValue / sumBundleValue;
    }
  }

  public void computeScores (Map weights)
  {
    computeScores(weights.get(ScoringFactor.PROFILE_CHANGE),
        weights.get(ScoringFactor.BUNDLE_VALUE));
  }

  @StateChange
  public void computeScores (double profileChangeWeight,
                             double bundleValueWeight)
  {
    for (CapacityProfile profile : opinions.keySet()) {
      Opinion opinion = opinions.get(profile);
      // Daniel: here was a bug that prefer more expensive consumption tariffs...
      double usageChargeScoringSign = opinion.usageCharge > 0 ? +1.0 : -1.0;
      Double score = usageChargeScoringSign * opinion.normUsageCharge
          + profileChangeWeight * opinion.normProfileChange
          + bundleValueWeight * opinion.normBundleValue;
      // HACK BY DANIEL TO OVERCOME THE 0.0001 in computeUtilities()
      scores.put(profile, score * SCORE_SCALING_FACTOR);
    }
  }

  @StateChange
  public void computeUtilities ()
  {
    if (scores.size() == 1) {
      utilities.put(scores.keySet().iterator().next(), UTILITY_RANGE_MAX_VALUE);
      return;
    }
    double best = Collections.max(scores.values());
    double worst = Collections.max(scores.values());
    double sum = 0.0;
    for (Double score : scores.values()) {
      sum += score;
    }
    double mean = sum / scores.size();
    double basis = Math.max((best - mean), (mean - worst));
    if (Math.abs(basis - 0.0) < 0.0001) {
      for (AbstractMap.Entry entry : scores.entrySet()) {
        utilities.put(entry.getKey(), UTILITY_RANGE_MAX_VALUE);
      }
    }
    else {
      for (AbstractMap.Entry entry : scores.entrySet()) {
        double utility = ((entry.getValue() - mean) / basis) * UTILITY_RANGE_MAX_VALUE;
        utilities.put(entry.getKey(), utility);
      }
    }
  }

  @StateChange
  public void computeProbabilities (double rationality)
  {
    // multinomical logit choice model; utilities expected to be in [-3.0, +3.0]

    double denominator = 0.0;
    for (AbstractMap.Entry entry : utilities.entrySet()) {
      double numerator = Math.exp(rationality * utilities.get(entry.getKey()));
      probabilities.put(entry.getKey(), numerator);
      denominator += numerator;
    }
    for (AbstractMap.Entry entry : probabilities.entrySet()) {
      double numerator = entry.getValue();
      double probability = numerator / denominator;  // normalize
      if (Double.isNaN(probability)) {
        System.err.println(this.getClass().getCanonicalName()
            + ": Computed probability is NaN!");
        System.err.println("  *** opinions: " + opinions.keySet()
            + ": " + opinions.values());
        System.err.println("  *** scores: " + scores.keySet()
            + ": " + scores.values());
        System.err.println("  *** utilities: " + utilities.keySet()
            + ": " + utilities.values());
        System.err.println("  *** probabilities: " + probabilities.keySet()
            + ": " + probabilities.values());
        throw new Error("Computed probability is NaN!");
      }
      entry.setValue(probability);
    }
  }

  public class Opinion
  {
    // raw computed metrics
    double usageCharge; // (-inf, +inf) under current tariff subscriptions
    double profileChange;  // [0, +inf)
    double bundleValue;  // [0, +inf)

    // normalized metrics
    double normUsageCharge;
    double normProfileChange;
    double normBundleValue;

    @Override
    public String toString ()
    {
      return "Opinion:[" + usageCharge + ", " + profileChange + ", " +
          bundleValue + ", " + normUsageCharge + ", " + normProfileChange +
          ", " + normBundleValue + "]";
    }
  }

  public interface Listener
  {
    void handleProfileRecommendation (ProfileRecommendation rec);

    void handleProfileRecommendationPerSub (ProfileRecommendation rec,
                                            TariffSubscription sub,
                                            CapacityProfile capacityProfile);
  }

  public double getNonScaledScore (CapacityProfile chosenProfile)
  {
    return scores.get(chosenProfile) / SCORE_SCALING_FACTOR;
  }
}





© 2015 - 2025 Weber Informatics LLC | Privacy Policy