source: ANL2022/LuckyAgent2022/LuckyAgent2022.py

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

#6 added ANAC2022 parties

File size: 21.6 KB
Line 
1#######################################################
2# author: Arash Ebrahimnezhad
3# Email: Arash.ebrah@gmail.com
4#######################################################
5import logging
6from random import randint
7import random
8from time import time
9from tkinter.messagebox import NO
10from typing import cast
11import math
12import pickle
13import os
14from statistics import mean
15from geniusweb.actions.Accept import Accept
16from geniusweb.actions.Action import Action
17from geniusweb.actions.Offer import Offer
18from geniusweb.actions.PartyId import PartyId
19from geniusweb.bidspace.AllBidsList import AllBidsList
20from geniusweb.inform.ActionDone import ActionDone
21from geniusweb.inform.Finished import Finished
22from geniusweb.inform.Inform import Inform
23from geniusweb.inform.Settings import Settings
24from geniusweb.inform.YourTurn import YourTurn
25from geniusweb.issuevalue.Bid import Bid
26from geniusweb.issuevalue.Domain import Domain
27from geniusweb.party.Capabilities import Capabilities
28from geniusweb.party.DefaultParty import DefaultParty
29from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import (
30 LinearAdditiveUtilitySpace,
31)
32from geniusweb.profileconnection.ProfileConnectionFactory import (
33 ProfileConnectionFactory,
34)
35from geniusweb.progress.ProgressTime import ProgressTime
36from geniusweb.references.Parameters import Parameters
37from tudelft_utilities_logging.ReportToLogger import ReportToLogger
38from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
39from .utils.opponent_model import OpponentModel
40from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
41from agents.time_dependent_agent.extended_util_space import ExtendedUtilSpace
42from decimal import Decimal
43from geniusweb.opponentmodel import FrequencyOpponentModel
44
45
46NUMBER_OF_GOALS = 5
47
48
49class LuckyAgent2022(DefaultParty):
50 """
51 Template of a Python geniusweb agent.
52 """
53
54 def __init__(self):
55 super().__init__()
56 self.logger: ReportToLogger = self.getReporter()
57
58 self.domain: Domain = None
59 self.parameters: Parameters = None
60 self.profile: LinearAdditiveUtilitySpace = None
61 self.progress: ProgressTime = None
62 self.me: PartyId = None
63 self.other: str = None
64 self.settings: Settings = None
65 self.storage_dir: str = None
66
67 self.last_received_bid: Bid = None
68
69 self.received_bid_details = []
70 self.my_bid_details = []
71 self.best_received_bid = None
72
73 self.logger.log(logging.INFO, "party is initialized")
74 self.alpha = 1.0
75 self.betta = 0.0
76 # self.pattern = randint(0, PATTERN_SIZE)
77 self.agreement_utility = 0.0
78 self._utilspace: LinearAdditive = None # type:ignore
79 self.who_accepted = None
80
81 self.is_called = False
82
83 # ************* Parameters *************
84 self.max = 1.0
85 self.min = 0.6
86 self.e = 0.05
87 self.increasing_e = 0.025
88 self.decreasing_e = 0.025
89 self.epsilon = 1.0
90 self.good_agreement_u = 0.95
91 self.condition_d = 0
92
93 def ff(self, ll, n):
94 x_list = []
95 for x in ll[::-1]:
96 if x[1] == n:
97 x_list.append(x[0])
98 else:
99 break
100 if len(x_list) > 0:
101 m = mean(x_list)
102 else:
103 m = 0.8
104 return m
105
106 def set_parameters(self, opp):
107 if not os.path.exists(f"{self.storage_dir}/m_data"):
108 self.min = 0.6
109 self.e = 0.05
110 else:
111 rand_num = random.random()
112 saved_data = self.return_saved_data('m_data')
113 condition_data = self.return_saved_data('c_data')
114 if opp in saved_data:
115 self.good_agreement_u = self.good_agreement_u - \
116 (len(saved_data[opp]) * 0.01)
117 if self.good_agreement_u < 0.7:
118 self.good_agreement_u = 0.7
119 if len(saved_data[opp]) >= 2:
120 if (saved_data[opp][-2][0] == 0 and saved_data[opp][-1][0] > 0) or ((saved_data[opp][-2][1] == saved_data[opp][-1][1]) and (saved_data[opp][-2][2] == saved_data[opp][-1][2])):
121 self.condition_d = condition_data[opp] + \
122 saved_data[opp][-1][0]
123 if 0 <= self.condition_d < 1:
124 self.condition_d = 1
125 self.epsilon = self.epsilon / self.condition_d
126 if rand_num > self.epsilon:
127 self.min = saved_data[opp][-1][1]
128 self.e = saved_data[opp][-1][2]
129 else:
130 if saved_data[opp][-1][0] > 0 and saved_data[opp][-1][0] < self.good_agreement_u:
131 self.min = saved_data[opp][-1][1] + \
132 self.increasing_e
133 if self.min > 0.7:
134 self.min = 0.7
135 self.e = saved_data[opp][-1][2] - \
136 self.increasing_e
137 if self.e < 0.005:
138 self.e = 0.005
139 if saved_data[opp][-1][0] == 0:
140 self.condition_d = condition_data[opp] - (
141 1-self.ff(saved_data[opp], saved_data[opp][-1][1]))
142 if self.condition_d < 0:
143 self.condition_d = 0
144 self.min = saved_data[opp][-1][1] - \
145 self.decreasing_e
146 if self.min < 0.5:
147 self.min = 0.5
148 self.e = saved_data[opp][-1][2] + \
149 self.decreasing_e
150 if self.e > 0.1:
151 self.e = 0.1
152 if saved_data[opp][-1][0] >= self.good_agreement_u:
153 self.min = saved_data[opp][-1][1]
154 self.e = saved_data[opp][-1][2]
155 else:
156 if saved_data[opp][-1][0] > 0 and saved_data[opp][-1][0] < self.good_agreement_u:
157 self.min = saved_data[opp][-1][1] + \
158 self.increasing_e
159 if self.min > 0.7:
160 self.min = 0.7
161 self.e = saved_data[opp][-1][2] - self.increasing_e
162 if self.e < 0.005:
163 self.e = 0.005
164 if saved_data[opp][-1][0] == 0:
165 self.condition_d = condition_data[opp] - (
166 1-self.ff(saved_data[opp], saved_data[opp][-1][1]))
167 if self.condition_d < 0:
168 self.condition_d = 0
169 self.min = saved_data[opp][-1][1] - \
170 self.decreasing_e
171 if self.min < 0.5:
172 self.min = 0.5
173 self.e = saved_data[opp][-1][2] + self.decreasing_e
174 if self.e > 0.1:
175 self.e = 0.1
176 if saved_data[opp][-1][0] >= self.good_agreement_u:
177 self.min = saved_data[opp][-1][1]
178 self.e = saved_data[opp][-1][2]
179 else:
180 if saved_data[opp][-1][0] > 0 and saved_data[opp][-1][0] < self.good_agreement_u:
181 self.min = saved_data[opp][-1][1] + self.increasing_e
182 if self.min > 0.7:
183 self.min = 0.7
184 self.e = saved_data[opp][-1][2] - self.increasing_e
185 if self.e < 0.005:
186 self.e = 0.005
187 if saved_data[opp][-1][0] == 0:
188 self.condition_d = condition_data[opp] - (
189 1-self.ff(saved_data[opp], saved_data[opp][-1][1]))
190 if self.condition_d < 0:
191 self.condition_d = 0
192 self.min = saved_data[opp][-1][1] - self.decreasing_e
193 if self.min < 0.5:
194 self.min = 0.5
195 self.e = saved_data[opp][-1][2] + self.decreasing_e
196 if self.e > 0.1:
197 self.e = 0.1
198 if saved_data[opp][-1][0] >= self.good_agreement_u:
199 self.min = saved_data[opp][-1][1]
200 self.e = saved_data[opp][-1][2]
201 else:
202 self.min = 0.6
203 self.e = 0.05
204
205 def return_saved_data(self, file_name):
206 # for reading also binary mode is important
207 file = open(f"{self.storage_dir}/{file_name}", 'rb')
208 saved_data = pickle.load(file)
209 file.close()
210 return saved_data
211
212 def notifyChange(self, data: Inform):
213 """MUST BE IMPLEMENTED
214 This is the entry point of all interaction with your agent after is has been initialised.
215 How to handle the received data is based on its class type.
216 Args:
217 info (Inform): Contains either a request for action or information.
218 """
219
220 # a Settings message is the first message that will be send to your
221 # agent containing all the information about the negotiation session.
222 if isinstance(data, Settings):
223 self.settings = cast(Settings, data)
224 self.me = self.settings.getID()
225
226 # progress towards the deadline has to be tracked manually through the use of the Progress object
227 self.progress = self.settings.getProgress()
228
229 self.parameters = self.settings.getParameters()
230 self.storage_dir = self.parameters.get("storage_dir")
231
232 # the profile contains the preferences of the agent over the domain
233 profile_connection = ProfileConnectionFactory.create(
234 data.getProfile().getURI(), self.getReporter()
235 )
236 self.profile = profile_connection.getProfile()
237 self.domain = self.profile.getDomain()
238
239 # initialize FrequencyOpponentModel
240 self.opponent_model = FrequencyOpponentModel.FrequencyOpponentModel.create().With(
241 newDomain=self.profile.getDomain(),
242 newResBid=self.profile.getReservationBid())
243
244 profile_connection.close()
245
246 # ActionDone informs you of an action (an offer or an accept)
247 # that is performed by one of the agents (including yourself).
248 elif isinstance(data, ActionDone):
249 action = cast(ActionDone, data).getAction()
250
251 actor = action.getActor()
252
253 if isinstance(action, Accept):
254 # print(str(actor).rsplit("_", 1)[0], '=>', cast(Offer, action).getBid())
255 agreement_bid = cast(Offer, action).getBid()
256 self.agreement_utility = float(
257 self.profile.getUtility(agreement_bid))
258 self.who_accepted = str(actor).rsplit("_", 1)[0]
259
260 # ignore action if it is our action
261 if actor != self.me:
262 # obtain the name of the opponent, cutting of the position ID.
263 self.other = str(actor).rsplit("_", 1)[0]
264
265 # set parameters according of saved data
266 if not self.is_called:
267 self.set_parameters(self.other)
268 self.is_called = True
269
270 # process action done by opponent
271 self.opponent_action(action)
272 # YourTurn notifies you that it is your turn to act
273 elif isinstance(data, YourTurn):
274 # execute a turn
275 self.my_turn()
276
277 # Finished will be send if the negotiation has ended (through agreement or deadline)
278 elif isinstance(data, Finished):
279 self.save_data()
280 # terminate the agent MUST BE CALLED
281 self.logger.log(logging.INFO, "party is terminating:")
282 super().terminate()
283 else:
284 self.logger.log(logging.WARNING,
285 "Ignoring unknown info " + str(data))
286
287 def getCapabilities(self) -> Capabilities:
288 """MUST BE IMPLEMENTED
289 Method to indicate to the protocol what the capabilities of this agent are.
290 Leave it as is for the ANL 2022 competition
291 Returns:
292 Capabilities: Capabilities representation class
293 """
294 return Capabilities(
295 set(["SAOP"]),
296 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
297 )
298
299 def send_action(self, action: Action):
300 """Sends an action to the opponent(s)
301 Args:
302 action (Action): action of this agent
303 """
304 self.getConnection().send(action)
305
306 # give a description of your agent
307 def getDescription(self) -> str:
308 """MUST BE IMPLEMENTED
309 Returns a description of your agent. 1 or 2 sentences.
310 Returns:
311 str: Agent description
312 """
313 return "LuckyAgent2022"
314
315 def opponent_action(self, action):
316 """Process an action that was received from the opponent.
317 Args:
318 action (Action): action of opponent
319 """
320 # if it is an offer, set the last received bid
321 if isinstance(action, Offer):
322
323 bid = cast(Offer, action).getBid()
324
325 # update opponent model with bid
326 self.opponent_model = self.opponent_model.WithAction(
327 action=action, progress=self.progress)
328 # set bid as last received
329 self.last_received_bid = bid
330 # self.received_bids.append(bid)
331 self.received_bid_details.append(BidDetail(
332 bid, float(self.profile.getUtility(bid))))
333
334 def my_turn(self):
335 """This method is called when it is our turn. It should decide upon an action
336 to perform and send this action to the opponent.
337 """
338 self.cal_thresholds()
339 self._updateUtilSpace()
340
341 next_bid = self.find_bid()
342 # check if the last received offer is good enough
343 if self.accept_condition(self.last_received_bid, next_bid):
344 # if so, accept the offer
345 action = Accept(self.me, self.last_received_bid)
346 else:
347 # if not, find a bid to propose as counter offer
348 action = Offer(self.me, next_bid)
349 # self.my_bids.append(next_bid)
350 self.my_bid_details.append(
351 BidDetail(next_bid, float(self.profile.getUtility(next_bid))))
352
353 # send the action
354 self.send_action(action)
355
356 def _updateUtilSpace(self) -> LinearAdditive: # throws IOException
357 newutilspace = self.profile
358 if not newutilspace == self._utilspace:
359 self._utilspace = cast(LinearAdditive, newutilspace)
360 self._extendedspace = ExtendedUtilSpace(self._utilspace)
361 return self._utilspace
362
363 def save_data(self):
364 """This method is called after the negotiation is finished. It can be used to store data
365 for learning capabilities. Note that no extensive calculations can be done within this method.
366 Taking too much time might result in your agent being killed, so use it for storage only.
367 """
368 # **************************************************
369
370 c_data = {}
371 if os.path.isfile(f"{self.storage_dir}/c_data"):
372 # OLD
373 # dbfile_c = open(f"{self.storage_dir}/c_data", 'rb')
374 # c_data = pickle.load(dbfile_c)
375 # dbfile_c.close()
376 # NEW
377 with open(f"{self.storage_dir}/c_data", 'rb') as dbfile_c:
378 c_data = pickle.load(dbfile_c)
379
380 if os.path.exists(f"{self.storage_dir}/c_data"):
381 os.remove(f"{self.storage_dir}/c_data")
382
383 c_data[self.other] = self.condition_d
384 # OLD
385 # dbfile_c = open(f"{self.storage_dir}/c_data", 'ab')
386 # pickle.dump(c_data, dbfile_c)
387 # dbfile_c.close()
388 # NEW
389 with open(f"{self.storage_dir}/c_data", 'ab') as dbfile_c:
390 pickle.dump(c_data, dbfile_c)
391
392 m_data = {}
393 if os.path.isfile(f"{self.storage_dir}/m_data"):
394 # OLD
395 # dbfile = open(f"{self.storage_dir}/m_data", 'rb')
396 # m_data = pickle.load(dbfile)
397 # dbfile.close()
398 # NEW
399 with open(f"{self.storage_dir}/m_data", 'rb') as dbfile:
400 m_data = pickle.load(dbfile)
401
402
403 if os.path.exists(f"{self.storage_dir}/m_data"):
404 os.remove(f"{self.storage_dir}/m_data")
405
406 m_tuple = (self.agreement_utility, self.min, self.e)
407 if self.other in m_data:
408 m_data[self.other].append(m_tuple)
409 else:
410 m_data[self.other] = [m_tuple, ]
411
412 # OLD
413 # dbfile = open(f"{self.storage_dir}/m_data", 'ab')
414 # # source, destination
415 # pickle.dump(m_data, dbfile)
416 # dbfile.close()
417 # NEW
418 with open(f"{self.storage_dir}/m_data", 'ab') as dbfile:
419 pickle.dump(m_data, dbfile)
420
421 ###########################################################################################
422 ################################## Example methods below ##################################
423 ###########################################################################################
424
425 def accept_condition(self, received_bid: Bid, next_bid) -> bool:
426 if received_bid is None:
427 return False
428
429 progress = self.progress.get(time() * 1000)
430
431 # set reservation value
432 if self.profile.getReservationBid() is None:
433 reservation = 0.0
434 else:
435 reservation = self.profile.getUtility(
436 self.profile.getReservationBid())
437
438 received_bid_utility = self.profile.getUtility(received_bid)
439 condition1 = received_bid_utility >= self.threshold_acceptance and received_bid_utility >= reservation
440 condition2 = progress > 0.97 and received_bid_utility > self.min and received_bid_utility >= reservation
441 condition3 = self.alpha*float(received_bid_utility) + self.betta >= float(
442 self.profile.getUtility(next_bid)) and received_bid_utility >= reservation
443
444 return condition1 or condition2 or condition3
445
446 def find_bid(self) -> Bid:
447 """
448 @return next possible bid with current target utility, or null if no such
449 bid.
450 """
451 interval = self.threshold_high - self.threshold_low
452 s = interval / NUMBER_OF_GOALS
453
454 utility_goals = []
455 for i in range(NUMBER_OF_GOALS):
456 utility_goals.append(self.threshold_low+s*i)
457 utility_goals.append(self.threshold_high)
458
459 options: ImmutableList[Bid] = self._extendedspace.getBids(
460 Decimal(random.choice(utility_goals)))
461
462 opponent_utilities = []
463 for option in options:
464 if self.opponent_model != None:
465 opp_utility = float(
466 self.opponent_model.getUtility(option))
467 if opp_utility > 0:
468 opponent_utilities.append(opp_utility)
469 else:
470 opponent_utilities.append(0.00001)
471 else:
472 opponent_utilities.append(0.00001)
473
474 if options.size() == 0:
475 # if we can't find good bid, get max util bid....
476 options = self._extendedspace.getBids(self._extendedspace.getMax())
477 return options.get(randint(0, options.size() - 1))
478 # pick a random one.
479
480 next_bid = random.choices(list(options), weights=opponent_utilities)[0]
481 for bid_detaile in self.received_bid_details:
482 if bid_detaile.getUtility() >= self.profile.getUtility(next_bid):
483 next_bid = bid_detaile.getBid()
484
485 return random.choices(list(options), weights=opponent_utilities)[0]
486
487 # ************************************************************
488 def f(self, t, k, e):
489 return k + (1-k)*(t**(1/e))
490
491 def p(self, min1, max1, e, t):
492 return min1 + (1-self.f(t, 0, e))*(max1-min1)
493
494 def cal_thresholds(self):
495 progress = self.progress.get(time() * 1000)
496 self.threshold_high = self.p(self.min+0.1, self.max, self.e, progress)
497 self.threshold_acceptance = self.p(
498 self.min+0.1, self.max, self.e, progress) - (0.1*((progress+0.0000001)))
499 self.threshold_low = self.p(self.min+0.1, self.max, self.e, progress) - \
500 (0.1*((progress+0.0000001))) * abs(math.sin(progress * 60))
501
502 # ================================================================
503 def get_domain_size(self, domain: Domain):
504 domain_size = 1
505 for issue in domain.getIssues():
506 domain_size *= domain.getValues(issue).size()
507 return domain_size
508 # ================================================================
509
510
511class BidDetail:
512 def __init__(self, bid: Bid, utility: float):
513 self.__bid = bid
514 self.__utiltiy = utility
515
516 def getBid(self):
517 return self.__bid
518
519 def getUtility(self):
520 return self.__utiltiy
521
522 def __repr__(self) -> str:
523 return f'{self.__bid}: {self.__utiltiy}'
Note: See TracBrowser for help on using the repository browser.