source: ANL2022/LuckyAgent2022_resubmit/LuckyAgent2022.py

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

#6 added ANAC2022 parties

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