source: CSE3210/agent41/agent41.py

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

#6 Added CSE3210 parties

File size: 11.7 KB
Line 
1import decimal
2import time
3from random import randint
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.bidspace.AllBidsList import AllBidsList
10from geniusweb.inform.ActionDone import ActionDone
11from geniusweb.inform.Finished import Finished
12from geniusweb.inform.Inform import Inform
13from geniusweb.inform.Settings import Settings
14from geniusweb.inform.YourTurn import YourTurn
15from geniusweb.issuevalue.Bid import Bid
16from geniusweb.opponentmodel.FrequencyOpponentModel import FrequencyOpponentModel
17from geniusweb.party.Capabilities import Capabilities
18from geniusweb.party.DefaultParty import DefaultParty
19from geniusweb.profileconnection.ProfileConnectionFactory import (
20 ProfileConnectionFactory,
21)
22from geniusweb.progress.ProgressRounds import ProgressRounds
23from tudelft_utilities_logging.Reporter import Reporter
24
25reservation_progress = 0.995
26search_numb = 2500
27
28
29class Agent41(DefaultParty):
30 """
31 Template agent that offers random bids until a bid with sufficient utility is offered.
32 """
33
34 def __init__(self, reporter: Reporter = None):
35 super().__init__(reporter)
36 # self.getReporter().log(logging.INFO, "party is initialized")
37 self._profile = None
38 self._last_received_bid: Bid = None
39 self._best_past_bid: Bid = None
40 self.opponentModel = FrequencyOpponentModel.create()
41
42
43 # maximal advantage that our opponent might have over us to accept the bid in the later phase
44 self.util_adv_from_accept = decimal.Decimal(0.2)
45 # maximal advantage that our opponent might have over us
46 # to propose the bid in the later phase
47 self.util_adv_from_offer = decimal.Decimal(0.1)
48 # maximal advantage that our agent might have over the opponent
49 # to propose the bid in the later phase (potential to be removed)
50 self.util_adv_to_offer = decimal.Decimal(0.8)
51 # progress point at which the intermediate strategy starts being applied
52 self.progress_mid = 0.6
53 # progress point at which the late strategy starts being applied
54 self.progress_fast = 0.95
55 # starting utility range for which the offers are considered
56 self.utility_range = [decimal.Decimal(0.9),
57 decimal.Decimal(1.1)]
58 # linear decrease factors in different stages of the negotiations
59 self.slow_decrease = decimal.Decimal(0.0005)
60 self.mid_decrease = decimal.Decimal(0.001)
61 self.fast_decrease = decimal.Decimal(0.002)
62 # initial reservation value under which bids are denied
63 self.minimal_reservation_val = decimal.Decimal(0.6)
64
65 def notifyChange(self, info: Inform):
66 """This is the entry point of all interaction with your agent after is has been initialised.
67
68 Args:
69 info (Inform): Contains either a request for action or information.
70 """
71
72 # a Settings message is the first message that will be send to your
73 # agent containing all the information about the negotiation session.
74 if isinstance(info, Settings):
75 self._settings: Settings = cast(Settings, info)
76 self._me = self._settings.getID()
77
78 # progress towards the deadline has to be tracked manually through the use of the Progress object
79 self._progress: ProgressRounds = self._settings.getProgress()
80
81 # the profile contains the preferences of the agent over the domain
82 self._profile = ProfileConnectionFactory.create(
83 info.getProfile().getURI(), self.getReporter()
84 )
85
86 domain = self._profile.getProfile().getDomain()
87 self.opponentModel = self.opponentModel.With(newDomain=domain, newResBid=0)
88
89 # ActionDone is an action send by an opponent (an offer or an accept)
90 elif isinstance(info, ActionDone):
91 action: Action = cast(ActionDone, info).getAction()
92
93 self.opponentModel = self.opponentModel.WithAction(action=action,
94 progress=self._progress)
95
96 # if it is an offer, set the last received bid
97 if isinstance(action, Offer):
98 self._last_received_bid = cast(Offer, action).getBid()
99
100
101 # YourTurn notifies you that it is your turn to act
102 elif isinstance(info, YourTurn):
103 action = self._my_turn()
104 if isinstance(self._progress, ProgressRounds):
105 self._progress = self._progress.advance()
106 self.getConnection().send(action)
107
108 # Finished will be send if the negotiation has ended (through agreement or deadline)
109 elif isinstance(info, Finished):
110 # terminate the agent MUST BE CALLED
111 self.terminate()
112 # else:
113 # self.getReporter().log(
114 # logging.WARNING, "Ignoring unknown info " + str(info)
115 # )
116
117 # lets the geniusweb system know what settings this agent can handle
118 # leave it as it is for this competition
119 def getCapabilities(self) -> Capabilities:
120 return Capabilities(
121 set(["SAOP"]),
122 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
123 )
124
125 # terminates the agent and its connections
126 # leave it as it is for this competition
127 def terminate(self):
128 # self.getReporter().log(logging.INFO, "party is terminating:")
129 super().terminate()
130 if self._profile is not None:
131 self._profile.close()
132 self._profile = None
133
134
135
136 # give a description of your agent
137 def getDescription(self) -> str:
138 return "Agent orange for Collaborative AI course"
139
140 def _my_turn(self):
141 """
142 Executes a turn.
143 """
144 # update the utility range
145 self._update_range()
146
147 # check if the last received offer if the opponent is good enough
148 if self._is_good(self._last_received_bid):
149 # if so, accept the offer
150 action = Accept(self._me, self._last_received_bid)
151 else:
152 # if not, find a bid to propose as counter offer
153 bid = self._find_bid()
154 action = Offer(self._me, bid)
155
156 # send the action
157 return action
158
159 # method that checks if we would agree with an offer
160 def _is_good(self, bid: Bid) -> bool:
161 """
162 Checks if the received offer is good enough to be accepted.
163 """
164 if bid is None:
165 return False
166
167 profile = self._profile.getProfile()
168
169 # Update the best past bid effectively making it a reservation bid
170 if self._best_past_bid is None:
171 self._best_past_bid = bid
172 elif profile.getUtility(bid) > profile.getUtility(self._best_past_bid):
173 self._best_past_bid = bid
174
175 # get the utility of us and our opponent (frequency model)
176 our_util = profile.getUtility(bid)
177 opp_util = self.opponentModel.getUtility(bid)
178 progress = self._progress.get(time.time() * 1000)
179
180 # immediately return the offer it is below our lower utility threshold
181 if our_util < self.utility_range[0]:
182 return False
183 # if the progress reached a certain point,
184 # accept the ofer only if it is not overwhelmingly better than the opponents
185 if progress > self.progress_mid:
186 return our_util + self.util_adv_from_accept >= opp_util
187 # if this progress has not been reached yet,
188 # simply accept the offer if it is better than our utility
189 return True
190
191 def _find_bid(self) -> Bid:
192 """
193 Find the most suitable bid to offer given the current state of negotiations.
194 """
195 domain = self._profile.getProfile().getDomain()
196 profile = self._profile.getProfile()
197
198 # verify if the reservation bid should be used
199 if self._verify_reservation_val():
200 return self._best_past_bid
201
202 # store the best bid found so far
203 best_bid: Bid = self._best_past_bid
204 # store the difference between the utility of the best bid
205 # and the upper threshold of the range
206 best_dist = decimal.Decimal(1.0)
207
208 all_bids = AllBidsList(domain)
209 # search through a number of random bids to find a suitable bid
210 for _ in range(search_numb):
211 # get the random bid and the utility of it
212 bid = all_bids.get(randint(0, all_bids.size() - 1))
213 our_util = profile.getUtility(bid)
214 # verify if the bid is suitable to be offered to the opponent
215 if self._verify_bid(bid):
216 best_bid = bid
217 break
218 # If the best bid was not found, check if the current bid is closer to idea value
219 curr_dist = abs(our_util - self.utility_range[1])
220 if curr_dist < best_dist:
221 best_bid = bid
222 best_dist = curr_dist
223
224 return best_bid
225
226 def _update_range(self):
227 """
228 Update the acceptance range depending on the current progress of the negotiation.
229 The further we are into the negotiation, the faster the utility range drops.
230 """
231 progress = self._progress.get(time.time() * 1000)
232 if progress < self.progress_mid:
233 self.utility_range[1] = self.utility_range[1] - self.slow_decrease
234 self.utility_range[0] = self.utility_range[0] - self.slow_decrease
235 elif progress > self.progress_fast:
236 self.utility_range[1] = self.utility_range[1] - self.fast_decrease
237 self.utility_range[0] = self.utility_range[0] - self.fast_decrease
238 else:
239 self.utility_range[1] = self.utility_range[1] - self.mid_decrease
240 self.utility_range[0] = self.utility_range[0] - self.mid_decrease
241
242 def _verify_reservation_val(self) -> bool:
243 """
244 Return the best bid received so far, if the time is almost up and the bid is good enough.
245 The reservation value is equal to the best offer received from the opponent.
246 It is sent if our utility coming from this offer exceeds 0.7
247 or if the utility of the reservation bid exceeds our upper utility threshold.
248 """
249 if self._best_past_bid is not None:
250 reservation_val = self._profile.getProfile().getUtility(
251 self._best_past_bid)
252 if self._progress.get(
253 0) >= reservation_progress and reservation_val >= self.minimal_reservation_val:
254 return True
255 if self._profile.getProfile().getUtility(self._best_past_bid) >= self.utility_range[1]:
256 return True
257 return False
258
259 def _verify_bid(self, bid: Bid) -> bool:
260 """
261 Verifies if the bid that our agent came up with is good enough to be offered to another agent.
262 """
263 progress = self._progress.get(time.time() * 1000)
264 profile = self._profile.getProfile()
265 our_util = profile.getUtility(bid)
266
267 # do not consider the bid if it is worse than our reservation bid
268 if self._best_past_bid is not None and our_util < profile.getUtility(self._best_past_bid):
269 return False
270 # if a certain point in the negotiation has not been reached,
271 # propose the bid if it falls into the accepted utility range
272 if progress < self.progress_mid:
273 return self.utility_range[0] < our_util < self.utility_range[1]
274
275 # otherwise, in addition to considering the utility range, also consider the utility of our opponent, make sure
276 # that the utility difference between us and the opponent is not too high
277 opp_util = self.opponentModel.getUtility(bid)
278 return our_util + self.util_adv_from_offer > opp_util > our_util - self.util_adv_to_offer \
279 and self.utility_range[0] < our_util < self.utility_range[1]
Note: See TracBrowser for help on using the repository browser.