source: ANL2022/thirdagent/third_agent.py

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

#6 added ANAC2022 parties

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