source: CSE3210/agent67/agent67.py

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

#6 Added CSE3210 parties

File size: 15.4 KB
Line 
1import logging
2import time
3import numpy as np
4from typing import cast
5
6from geniusweb.actions.Accept import Accept
7from geniusweb.actions.Action import Action
8from geniusweb.actions.Offer import Offer
9from geniusweb.actions.PartyId import PartyId
10from geniusweb.bidspace.AllBidsList import AllBidsList
11from geniusweb.inform.ActionDone import ActionDone
12from geniusweb.inform.Finished import Finished
13from geniusweb.inform.Inform import Inform
14from geniusweb.inform.Settings import Settings
15from geniusweb.inform.YourTurn import YourTurn
16from geniusweb.issuevalue.Bid import Bid
17from geniusweb.issuevalue.Domain import Domain
18from geniusweb.issuevalue.Value import Value
19from geniusweb.issuevalue.ValueSet import ValueSet
20from geniusweb.party.Capabilities import Capabilities
21from geniusweb.party.DefaultParty import DefaultParty
22from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
23from geniusweb.profileconnection.ProfileConnectionFactory import (
24 ProfileConnectionFactory,
25)
26from geniusweb.progress.ProgressRounds import ProgressRounds
27from tudelft_utilities_logging.Reporter import Reporter
28
29
30
31class Agent67(DefaultParty):
32 """
33 Template agent that offers random bids until a bid with sufficient utility is offered.
34 """
35
36 def __init__(self, reporter: Reporter = None):
37 super().__init__(reporter)
38 self.getReporter().log(logging.INFO, "party is initialized")
39 self._profile = None
40 self._last_received_bid: Bid = None
41 self.best_offer_opponent: Bid = None
42 self.best_bid: Bid = None
43 self.calculated_bid: bool = False
44 self.opponent_issues = {}
45 self.opp_history_bids = ({})
46 self.sorted_bid = []
47
48 self.walk_down_counter = 0
49 self.curr_walk_down_bid = None
50 self.whether_walk_down = True
51
52 self.average_util = 0
53 self.issues = []
54 self.bid_history = {}
55 self.opp_profile = {}
56
57 # Issues to numeric
58 self.issue_to_numeric = {}
59 self.idx_issue = 1
60
61 # Values to numeric
62 self.value_to_numeric = {}
63 self.idx_value = 1
64
65 def notifyChange(self, info: Inform):
66 """This is the entry point of all interaction with your agent after is has been initialised.
67
68 Args:
69 info (Inform): Contains either a request for action or information.
70 """
71
72 # a Settings message is the first message that will be send to your
73 # agent containing all the information about the negotiation session.
74 if isinstance(info, Settings):
75 self._settings: Settings = cast(Settings, info)
76 self._me = self._settings.getID()
77
78 # progress towards the deadline has to be tracked manually through the use of the Progress object
79 self._progress = self._settings.getProgress()
80
81 # the profile contains the preferences of the agent over the domain
82 self._profile = ProfileConnectionFactory.create(
83 info.getProfile().getURI(), self.getReporter()
84 )
85 # ActionDone is an action send by an opponent (an offer or an accept)
86 elif isinstance(info, ActionDone):
87 action: Action = cast(ActionDone, info).getAction()
88
89 # if it is an offer, set the last received bid
90 if isinstance(action, Offer):
91 self._last_received_bid = cast(Offer, action).getBid()
92 # YourTurn notifies you that it is your turn to act
93 elif isinstance(info, YourTurn):
94 action = self._myTurn()
95 if isinstance(self._progress, ProgressRounds):
96 self._progress = self._progress.advance()
97 self.getConnection().send(action)
98
99 # Finished will be send if the negotiation has ended (through agreement or deadline)
100 elif isinstance(info, Finished):
101 # terminate the agent MUST BE CALLED
102 self.terminate()
103 else:
104 self.getReporter().log(
105 logging.WARNING, "Ignoring unknown info " + str(info)
106 )
107
108 # lets the geniusweb system know what settings this agent can handle
109 # leave it as it is for this competition
110 def getCapabilities(self) -> Capabilities:
111 return Capabilities(
112 set(["SAOP"]),
113 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
114 )
115
116 # terminates the agent and its connections
117 # leave it as it is for this competition
118 def terminate(self):
119 self.getReporter().log(logging.INFO, "party is terminating:")
120 super().terminate()
121 if self._profile is not None:
122 self._profile.close()
123 self._profile = None
124
125
126
127 """
128 AgentBernie : Using frequency analysis model with walk-down strategy and boulware-style concession.
129 """
130
131 def getDescription(self) -> str:
132 return "AgentBernie : Using frequency analysis model with walk-down strategy and boulware-style concession."
133
134 # execute a turn
135 def _myTurn(self):
136
137 # Sort the highest bid in decreasing order
138 if len(self.sorted_bid) <= 0:
139 self.sort_high_bids()
140 bid = self.sorted_bid[0]
141 action = Offer(self._me, bid)
142 return action
143
144 else:
145 # Update the opponent profile with the new bid
146 self.update_bid_history(self._last_received_bid)
147 self.analyse_opp_profile()
148
149 # check if the last received offer if the opponent is good enough
150 if self._isGood(self._last_received_bid):
151 # if so, accept the offer
152 action = Accept(self._me, self._last_received_bid)
153 else:
154 # if not, find a bid to propose as counter offer
155 bid = self._findBid()
156 action = Offer(self._me, bid)
157
158 # send the action
159 return action
160
161 #####################################################################################
162 ############################## ACCEPTANCE STRATEGY ##################################
163 #####################################################################################
164
165 def _isGood(self, bid: Bid) -> bool:
166 """
167 Checking whether the opponent's offered bid is good or bad.
168 """
169 if bid is None:
170 return False
171 profile = self._profile.getProfile()
172 progress = self._progress.get(time.time() * 1000)
173
174 # If 90% of the rounds towards the deadline have passed
175 # and no progress made. Then switching to the
176 # concession strategy
177 if progress > 0.90 and profile.getUtility(bid) > 0.4:
178 return True
179
180 # 75% of the rounds towards the deadline have passed
181 # Using acceptance strategy here called : AC_NEXT
182 return progress > 0.75 \
183 and profile.getUtility(bid) > profile.getUtility(self._findBid()) \
184 and self.batna(bid)
185
186 #####################################################################################
187 ############################## BIDDING STRATEGY #####################################
188 #####################################################################################
189
190 def _findBid(self) -> Bid:
191 """
192 Finds the best offer for us and the opponent to
193 reach desirable agreeement and results.
194 """
195 profile = self._profile.getProfile()
196
197 # Walk-down strategy, stop until offered utility value
198 # is below lambda parameter
199 if(self.whether_walk_down):
200 walk_down_bid = self.walk_down_strategy()
201 util_bid = profile.getUtility(walk_down_bid)
202
203 lambda_value = 0.80
204 if util_bid > lambda_value:
205 return walk_down_bid
206 else:
207 self.whether_walk_down = False
208 return self.find_best_offer()
209
210 return self.find_best_offer()
211
212 def walk_down_strategy(self):
213 """
214 Walk-down strategy : Starting from the highest until
215 the utility is below lambda parameter.
216 """
217 walk_down_bid = self.sorted_bid[self.walk_down_counter]
218 self.curr_walk_down_bid = walk_down_bid
219 self.walk_down_counter += + 1
220
221 return walk_down_bid
222
223 def batna(self, bid):
224 """
225 Checking whether the bid's utility is
226 bove batna utility value.
227 """
228 profile = self._profile.getProfile()
229 reservation_bid = profile.getReservationBid()
230
231 if reservation_bid is None:
232 return True
233 else:
234 return profile.getUtility(bid) > profile.getUtility(reservation_bid)
235
236 def find_best_offer(self) -> Bid:
237 """
238 Finding
239 """
240 for bid in self.sorted_bid:
241 found = True
242
243 for issue, value in bid.getIssueValues().items():
244 num_issue = self.issue_to_numeric[issue]
245 if self.accept_range(issue, value) \
246 and self.batna(bid) \
247 and self.opp_profile[num_issue][1] != -1:
248 continue
249 else:
250 found = False
251 break
252 if(found):
253 return bid
254 curr_walk_down_bid = self.walk_down_strategy()
255 return curr_walk_down_bid
256
257 #####################################################################################
258 ############################## OPPONENT MODELLING ###################################
259 #####################################################################################
260
261 def update_bid_history(self, bid):
262 """
263 Adding new bid/offer to the history, if issue is not recorded in the
264 "issue_to_numeric". Assign a numeric representation of the issue and save it in the
265 dict.
266
267 Same for values, if there are missing numerical representation for them. Create one in
268 "value_to_numeric" dict.
269 """
270 bid_dict = bid.getIssueValues().items()
271 for issue, value in bid_dict:
272
273 # If issue doesn't exist in the categorical-numerical mapping
274 # in dictionary "map_issues_to_numeric_and_initialize" then
275 # initialize. Same for values
276 if issue not in self.issue_to_numeric:
277 self.map_issues_to_numeric_and_initialize(issue)
278 if value not in self.value_to_numeric:
279 self.map_value_to_numeric(value)
280
281 idx_numeric_issue = self.issue_to_numeric[issue]
282 self.bid_history[idx_numeric_issue].append(value)
283
284 def analyse_opp_profile(self):
285 """
286 Calculate the mode and variance of values per issue to
287 know the opponent's profile
288 """
289 for issue, values in self.bid_history.items():
290 if issue not in self.opp_profile:
291 self.opp_profile[issue] = ()
292
293 # Mapping from categorival to numerical with values from the issue
294 numerical_values = [self.value_to_numeric[value]
295 for value in values]
296
297 if(len(numerical_values) == 1):
298 self.opp_profile[issue] = (
299 max(numerical_values, key=numerical_values.count), -1)
300 else:
301 self.opp_profile[issue] = (
302 max(numerical_values, key=numerical_values.count), np.var(numerical_values, ddof=1))
303
304 def accept_range(self, issue, value):
305 """
306 Accepts when given that the value of the partucular issue is in the
307 calculated acceptable range.
308 """
309 issue_range = self.calculate_acceptable_range(issue)
310 if value not in self.value_to_numeric:
311 self.map_value_to_numeric(value)
312
313 low = issue_range[0]
314 high = issue_range[1]
315
316 return low < self.value_to_numeric[value] < high
317
318 def calculate_acceptable_range(self, issue):
319 """
320 Calculate ranges by taking a mode and variance into account.
321 """
322 numerical_issue = self.issue_to_numeric[issue]
323 issue_mode = self.opp_profile[numerical_issue][0]
324 issue_var = self.opp_profile[numerical_issue][1]
325
326 # Calculating the ranges
327 low = issue_mode - issue_var
328 high = issue_mode + issue_var
329
330 return low, high
331
332 #####################################################################################
333 ############################## HELPER FUNCTIONS #####################################
334 #####################################################################################
335
336 def update_opponent_issues(self):
337 """
338 Keep track of frequencies of the values in bids received by
339 the opponents over period of time.
340 """
341
342 recentIssues = self._last_received_bid.getIssues()
343 recentIssuesValues = self._last_received_bid.getIssueValues()
344
345 for issue in recentIssues:
346 if issue in self.opponent_issues:
347 if recentIssuesValues[issue] in self.opponent_issues[issue]:
348 self.opponent_issues[issue] = self.opponent_issues[issue][recentIssuesValues[issue]] + 1
349 else:
350 self.opponent_issues[issue][recentIssuesValues[issue]] = 1
351 else:
352 self.opponent_issues[issue][recentIssuesValues[issue]] = 1
353
354 def update_history_opp_issues(self):
355 """
356 Updates history of opponent's issues
357 """
358 recentIssuesValues = self._last_received_bid.getIssueValues()
359 self.opp_history_bids.append(recentIssuesValues)
360
361 def always_best_bid_init(self) -> Bid:
362 """
363 Returns the best bid
364 """
365 if(not self.calculated_bid):
366 domain = self._profile.getProfile().getDomain()
367 all_bids = AllBidsList(domain)
368 profile = self._profile.getProfile()
369
370 best_utility = 0.0
371
372 for x in all_bids:
373 curr_utility = profile.getUtility(x)
374 if(best_utility < curr_utility):
375 bid = x
376 best_utility = curr_utility
377
378 self.calculated_bid = True
379 self.best_bid = bid
380
381 return self.best_bid
382 else:
383 return self.best_bid
384
385 def sort_high_bids(self):
386 """
387 Sorting bids based on the utility values
388 """
389 temp_tuple_bid = []
390 if(not self.calculated_bid):
391 domain = self._profile.getProfile().getDomain()
392 all_bids = AllBidsList(domain)
393 profile = self._profile.getProfile()
394
395 for x in all_bids:
396 temp_tuple_bid.append((profile.getUtility(x), x))
397
398 temp_tuple_bid = sorted(
399 temp_tuple_bid, key=lambda x: x[0], reverse=True)
400
401 self.calculated_bid = True
402 self.sorted_bid = [bid[1] for bid in temp_tuple_bid]
403
404 def map_issues_to_numeric_and_initialize(self, issue):
405 """
406 Map issues which are represented in String to numeric values,
407 furthermore it initializes issue-history pair in "bid_history" dict.
408 """
409 self.issue_to_numeric[issue] = self.idx_issue
410 self.bid_history[self.idx_issue] = []
411 self.idx_issue = self.idx_issue + 1
412
413 def map_value_to_numeric(self, value):
414 """
415 Map values which are represented in String to numeric values.
416 """
417 self.value_to_numeric[value] = self.idx_value
418 self.idx_value = self.idx_value + 1
419
420 def calculate_avg_util(self):
421 profile = self._profile.getProfile()
422 util_avg = 0
423
424 for bid in self.sorted_bid:
425 util_avg += profile.getUtility(bid)
426
427 return util_avg / len(self.sorted_bid)
Note: See TracBrowser for help on using the repository browser.