source: ANL2022/AgentFO2/AgentFO2.py

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

#6 added ANAC2022 parties

File size: 15.1 KB
Line 
1import csv
2import logging
3import os
4from random import randint
5from time import time
6from typing import cast
7
8from geniusweb.actions.Accept import Accept
9from geniusweb.actions.Action import Action
10from geniusweb.actions.Offer import Offer
11from geniusweb.actions.PartyId import PartyId
12from geniusweb.bidspace.BidsWithUtility import BidsWithUtility
13from geniusweb.bidspace.Interval import Interval
14from geniusweb.inform.ActionDone import ActionDone
15from geniusweb.inform.Finished import Finished
16from geniusweb.inform.Inform import Inform
17from geniusweb.inform.Settings import Settings
18from geniusweb.inform.YourTurn import YourTurn
19from geniusweb.issuevalue.Bid import Bid
20from geniusweb.issuevalue.Domain import Domain
21from geniusweb.party.Capabilities import Capabilities
22from geniusweb.party.DefaultParty import DefaultParty
23from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import (
24 LinearAdditiveUtilitySpace,
25)
26from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
27from geniusweb.profileconnection.ProfileConnectionFactory import (
28 ProfileConnectionFactory,
29)
30from geniusweb.progress.ProgressTime import ProgressTime
31from geniusweb.references.Parameters import Parameters
32from tudelft_utilities_logging.ReportToLogger import ReportToLogger
33from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
34from decimal import Decimal
35
36
37
38class AgentFO2(DefaultParty):
39 def __init__(self):
40 super().__init__()
41 self.logger: ReportToLogger = self.getReporter()
42
43 self.domain: Domain = None
44 self.parameters: Parameters = None
45 self.profile: LinearAdditiveUtilitySpace = None
46 self.progress: ProgressTime = None
47 self.me: PartyId = None
48 self.other: str = None
49 self.settings: Settings = None
50 self.storage_dir: str = None
51 self.allbid:BidsWithUtility = None
52
53 self.pre_opponent_bid_hamming=None
54 self.pre_opponent_utility_log=None
55 self.which_pre_accept=None
56 self.pre_strategy=None
57
58 self.last_received_bid: Bid = None
59 self.logger.log(logging.INFO, "party is initialized")
60
61 def notifyChange(self, data: Inform):
62 """MUST BE IMPLEMENTED
63 This is the entry point of all interaction with your agent after is has been initialised.
64 How to handle the received data is based on its class type.
65
66 Args:
67 info (Inform): Contains either a request for action or information.
68 """
69 # a Settings message is the first message that will be send to your
70 # agent containing all the information about the negotiation session.
71 if isinstance(data, Settings):
72 self.opponent_utility_log=[]
73 self.opponent_bid_hamming=[]
74 self.opponent_strategy=-1
75 self.which_accept=[-1,0,0]
76 self.read_data=True
77 self.strategy_set_FLAG=True
78 self.min=0.5
79 self.accept_utilgoal=0.8
80 self.random_max=1.0
81 self.not_accept=0.05
82 self.settings = cast(Settings, data)
83 self.me = self.settings.getID()
84
85 # progress towards the deadline has to be tracked manually through the use of the Progress object
86 self.progress = self.settings.getProgress()
87
88 self.parameters = self.settings.getParameters()
89 self.storage_dir = self.parameters.get("storage_dir")
90
91 # the profile contains the preferences of the agent over the domain
92 profile_connection = ProfileConnectionFactory.create(
93 data.getProfile().getURI(), self.getReporter()
94 )
95 self.profile = profile_connection.getProfile()
96 self.domain = self.profile.getDomain()
97 self.issue=self.domain.getIssuesValues()
98 self.allbid = BidsWithUtility.create(cast(LinearAdditive,self.profile))
99 profile_connection.close()
100
101 # ActionDone informs you of an action (an offer or an accept)
102 # that is performed by one of the agents (including yourself).
103 elif isinstance(data, ActionDone):
104 action = cast(ActionDone, data).getAction()
105 actor = action.getActor()
106 # ignore action if it is our action
107 if actor != self.me:
108 # obtain the name of the opponent, cutting of the position ID.
109 self.other = str(actor).split("_")[-2]
110
111 # read data
112 if self.read_data and os.path.exists(f"{self.storage_dir}/{self.other}.csv"):
113 with open(f"{self.storage_dir}/{self.other}.csv","r") as f:
114 reader=csv.reader(f)
115 l=[row for row in reader]
116 l=[[float(v) for v in row] for row in l]
117 self.pre_opponent_utility_log=l[0]
118 self.pre_opponent_bid_hamming=l[1]
119 self.which_pre_accept=l[2]
120 self.pre_strategy=l[3]
121 self.accept_utilgoal=max(0.8,self.which_pre_accept[1])
122 self.opponent_strategy_search()
123 self.read_data=False
124
125 # process action done by opponent
126 self.opponent_action(action)
127 # YourTurn notifies you that it is your turn to act
128 elif isinstance(data, YourTurn):
129 # execute a turn
130 self.my_turn()
131
132 # Finished will be send if the negotiation has ended (through agreement or deadline)
133 elif isinstance(data, Finished):
134 self.save_data()
135 # terminate the agent MUST BE CALLED
136 self.logger.log(logging.INFO, "party is terminating:")
137 super().terminate()
138 else:
139 self.logger.log(logging.WARNING, "Ignoring unknown info " + str(data))
140
141 def opponent_strategy_search(self):
142 if len(self.pre_opponent_bid_hamming)>=20:
143 x=2
144 while not self.pre_opponent_bid_hamming.count(x):
145 x+=1
146 if x>8:
147 x=1
148 break
149 if x>=2:
150 ind=self.pre_opponent_bid_hamming.index(x)
151 else:
152 ind=-1
153
154 if ind>=20:
155 # opponent strategy is time-dipendent
156 self.opponent_strategy=0
157 elif ind>=0:
158 # opponent strategy is random or others
159 count=0
160 x=min(20,len(self.pre_opponent_bid_hamming))
161 for i in range(x):
162 if self.pre_opponent_bid_hamming[i]>=2:
163 count+=1
164 if count>=10:
165 self.opponent_strategy=1
166 else:
167 self.opponent_strategy=2
168 else:
169 # opponent strategy is others
170 self.opponent_strategy=2
171 else:
172 self.opponent_strategy=self.pre_strategy[0]
173 if self.opponent_strategy==-1:
174 self.opponent_strategy=2
175
176 def my_strategy_setting(self):
177 self.opponent_one=self.profile.getUtility(self.last_received_bid)
178 if self.which_pre_accept:
179 if self.opponent_strategy==0: # when opponent strategy is time-dependent
180 if self.which_pre_accept[0]==0: # pre-accept is me
181 pre_acc_util=self.which_pre_accept[2]
182 sup_util=self.utility_suppose(pre_acc_util)
183 self.min=min(sup_util,pre_acc_util)
184 elif self.which_pre_accept[0]==1: # pre-accept is opponent
185 pre_acc_util=self.which_pre_accept[2]
186 sup_util=self.utility_suppose(pre_acc_util)
187 self.min=max(sup_util,pre_acc_util)
188 self.min=min(self.min,0.9)
189 else: # pre-accept is unknown or None
190 self.min=self.pre_strategy[1]-0.05
191 elif self.opponent_strategy==1: # when opponent strategy is random
192 if self.which_pre_accept[0]<=0: # pre-accept is me or None or unkown
193 self.accept_utilgoal=max(max(self.pre_opponent_utility_log),self.which_pre_accept[1])
194 self.not_accept=1/2.718
195 self.random_max=0
196 elif self.which_pre_accept[0]==1: # pre-accept is opponent
197 self.not_accept=0.1
198 self.accept_utilgoal=max(max(self.pre_opponent_utility_log),self.which_pre_accept[1])
199 elif self.opponent_strategy==2: # when opponent strategy is others
200 if self.which_accept[0]>=0: # pre-negotiation is accepted
201 self.min=min(self.pre_strategy[1]+0.05,0.8)
202 else:
203 self.min=max(self.pre_strategy[1]-0.05,0.4)
204
205 def utility_suppose(self,util): # supposed pareto front
206 return min(1.0,-float(util)+1.0+float(self.opponent_one))
207
208 def getCapabilities(self) -> Capabilities:
209 """MUST BE IMPLEMENTED
210 Method to indicate to the protocol what the capabilities of this agent are.
211 Leave it as is for the ANL 2022 competition
212
213 Returns:
214 Capabilities: Capabilities representation class
215 """
216 return Capabilities(
217 set(["SAOP"]),
218 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
219 )
220
221 def send_action(self, action: Action):
222 """Sends an action to the opponent(s)
223
224 Args:
225 action (Action): action of this agent
226 """
227 self.getConnection().send(action)
228
229 # give a description of your agent
230 def getDescription(self) -> str:
231 """MUST BE IMPLEMENTED
232 Returns a description of your agent. 1 or 2 sentences.
233
234 Returns:
235 str: Agent description
236 """
237 return "AgentFO2"
238
239 def opponent_action(self, action):
240 """Process an action that was received from the opponent.
241
242 Args:
243 action (Action): action of opponent
244 """
245 # if it is an offer, set the last received bid
246 if isinstance(action, Offer):
247
248 bid = cast(Offer, action).getBid()
249 self.opponent_utility_log.append(self.profile.getUtility(bid))
250 count=0
251 # ハミング距離
252 if len(self.opponent_bid_hamming):
253 for issue in self.issue.keys():
254 now_value=bid.getValue(issue)
255 pre_value=self.last_received_bid.getValue(issue)
256 if not now_value==pre_value:
257 count+=1
258 self.opponent_bid_hamming.append(count)
259 else:
260 self.opponent_bid_hamming.append(0)
261
262 # set bid as last received
263 self.last_received_bid = bid
264 if self.strategy_set_FLAG:
265 self.my_strategy_setting()
266 self.strategy_set_FLAG=False
267 elif isinstance(action,Accept):
268 self.which_accept[0]=1
269 bid=cast(Accept,action).getBid()
270 self.which_accept[1]=self.profile.getUtility(bid)
271 if self.strategy_set_FLAG:
272 self.which_accept[2]=self.which_accept[1]
273 else:
274 self.which_accept[2]=self.utility_suppose(self.which_accept[1])
275
276 def my_turn(self):
277 """This method is called when it is our turn. It should decide upon an action
278 to perform and send this action to the opponent.
279 """
280 bid = self._makeBid()
281 myAction: Action
282 if bid == None or (
283 self.last_received_bid != None
284 and self.accept_condition(bid)
285 ):
286 # if bid==null we failed to suggest next bid.
287 myAction = Accept(self.me, self.last_received_bid)
288 self.which_accept[0]=0
289 self.which_accept[1]=self.profile.getUtility(self.last_received_bid)
290 self.which_accept[2]=self.utility_suppose(self.which_accept[1])
291 else:
292 myAction = Offer(self.me, bid)
293 self.getConnection().send(myAction)
294
295 def save_data(self):
296 """This method is called after the negotiation is finished. It can be used to store data
297 for learning capabilities. Note that no extensive calculations can be done within this method.
298 Taking too much time might result in your agent being killed, so use it for storage only.
299 """
300
301 with open(f"{self.storage_dir}/{self.other}.csv", "w") as f:
302 writer=csv.writer(f)
303 writer.writerow(self.opponent_utility_log)
304 writer.writerow(self.opponent_bid_hamming)
305 writer.writerow(self.which_accept)
306 writer.writerow([self.opponent_strategy,self.min])
307
308
309 def accept_condition(self, bid: Bid) -> bool:
310 if bid is None:
311 return False
312
313 # progress of the negotiation session between 0 and 1 (1 is deadline)
314 progress = self.progress.get(time() * 1000)
315
316 util=self.profile.getUtility(self.last_received_bid)
317
318 conditions = [
319 Decimal(1.02)*util+Decimal(0.04)>self.profile.getUtility(bid),
320 util>self.accept_utilgoal and self.not_accept<=progress,
321 self.random_max<util and self.not_accept<=progress,
322 progress>0.95 and util>=self.utility_suppose(util),
323 util>0.85 and self.utility_suppose(util)<=util
324 ]
325
326 if self.not_accept>progress:
327 if self.random_max<util:
328 self.random_max=util
329
330 return any(conditions)
331
332
333 def _makeBid(self) -> Bid:
334 """
335 @return next possible bid with current target utility, or null if no such
336 bid.
337 """
338 progress = self.progress.get(time() * 1000)
339
340 utilityGoal = self._getUtilityGoal(
341 progress,
342 self.getE(),
343 self.getMin(),
344 self.getMax(),
345 )
346
347 options: ImmutableList[Bid] = self.allbid.getBids(Interval(utilityGoal-Decimal(0.05),min(self.getMax(),utilityGoal+Decimal(0.05))))
348 if options.size() == 0:
349 # if we can't find good bid
350 options = self.allbid.getBids(Interval(utilityGoal,self.getMax()))
351 # pick a random one.
352 return options.get(randint(0, options.size() - 1))
353
354 def getE(self) -> float:
355 return 0.4
356
357 def getMin(self) -> Decimal:
358 return Decimal(self.min)
359
360 def getMax(self) -> Decimal:
361 return self.allbid.getRange().getMax()
362
363 def _getUtilityGoal(
364 self, t: float, e: float, minUtil: Decimal, maxUtil: Decimal
365 ) -> Decimal:
366 """
367 @param t the time in [0,1] where 0 means start of nego and 1 the
368 end of nego (absolute time/round limit)
369 @param e the e value that determinses how fast the party makes
370 concessions with time. Typically around 1. 0 means no
371 concession, 1 linear concession, &gt;1 faster than linear
372 concession.
373 @param minUtil the minimum utility possible in our profile
374 @param maxUtil the maximum utility possible in our profile
375 @return the utility goal for this time and e value
376 """
377
378
379 ft1 = Decimal(1)
380 if e != 0:
381 ft1 = round(Decimal(1 - pow(t, 1 / e)), 6) # defaults ROUND_HALF_UP
382 return max(min((minUtil + (maxUtil - minUtil) * ft1), maxUtil), minUtil)
383
384
Note: See TracBrowser for help on using the repository browser.