source: CSE3210/agent68/bidding/bidding.py

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

#6 Added CSE3210 parties

File size: 11.7 KB
Line 
1from multiprocessing import Value
2
3from geniusweb.opponentmodel.FrequencyOpponentModel import FrequencyOpponentModel
4from geniusweb.profile.Profile import Profile
5from geniusweb.issuevalue.Bid import Bid
6from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
7from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
8from tudelft.utilities.immutablelist.JoinedList import JoinedList
9from geniusweb.inform.Settings import Settings
10from geniusweb.profileconnection.ProfileConnectionFactory import (
11 ProfileConnectionFactory,
12)
13from time import sleep, time as clock
14from decimal import Decimal
15
16from random import randint, random
17from typing import cast, Dict, List, Set, Collection
18
19# from main.bidding.extended_util_space import ExtendedUtilSpace
20# from Group68_NegotiationAssignment_Agent.Group68_NegotiationAssignment_Agent.bidding.extended_util_space import ExtendedUtilSpace
21from .extended_util_space import ExtendedUtilSpace
22from geniusweb.progress.Progress import Progress
23import numpy as np
24
25
26class Bidding():
27
28 def __init__(self) -> None:
29 self._profileint: ProfileInterface = None # type:ignore
30 self._utilspace: LinearAdditive = None # type:ignore
31 self._me: PartyId = None # type:ignore
32 self._progress: Progress = None # type:ignore
33 self._extendedspace: ExtendedUtilSpace = None # type:ignore
34 self._e: float = 0.002
35 self._settings: Settings = None # type:ignore
36
37 self._best_backup_bid: Bid = None
38 self._opp_utility = None
39 self._bids_to_make_stack: [Bid] = []
40 self._resBidValue = Decimal('0.0')
41 self._recentBidScore = [Decimal('0.0') for _ in range(5)]
42 self._utilW = 0.75
43 self._leniencyW = 0.25
44 self._leniencyBase = 0.35
45
46 def initBidding(self, info: Settings, reporter):
47 self._settings = info
48 self._me = self._settings.getID()
49 self._progress = self._settings.getProgress()
50 self._profileint = ProfileConnectionFactory.create(
51 self._settings.getProfile().getURI(), reporter
52 )
53 resBid = self._profileint.getProfile().getReservationBid()
54 params = self._settings.getParameters()
55
56 self._utilW = params.getDouble("utilWeight", 0.8, 0.0, 1.0)
57 self._leniencyW = params.getDouble("leniencyWeight", 0.2, 0.0, 1.0)
58 self._leniencyBase = params.getDouble("leniencyBase", 0.4, 0.0, 1.0)
59
60 if (resBid):
61 self._resBidValue = self._profileint.getProfile().getUtility(resBid)
62 else:
63 self._resBidValue = 0.0
64
65 def updateProgress(self, progress):
66 self._progress = progress
67
68 def getE(self) -> float:
69 """
70 @return the E value that controls the party's behaviour. Depending on the
71 value of e, extreme sets show clearly different patterns of
72 behaviour [1]:
73
74 1. Boulware: For this strategy e < 1 and the initial offer is
75 maintained till time is almost exhausted, when the agent concedes
76 up to its reservation value.
77
78 2. Conceder: For this strategy e > 1 and the agent goes to its
79 reservation value very quickly.
80
81 3. When e = 1, the price is increased linearly.
82
83 4. When e = 0, the agent plays hardball.
84 """
85 return self._e
86
87 def receivedBid(self, bid: Bid):
88 self._recentBidScore.sort()
89
90 profile = cast(LinearAdditive, self._profileint.getProfile())
91
92 bidUtility = profile.getUtility(bid)
93
94 if (self._recentBidScore[0] < bidUtility):
95 self._recentBidScore[0] = bidUtility
96
97 self.updateBestBackupBid(bid, bidUtility)
98
99 def leniencyThresh(self):
100 """
101 Modify acceptance threshold based on the recent offers from the opponent.
102 Good offers -> Lower threshold
103 Bad offers -> Higher threshold
104
105 Returns:
106 _type_: _description_
107 """
108 avgRecentBids = float(sum(self._recentBidScore) / len(self._recentBidScore))
109
110 return np.clip((1 - avgRecentBids) + self._leniencyBase, 0, 1)
111
112 def updateBestBackupBid(self, bid: Bid, bidScore):
113 """Updates best bid proposed by the opponent so far.
114 In order to have a backup bid in the final few rounds.
115
116 Args:
117 bid (Bid): _description_
118 """
119 if self._best_backup_bid is None:
120 self._best_backup_bid = bid
121 return
122
123 profile = cast(LinearAdditive, self._profileint.getProfile())
124 if profile.getUtility(self._best_backup_bid) < bidScore:
125 self._best_backup_bid = bid
126
127 def updateOpponentUtility(self, oppUtility):
128 """Passes the estimated opponent utility function to use during sorting the list of bids gotten
129
130 Args: opp_utility_func: estimated opponent utility function from frequency analysis
131 """
132 self._opp_utility = oppUtility
133
134 def setProfile(self, profile: Profile):
135 self._profileint = profile
136
137 def setE(self, E: float):
138 self._e = E
139
140 def _updateUtilSpace(self) -> LinearAdditive: # throws IOException
141 newutilspace = self._profileint.getProfile()
142 if not newutilspace == self._utilspace:
143 self._utilspace = cast(LinearAdditive, newutilspace)
144 self._extendedspace = ExtendedUtilSpace(self._utilspace)
145 return self._utilspace
146
147 """Method to select bids to make. Works with stateful stack- _bids_to_make_stack.
148 Makes 2 bids for every utility goal when the stack is empty and pops the stack if it is not.
149
150 Args: opponent: estimated frequency model of the opponent used to sort and select bids to make
151 """
152
153 def makeBid(self, opponent: FrequencyOpponentModel):
154 freqs: Dict[str, Dict[Value, int]] = opponent._bidFrequencies
155
156 time = self._progress.get(round(clock() * 1000))
157
158 # if this is the first round of negotiation where the max and second max frequency issue-value are uninitialized.
159 # return the max bid from self._extendedspace
160 if len(freqs) == 0 or time <= 0.02:
161 options = self._extendedspace.getBids(self._extendedspace.getMax())
162 outBid = options.get(randint(0, options.size() - 1))
163
164 return outBid
165
166 profile = cast(LinearAdditive, self._profileint.getProfile())
167 # Following find largest and second-largest frequency issue-value pairs in one iteration through table
168 max_freq = -1
169 max_issue: str = None
170 max_value: Value = None
171 second_max: str = -1
172 second_max_issue: Value = None
173 second_max_value = None
174 for issue in freqs:
175 for value in freqs[issue]:
176 frequency = freqs[issue][value]
177 if frequency >= max_freq:
178 max_issue = issue
179 max_value = value
180 max_freq = frequency
181 elif frequency >= second_max:
182 second_max = frequency
183 second_max_value = value
184 second_max_issue = issue
185
186 # Get our bids
187 # If we don't have any bids pre-calculated, calculate them
188 if len(self._bids_to_make_stack) == 0:
189 time = self._progress.get(round(clock() * 1000))
190 leniencyValue = self.leniencyThresh()
191 utilityGoal = float(self._getUtilityGoal(
192 time,
193 self.getE(),
194 self._extendedspace.getMin(),
195 self._extendedspace.getMax(),
196 ))
197
198 utilityGoal = Decimal(self._utilW * utilityGoal + self._leniencyW * leniencyValue)
199
200 options: ImmutableList[Bid] = self._extendedspace.getBids(utilityGoal)
201
202 if options.size() == 0:
203 # No good bid found - return bid with max utility
204 options = self._extendedspace.getBids(self._extendedspace.getMax())
205 outBid = options.get(randint(0, options.size() - 1))
206 return outBid
207
208 # filter based on the frequencies found above
209 filtered = list(filter(lambda bid: bid._issuevalues[max_issue] == max_value, options))
210 top = []
211 if len(filtered) == 0 or self._progress.get(round(clock() * 1000)) < 0.25:
212 util_predictor = lambda bid: opponent.getUtility(bid)
213
214 top = sorted(self._joinedSubList(options, 0, min(10, options.size())), key=util_predictor, reverse=True)
215 # if top[0] is smaller than best bid so far
216 if profile.getUtility(self._best_backup_bid) >= profile.getUtility(top[0]):
217 self._bids_to_make_stack.append(self._best_backup_bid)
218 else:
219 self._bids_to_make_stack.append(top[0])
220 else:
221 top = filtered[:min(10, len(filtered))]
222 util_predictor = lambda bid: opponent.getUtility(bid)
223 top = sorted(top, key=util_predictor, reverse=True)
224
225 # Proposing the bid from the opponent with the best utility so far
226 if len(top) >= 2:
227 # push two bids on stack if 2 were found else just the one
228 if profile.getUtility(self._best_backup_bid) >= profile.getUtility(top[1]):
229 self._bids_to_make_stack.append(self._best_backup_bid)
230 self._bids_to_make_stack.append(top[0])
231 # Return the next best calculated bid
232 return self._bids_to_make_stack.pop()
233
234 def _pickBestOpponentUtility(self, bidlist: ImmutableList[Bid]) -> List[Bid]:
235
236 outBids: List[Bid] = sorted(bidlist, key=self._opp_utility, reverse=True)
237
238 return outBids[0:2]
239
240 def _getUtilityGoal(
241 self, t: float, e: float, minUtil: Decimal, maxUtil: Decimal
242 ) -> Decimal:
243 """
244 @param t the time in [0,1] where 0 means start of nego and 1 the
245 end of nego (absolute time/round limit)
246 @param e the e value that determinses how fast the party makes
247 concessions with time. Typically around 1. 0 means no
248 concession, 1 linear concession, &gt;1 faster than linear
249 concession.
250 @param minUtil the minimum utility possible in our profile
251 @param maxUtil the maximum utility possible in our profile
252 @return the utility goal for this time and e value
253 """
254
255 # Minimum util value cannot be less than reservation bid value.
256 if minUtil < self._resBidValue:
257 minUtil = self._resBidValue
258
259 ft1 = Decimal(1)
260 if e != 0:
261 ft1 = round(Decimal(1 - t * e), 6) # defaults ROUND_HALF_UP
262 return max(min((minUtil + (maxUtil - minUtil) * ft1), maxUtil), minUtil)
263
264 def _isGood(self, bid: Bid) -> bool:
265 """
266 @param bid the bid to check
267 @return true iff bid is good for us according to three criterias mentioned in the report.
268 """
269 if bid == None or self._profileint == None:
270 return False
271 profile = cast(LinearAdditive, self._profileint.getProfile())
272
273 time = self._progress.get(round(clock() * 1000))
274
275 #Accept final round
276 if (time >= 0.99):
277 return True
278
279 leniency = self.leniencyThresh()
280 bidUtil = profile.getUtility(bid)
281 utilGoal = float(self._getUtilityGoal(
282 time,
283 self.getE(),
284 self._extendedspace.getMin(),
285 self._extendedspace.getMax(),
286 ))
287
288 return bidUtil >= self._resBidValue \
289 and bidUtil >= (self._utilW * utilGoal + self._leniencyW * leniency) \
290 and bidUtil >= profile.getUtility(self._best_backup_bid)
291
292 def _joinedSubList(self, list: JoinedList, start: int, end: int):
293 res = []
294 for i in range(start, end):
295 res.append(list.get(i))
296 return res
Note: See TracBrowser for help on using the repository browser.