source: CSE3210/agent43/frequency_opponent_model_group_43.py

Last change on this file was 74, checked in by wouter, 21 months ago

#6 Added CSE3210 parties

File size: 8.5 KB
Line 
1from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
2from geniusweb.opponentmodel.OpponentModel import OpponentModel
3from decimal import Decimal
4from decimal import Context
5from geniusweb.issuevalue.Domain import Domain
6from geniusweb.issuevalue.Bid import Bid
7from typing import Dict, Optional
8from geniusweb.issuevalue.Value import Value
9from geniusweb.actions.Action import Action
10from geniusweb.progress.Progress import Progress
11from geniusweb.actions.Offer import Offer
12from geniusweb.references.Parameters import Parameters
13from geniusweb.utils import val, HASH, toStr
14
15class FrequencyOpponentModel(UtilitySpace, OpponentModel):
16 '''
17 implements an {@link OpponentModel} by counting frequencies of bids placed by
18 the opponent.
19 <p>
20 NOTE: {@link NumberValue}s are also treated as 'discrete', so the frequency
21 of one value does not influence the influence the frequency of nearby values
22 (as you might expect as {@link NumberValueSetUtilities} is only affected by
23 the endpoints).
24 <p>
25 immutable.
26 '''
27
28 _DECIMALS = 4 # accuracy of our computations.
29
30 def __init__(self, domain: Optional[Domain],
31 freqs: Dict[str, Dict[Value, float]], total: int,
32 resBid: Optional[Bid]):
33 '''
34 internal constructor. DO NOT USE, see create. Assumes the freqs keyset is
35 equal to the available issues.
36
37 @param domain the domain. Should not be None
38 @param freqs the observed frequencies for all issue values. This map is
39 assumed to be a fresh private-access only copy.
40 @param total the total number of bids contained in the freqs map. This
41 must be equal to the sum of the Integer values in the
42 {@link #bidFrequencies} for each issue (this is not
43 checked).
44 @param resBid the reservation bid. Can be null
45 '''
46 self._domain = domain
47 self._bidFrequencies = freqs
48 self._totalBids = total
49 self._resBid = resBid
50
51 @staticmethod
52 def create() -> "FrequencyOpponentModel":
53 return FrequencyOpponentModel(None, {}, 0, None)
54
55 # Override
56 def With(self, newDomain: Domain, newResBid: Optional[Bid]) -> "FrequencyOpponentModel":
57 if newDomain == None:
58 raise ValueError("domain is not initialized")
59 # FIXME merge already available frequencies?
60 return FrequencyOpponentModel(newDomain,
61 {iss: {} for iss in newDomain.getIssues()},
62 0, newResBid)
63
64 # Override
65 def getUtility(self, bid: Bid) -> Decimal:
66 if self._domain == None:
67 raise ValueError("domain is not initialized")
68 if self._totalBids == 0:
69 return Decimal(1)
70 sum = Decimal(0)
71
72 # Assume different weights
73 dict = self.getWeight()
74
75
76 for issue in val(self._domain).getIssues():
77 if issue in bid.getIssues():
78 # Using estimated weights, compute the utility of the opponent for a given bid.
79 sum = sum + Context.multiply(Context(), self._getFraction(issue, val(bid.getValue(issue))),
80 Decimal.from_float(dict[issue]))
81
82 return round(sum, FrequencyOpponentModel._DECIMALS)
83
84 # Override
85 def getName(self) -> str:
86 if self._domain == None:
87 raise ValueError("domain is not initialized")
88 return "FreqOppModel" + str(hash(self)) + "For" + str(self._domain)
89
90 # Override
91 def getDomain(self) -> Domain:
92 return val(self._domain)
93
94 # Override
95 def WithAction(self, action: Action, progress: Progress) -> "FrequencyOpponentModel":
96 if self._domain == None:
97 raise ValueError("domain is not initialized")
98
99 if not isinstance(action, Offer):
100 return self
101
102 # Method altered so that it computes utilities in a more accurate way than just frequencies.
103 bid: Bid = action.getBid()
104 newFreqs: Dict[str, Dict[Value, float]] = self.cloneMap(self._bidFrequencies)
105 for issue in self._domain.getIssues(): # type:ignore
106 freqs: Dict[Value, float] = newFreqs[issue]
107 values_in_issue = len(newFreqs[issue])
108 value = bid.getValue(issue)
109 avg_value = 0.5
110 if value != None:
111 oldfreq = 0
112 if value in freqs:
113 oldfreq = freqs[value]
114
115 for i in freqs:
116 if freqs[i] == 0:
117 freqs[i] = avg_value
118
119 freqs[value] = oldfreq + 0.05 # type:ignore
120 if freqs[value] > 1:
121 freqs[value] = 1
122 factor = values_in_issue * avg_value / sum(freqs.values())
123 for k in freqs:
124 freqs[k] = freqs[k] * factor
125
126 return FrequencyOpponentModel(self._domain, newFreqs,
127 self._totalBids + 1, self._resBid)
128
129 def getCounts(self, issue: str) -> Dict[Value, float]:
130 '''
131 @param issue the issue to get frequency info for
132 @return a map containing a map of values and the number of times that
133 value was used in previous bids. Values that are possible but not
134 in the map have frequency 0.
135 '''
136 if self._domain == None:
137 raise ValueError("domain is not initialized")
138 if not issue in self._bidFrequencies:
139 return {}
140 return dict(self._bidFrequencies.get(issue)) # type:ignore
141
142 # Override
143 def WithParameters(self, parameters: Parameters) -> OpponentModel:
144 return self # ignore parameters
145
146 def _getFraction(self, issue: str, value: Value) -> Decimal:
147 '''
148 @param issue the issue to check
149 @param value the value to check
150 @return the fraction of the total cases that bids contained given value
151 for the issue.
152 '''
153 if self._totalBids == 0:
154 return Decimal(0.5)
155 # return Decimal(1)
156 if not (issue in self._bidFrequencies and value in self._bidFrequencies[issue]):
157 return Decimal(0)
158
159 return Decimal(self._bidFrequencies[issue][value])
160
161 # return round((Decimal(freq) / self._totalBids), FrequencyOpponentModel._DECIMALS) # type:ignore
162
163 @staticmethod
164 def cloneMap(freqs: Dict[str, Dict[Value, float]]) -> Dict[str, Dict[Value, float]]:
165 '''
166 @param freqs
167 @return deep copy of freqs map.
168 '''
169 map: Dict[str, Dict[Value, float]] = {}
170 for issue in freqs:
171 map[issue] = dict(freqs[issue])
172 return map
173
174 # Override
175 def getReservationBid(self) -> Optional[Bid]:
176 return self._resBid
177
178 def __eq__(self, other):
179 return isinstance(other, self.__class__) and \
180 self._domain == other._domain and \
181 self._bidFrequencies == other._bidFrequencies and \
182 self._totalBids == other._totalBids and \
183 self._resBid == other._resBid
184
185 def __hash__(self):
186 return HASH((self._domain, self._bidFrequencies, self._totalBids, self._resBid))
187
188 # Override
189
190 # Override
191 def __repr__(self) -> str:
192 return "FrequencyOpponentModel[" + str(self._totalBids) + "," + \
193 toStr(self._bidFrequencies) + "]"
194
195
196 def toString(self):
197 return f"FrequencyOpponentModel({self._totalBids}, {self._bidFrequencies}"
198
199 # Obtain estimated weights of issues for the opponent.
200 def getWeight(self):
201 dict = {}
202 total_sum = 0
203 for issue in val(self._domain).getIssues():
204 # get freq of the values used in every issue
205 hash = self.getCounts(issue)
206
207 # pick out the max freq of the values from each issue
208 value_with_highest_frequency = max(hash, key=hash.get)
209 dict[issue] = hash[value_with_highest_frequency]
210 # keep track of total sum of "weights" (frequencies)
211 total_sum += dict[issue]
212
213 if total_sum == 0:
214 for issue in dict:
215 dict[issue] = 0
216
217 # get their "relative importance", relative to all other max frequencies from the other issue
218 else:
219 for issue in dict:
220 dict[issue] = (dict[issue] / total_sum)
221 return dict
Note: See TracBrowser for help on using the repository browser.