source: ANL2022/agentfish/agentfish.py@ 77

Last change on this file since 77 was 75, checked in by wouter, 2 years ago

#6 added ANAC2022 parties

File size: 12.3 KB
Line 
1import logging
2from random import randint,uniform
3from time import time
4from typing import cast
5
6from geniusweb.actions.Accept import Accept
7from geniusweb.actions.Action import Action
8from geniusweb.actions.LearningDone import LearningDone
9from geniusweb.actions.Offer import Offer
10from geniusweb.actions.PartyId import PartyId
11from geniusweb.actions.Vote import Vote
12from geniusweb.actions.Votes import Votes
13from geniusweb.bidspace.AllBidsList import AllBidsList
14from geniusweb.inform.ActionDone import ActionDone
15from geniusweb.inform.Finished import Finished
16from geniusweb.inform.Inform import Inform
17from geniusweb.inform.OptIn import OptIn
18from geniusweb.inform.Settings import Settings
19from geniusweb.inform.Voting import Voting
20from geniusweb.inform.YourTurn import YourTurn
21from geniusweb.issuevalue.Bid import Bid
22from geniusweb.issuevalue.Domain import Domain
23from geniusweb.issuevalue.Value import Value
24from geniusweb.issuevalue.ValueSet import ValueSet
25from geniusweb.party.Capabilities import Capabilities
26from geniusweb.party.DefaultParty import DefaultParty
27from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
28from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import (
29 LinearAdditiveUtilitySpace,
30)
31from geniusweb.profileconnection.ProfileConnectionFactory import (
32 ProfileConnectionFactory,
33)
34from geniusweb.progress.ProgressTime import ProgressTime
35from geniusweb.references.Parameters import Parameters
36from tudelft_utilities_logging.ReportToLogger import ReportToLogger
37
38from .utils.opponent_model import OpponentModel
39
40from geniusweb.progress.ProgressRounds import ProgressRounds
41from geniusweb.utils import val
42from geniusweb.profileconnection.ProfileInterface import ProfileInterface
43from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
44from geniusweb.progress.Progress import Progress
45from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
46from decimal import Decimal
47import sys
48from .extended_util_space import ExtendedUtilSpace
49from tudelft_utilities_logging.Reporter import Reporter
50
51
52class AgentFish(DefaultParty):
53 """
54 Template of a Python geniusweb agent.
55 """
56
57 def __init__(self):
58 super().__init__()
59 self.logger: ReportToLogger = self.getReporter()
60
61 self.domain: Domain = None
62 self.parameters: Parameters = None
63 self.profile: LinearAdditiveUtilitySpace = None
64 self.progress: ProgressTime = None
65 self.me: PartyId = None
66 self.other: str = None
67 self.settings: Settings = None
68 self.storage_dir: str = None
69
70 self.last_received_bid: Bid = None
71 self.opponent_model: OpponentModel = None
72 self.logger.log(logging.INFO, "party is initialized")
73
74 self.profileint: ProfileInterface = None # type:ignore
75 self.utilspace: LinearAdditive = None # type:ignore
76 self.extendedspace: ExtendedUtilSpace = None # type:ignore
77 self.e: float = 1.2
78 self.lastvotes: Votes = None # type:ignore
79 self.opponent_max = 0.0
80
81 def notifyChange(self, data: Inform):
82 """MUST BE IMPLEMENTED
83 This is the entry point of all interaction with your agent after is has been initialised.
84 How to handle the received data is based on its class type.
85
86 Args:
87 info (Inform): Contains either a request for action or information.
88 """
89
90 # a Settings message is the first message that will be send to your
91 # agent containing all the information about the negotiation session.
92 if isinstance(data, Settings):
93 self.settings = cast(Settings, data)
94 self.me = self.settings.getID()
95
96 # progress towards the deadline has to be tracked manually through the use of the Progress object
97 self.progress = self.settings.getProgress()
98
99 self.parameters = self.settings.getParameters()
100 self.storage_dir = self.parameters.get("storage_dir")
101
102 # the profile contains the preferences of the agent over the domain
103 profile_connection = ProfileConnectionFactory.create(
104 data.getProfile().getURI(), self.getReporter()
105 )
106 self.profile = profile_connection.getProfile()
107 self.domain = self.profile.getDomain()
108 profile_connection.close()
109
110 # ActionDone informs you of an action (an offer or an accept)
111 # that is performed by one of the agents (including yourself).
112 elif isinstance(data, ActionDone):
113 action = cast(ActionDone, data).getAction()
114 actor = action.getActor()
115
116 # ignore action if it is our action
117 if actor != self.me:
118 # obtain the name of the opponent, cutting of the position ID.
119 self.other = str(actor).rsplit("_", 1)[0]
120
121 # process action done by opponent
122 self.opponent_action(action)
123 # YourTurn notifies you that it is your turn to act
124 elif isinstance(data, YourTurn):
125 # execute a turn
126 self.my_turn()
127
128 # Finished will be send if the negotiation has ended (through agreement or deadline)
129 elif isinstance(data, Finished):
130 self.save_data()
131 # terminate the agent MUST BE CALLED
132 self.logger.log(logging.INFO, "party is terminating:")
133 super().terminate()
134 else:
135 self.logger.log(logging.WARNING, "Ignoring unknown info " + str(data))
136
137 def getE(self) -> float:
138
139 return self.e
140
141 def getCapabilities(self) -> Capabilities:
142 """MUST BE IMPLEMENTED
143 Method to indicate to the protocol what the capabilities of this agent are.
144 Leave it as is for the ANL 2022 competition
145
146 Returns:
147 Capabilities: Capabilities representation class
148 """
149 return Capabilities(
150 set(["SAOP"]),
151 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
152 )
153
154 def send_action(self, action: Action):
155 """Sends an action to the opponent(s)
156
157 Args:
158 action (Action): action of this agent
159 """
160 self.getConnection().send(action)
161
162 # give a description of your agent
163 def getDescription(self) -> str:
164 """MUST BE IMPLEMENTED
165 Returns a description of your agent. 1 or 2 sentences.
166
167 Returns:
168 str: Agent description
169 """
170 return "Foot in the Door agent for the ANL 2022 competition"
171
172 def opponent_action(self, action):
173 """Process an action that was received from the opponent.
174
175 Args:
176 action (Action): action of opponent
177 """
178 # if it is an offer, set the last received bid
179 if isinstance(action, Offer):
180 # create opponent model if it was not yet initialised
181 if self.opponent_model is None:
182 self.opponent_model = OpponentModel(self.domain)
183
184 bid = cast(Offer, action).getBid()
185
186 # update opponent model with bid
187 self.opponent_model.update(bid)
188 # set bid as last received
189 self.last_received_bid = bid
190
191 if self.opponent_max < self.profile.getUtility(bid):
192 self.opponent_max = self.profile.getUtility(bid)
193
194 def my_turn(self):
195 """This method is called when it is our turn. It should decide upon an action
196 to perform and send this action to the opponent.
197 """
198 # check if the last received offer is good enough
199 self.updateUtilSpace()
200 if self.accept_condition(self.last_received_bid):
201 # if so, accept the offer
202 action = Accept(self.me, self.last_received_bid)
203 else:
204 # if not, find a bid to propose as counter offer
205 bid = self.makeBid()
206 action = Offer(self.me, bid)
207
208 # send the action
209 self.send_action(action)
210
211 def updateUtilSpace(self) -> LinearAdditive: # throws IOException
212 newutilspace = self.profile
213 if not newutilspace == self.utilspace:
214 self.utilspace = cast(LinearAdditive, newutilspace)
215 self.extendedspace = ExtendedUtilSpace(self.utilspace)
216 return self.utilspace
217
218 def makeBid(self) -> Bid:
219 """
220 @return next possible bid with current target utility, or null if no such
221 bid.
222 """
223 time_to_deadline = self.progress.get(time() * 1000)
224
225 utilityGoal = self.getUtilityGoal(
226 time_to_deadline,
227 self.getE(),
228 self.extendedspace.getMin(),
229 self.extendedspace.getMax(),
230 )
231 options: ImmutableList[Bid] = self.extendedspace.getBids(utilityGoal)
232 if options.size() == 0:
233 # if we can't find good bid, get max util bid....
234 options = self.extendedspace.getBids(self.extendedspace.getMax())
235 # pick a random one.
236 return options.get(randint(0, options.size() - 1))
237
238 def getUtilityGoal(
239 self, t: float, e: float, minUtil: Decimal, maxUtil: Decimal
240 ) -> Decimal:
241 """
242 @param t the time in [0,1] where 0 means start of nego and 1 the
243 end of nego (absolute time/round limit)
244 @param e the e value that determinses how fast the party makes
245 concessions with time. Typically around 1. 0 means no
246 concession, 1 linear concession, &gt;1 faster than linear
247 concession.
248 @param minUtil the minimum utility possible in our profile
249 @param maxUtil the maximum utility possible in our profile
250 @return the utility goal for this time and e value
251 """
252
253 ft1 = Decimal(1)
254 if e != 0:
255 ft1 = round(Decimal(1 - pow(t, 1 / e)), 6) # defaults ROUND_HALF_UP
256 myoffer = max(min((maxUtil - (maxUtil - minUtil) * ft1), maxUtil), minUtil)
257 return max(myoffer, self.opponent_max)
258
259
260 def save_data(self):
261 """This method is called after the negotiation is finished. It can be used to store data
262 for learning capabilities. Note that no extensive calculations can be done within this method.
263 Taking too much time might result in your agent being killed, so use it for storage only.
264 """
265 data = "Data for learning (see README.md)"
266 with open(f"{self.storage_dir}/data.md", "w") as f:
267 f.write(data)
268
269 ###########################################################################################
270 ################################## Example methods below ##################################
271 ###########################################################################################
272
273 def accept_condition(self, bid: Bid) -> bool:
274 if bid is None:
275 return False
276
277 # progress of the negotiation session between 0 and 1 (1 is deadline)
278 progress = self.progress.get(time() * 1000)
279
280 # very basic approach that accepts if the offer is valued above 0.7 and
281 # 95% of the time towards the deadline has passed
282 maxAccept = 0.8
283 minAccept = 0.6
284 ft = round(Decimal(1 - progress), 6) # defaults ROUND_HALF_UP
285 acceptline = max(min((minAccept + (maxAccept - minAccept) * float(ft)), maxAccept), minAccept)
286 conditions = [
287 float(self.profile.getUtility(bid)) > acceptline,
288 progress > uniform(0.8, 0.95),
289 progress > 0.95,
290 ]
291 return all([any(conditions), progress > 0.1])
292
293 def score_bid(self, bid: Bid, alpha: float = 0.95, eps: float = 0.1) -> float:
294 """Calculate heuristic score for a bid
295
296 Args:
297 bid (Bid): Bid to score
298 alpha (float, optional): Trade-off factor between self interested and
299 altruistic behaviour. Defaults to 0.95.
300 eps (float, optional): Time pressure factor, balances between conceding
301 and Boulware behaviour over time. Defaults to 0.1.
302
303 Returns:
304 float: score
305 """
306 progress = self.progress.get(time() * 1000)
307
308 our_utility = float(self.profile.getUtility(bid))
309
310 time_pressure = 1.0 - progress ** (1 / eps)
311 score = alpha * time_pressure * our_utility
312
313 if self.opponent_model is not None:
314 opponent_utility = self.opponent_model.get_predicted_utility(bid)
315 opponent_score = (1.0 - alpha * time_pressure) * opponent_utility
316 score += opponent_score
317
318 return score
Note: See TracBrowser for help on using the repository browser.