package geniusweb.opponentmodel; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import geniusweb.actions.Action; import geniusweb.actions.Offer; import geniusweb.issuevalue.Bid; import geniusweb.issuevalue.Domain; import geniusweb.issuevalue.NumberValue; import geniusweb.issuevalue.Value; import geniusweb.profile.utilityspace.NumberValueSetUtilities; import geniusweb.profile.utilityspace.UtilitySpace; import geniusweb.progress.Progress; import geniusweb.references.Parameters; /** * implements an {@link OpponentModel} by counting frequencies of bids placed by * the opponent. *

* NOTE: {@link NumberValue}s are also treated as 'discrete', so the frequency * of one value does not influence the influence the frequency of nearby values * (as you might expect as {@link NumberValueSetUtilities} is only affected by * the endpoints). *

* immutable. */ public class FrequencyOpponentModel implements UtilitySpace, OpponentModel { private static final int DECIMALS = 4; // accuracy of our computations. private final Domain domain; private final Map> bidFrequencies; private final BigInteger totalBids; private final Bid resBid; public FrequencyOpponentModel() { this.domain = null; this.bidFrequencies = null; this.totalBids = BigInteger.ZERO; this.resBid = null; } /** * internal constructor. Assumes the freqs keyset is equal to the available * issues. * * @param domain the domain * @param freqs the observed frequencies for all issue values. This map is * assumed to be a fresh private-access only copy. * @param total the total number of bids contained in the freqs map. This * must be equal to the sum of the Integer values in the * {@link #bidFrequencies} for each issue (this is not * checked). * @param resBid the reservation bid. Can be null */ protected FrequencyOpponentModel(Domain domain, Map> freqs, BigInteger total, Bid resBid) { if (domain == null) { throw new IllegalStateException("domain is not initialized"); } this.domain = domain; this.bidFrequencies = freqs; this.totalBids = total; this.resBid = resBid; } @Override public FrequencyOpponentModel with(Domain newDomain, Bid newResBid) { if (newDomain == null) { throw new NullPointerException("domain is not initialized"); } // FIXME merge already available frequencies? return new FrequencyOpponentModel(newDomain, newDomain.getIssues().stream().collect( Collectors.toMap(iss -> iss, iss -> new HashMap<>())), BigInteger.ZERO, newResBid); } @Override public BigDecimal getUtility(Bid bid) { if (domain == null) { throw new IllegalStateException("domain is not initialized"); } if (totalBids == BigInteger.ZERO) { return BigDecimal.ONE; } BigDecimal sum = BigDecimal.ZERO; // Assume all issues have equal weight. for (String issue : domain.getIssues()) { if (bid.getIssues().contains(issue)) { sum = sum.add(getFraction(issue, bid.getValue(issue))); } } return sum.divide(new BigDecimal(bidFrequencies.size()), DECIMALS, BigDecimal.ROUND_HALF_UP); } @Override public String getName() { if (domain == null) { throw new IllegalStateException("domain is not initialized"); } return "FreqOppModel" + this.hashCode() + "For" + domain; } @Override public Domain getDomain() { return domain; } @Override public FrequencyOpponentModel with(Action action, Progress progress) { if (domain == null) { throw new IllegalStateException("domain is not initialized"); } if (!(action instanceof Offer)) return this; Bid bid = ((Offer) action).getBid(); Map> newFreqs = cloneMap(bidFrequencies); for (String issue : domain.getIssues()) { Map freqs = newFreqs.get(issue); Value value = bid.getValue(issue); if (value != null) { Integer oldfreq = freqs.get(value); if (oldfreq == null) { oldfreq = 0; } freqs.put(value, oldfreq + 1); } } return new FrequencyOpponentModel(domain, newFreqs, totalBids.add(BigInteger.ONE), resBid); } /** * * @param issue the issue to get frequency info for * @return a map containing a map of values and the number of times that * value was used in previous bids. Values that are possible but not * in the map have frequency 0. */ public Map getCounts(String issue) { if (domain == null) { throw new IllegalStateException("domain is not initialized"); } if (!(bidFrequencies.containsKey(issue))) { return Collections.emptyMap(); } return Collections.unmodifiableMap(bidFrequencies.get(issue)); } @Override public OpponentModel with(Parameters parameters) { return this; // ignore parameters } /** * * @param issue the issue to check * @param value the value to check * @return the fraction of the total cases that bids contained given value * for the issue. */ private BigDecimal getFraction(String issue, Value value) { if (totalBids == BigInteger.ZERO) { return BigDecimal.ONE; } Integer freq = bidFrequencies.get(issue).get(value); if (freq == null) { freq = 0; } return new BigDecimal(freq).divide(new BigDecimal(totalBids), DECIMALS, BigDecimal.ROUND_HALF_UP); } /** * * @param freqs * @return deep copy of freqs map. */ private static Map> cloneMap( Map> freqs) { Map> map = new HashMap<>(); for (String issue : freqs.keySet()) { map.put(issue, new HashMap(freqs.get(issue))); } return map; } @Override public Bid getReservationBid() { return resBid; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((bidFrequencies == null) ? 0 : bidFrequencies.hashCode()); result = prime * result + ((domain == null) ? 0 : domain.hashCode()); result = prime * result + ((totalBids == null) ? 0 : totalBids.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; FrequencyOpponentModel other = (FrequencyOpponentModel) obj; if (bidFrequencies == null) { if (other.bidFrequencies != null) return false; } else if (!bidFrequencies.equals(other.bidFrequencies)) return false; if (domain == null) { if (other.domain != null) return false; } else if (!domain.equals(other.domain)) return false; if (totalBids == null) { if (other.totalBids != null) return false; } else if (!totalBids.equals(other.totalBids)) return false; return true; } @Override public String toString() { return "FrequencyOpponentModel[" + totalBids + "," + bidFrequencies + "]"; } }