
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 java.util.AbstractMap;
import java.util.Map;
import java.util.HashMap;
import java.util.Collections;
import org.powertac.common.state.Domain;
import org.powertac.common.state.StateChange;
/**
* Contains maps of opinions, scores, utilities, and choice probabilities for each CapacityProfile.
*
* @author Prashant Reddy
*/
@Domain
public class ProfileRecommendation
{
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 HashMap();
private final Map utilities = new HashMap();
private final Map probabilities = new HashMap();
ProfileRecommendation()
{
opinions = new HashMap();
}
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);
double usageChargeScoringSign = opinion.normUsageCharge > 0 ? +1.0 : -1.0;
Double score = usageChargeScoringSign * opinion.normUsageCharge
+ profileChangeWeight * opinion.normProfileChange
+ bundleValueWeight * opinion.normBundleValue;
scores.put(profile, score);
}
}
@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 INNER CLASSES
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;
public String toString()
{
return "Opinion:[" + usageCharge + ", " + profileChange + ", " + bundleValue + ", "
+ normUsageCharge + ", " + normProfileChange + ", " + normBundleValue + "]";
}
}
public interface Listener
{
void handleProfileRecommendation(ProfileRecommendation rec);
}
} // end class
© 2015 - 2025 Weber Informatics LLC | Privacy Policy