source: CSE3210/agent27/agent27.py

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

#6 Added CSE3210 parties

File size: 11.9 KB
Line 
1import logging
2import time
3from typing import cast
4
5from geniusweb.actions.Accept import Accept
6from geniusweb.actions.Action import Action
7from geniusweb.actions.Offer import Offer
8from geniusweb.bidspace.AllBidsList import AllBidsList
9from geniusweb.inform.ActionDone import ActionDone
10from geniusweb.inform.Finished import Finished
11from geniusweb.inform.Inform import Inform
12from geniusweb.inform.Settings import Settings
13from geniusweb.inform.YourTurn import YourTurn
14from geniusweb.issuevalue.Bid import Bid
15from geniusweb.party.Capabilities import Capabilities
16from geniusweb.party.DefaultParty import DefaultParty
17from geniusweb.profileconnection.ProfileConnectionFactory import (
18 ProfileConnectionFactory,
19)
20from geniusweb.progress.ProgressRounds import ProgressRounds
21from tudelft_utilities_logging.Reporter import Reporter
22
23
24class Agent27(DefaultParty):
25 """
26 Agent that offers bids based on opponent modelling until an agreement is reached.
27 """
28
29 def __init__(self, reporter: Reporter = None):
30 super().__init__(reporter)
31 self.getReporter().log(logging.INFO, "party is initialized")
32 self._profile = None
33 self._last_received_bid: Bid = None
34 self.latest_bid: Bid = None
35 self.all_bids = []
36 self.opponent_preferences = []
37 self.opponent_value_count = {}
38 self.all_good_bids = []
39 self.all_previously_offered_bids = []
40 self.not_important_issues = []
41 self.middle_issues = []
42 self.all_available_bids_sorted = []
43
44 def notifyChange(self, info: Inform):
45 """This is the entry point of all interaction with your agent after is has been initialised.
46
47 Args:
48 info (Inform): Contains either a request for action or information.
49 """
50
51 # a Settings message is the first message that will be send to your
52 # agent containing all the information about the negotiation session.
53 if isinstance(info, Settings):
54 self._settings: Settings = cast(Settings, info)
55 self._me = self._settings.getID()
56
57 # progress towards the deadline has to be tracked manually through the use of the Progress object
58 self._progress: ProgressRounds = self._settings.getProgress()
59
60 # the profile contains the preferences of the agent over the domain
61 self._profile = ProfileConnectionFactory.create(
62 info.getProfile().getURI(), self.getReporter()
63 )
64 # ActionDone is an action send by an opponent (an offer or an accept)
65 elif isinstance(info, ActionDone):
66 action: Action = cast(ActionDone, info).getAction()
67
68 # if it is an offer, set the last received bid
69 if isinstance(action, Offer):
70 self._last_received_bid = cast(Offer, action).getBid()
71 # YourTurn notifies you that it is your turn to act
72 elif isinstance(info, YourTurn):
73 action = self._myTurn()
74 if isinstance(self._progress, ProgressRounds):
75 self._progress = self._progress.advance()
76 self.getConnection().send(action)
77
78 # Finished will be send if the negotiation has ended (through agreement or deadline)
79 elif isinstance(info, Finished):
80 # terminate the agent MUST BE CALLED
81 self.terminate()
82 else:
83 self.getReporter().log(
84 logging.WARNING, "Ignoring unknown info " + str(info)
85 )
86
87 # lets the geniusweb system know what settings this agent can handle
88 # leave it as it is for this competition
89 def getCapabilities(self) -> Capabilities:
90 return Capabilities(
91 set(["SAOP"]),
92 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
93 )
94
95 # terminates the agent and its connections
96 # leave it as it is for this competition
97 def terminate(self):
98 self.getReporter().log(logging.INFO, "party is terminating:")
99 super().terminate()
100 if self._profile is not None:
101 self._profile.close()
102 self._profile = None
103
104
105
106 # give a description of your agent
107 def getDescription(self) -> str:
108 return "Group 27 agent for Collaborative AI course"
109
110 # Initial setup
111 def init(self):
112 profile = self._profile.getProfile()
113 """ Determine which the not important issues are and save them """
114 self.get_not_important_issues()
115
116 """
117 Initialize dictionary where we store the count of each value for each issue in opponent's bids
118 Used for opponent modelling
119 """
120 issues = profile.getDomain().getIssues()
121 for issue in issues:
122 self.opponent_value_count[issue] = {}
123 for value in profile.getDomain().getValues(issue):
124 self.opponent_value_count[issue][value] = 0
125 """ Save a list of all bids in the domain ordered by utility """
126 self.order_bids()
127
128 """
129 Every time we receive a bid, add 1 to the counter for that value
130 Used for opponent modelling
131 """
132
133 def update_opponent_counts(self):
134 lrb = self._last_received_bid
135 for issue in lrb.getIssues():
136 self.opponent_value_count[issue][lrb.getValue(issue)] += 1
137
138 # execute a turn
139 def _myTurn(self):
140 profile = self._profile.getProfile()
141
142 # Initial setup
143 if not self.opponent_value_count:
144 self.init()
145
146 if self._last_received_bid is not None:
147 """ We update the count for each value for each issue of our opponent """
148 self.update_opponent_counts()
149 """ We update the list of all issues sent by our opponent """
150 self.all_bids.append((self._last_received_bid, profile.getUtility(self._last_received_bid)))
151
152 if self._isGood(self._last_received_bid):
153 """ if so, accept the offer """
154 action = Accept(self._me, self._last_received_bid)
155 else:
156 """ if not, find a bid to propose as counter offer """
157 bid = self._findBid()
158 action = Offer(self._me, bid)
159 self.latest_bid = bid
160
161 """ send the action """
162 return action
163
164 """
165 Method that decides whether an offer is good or not
166
167 Parameters
168 ----------
169 bid: the offer that is received/ to be send
170 """
171
172 def _isGood(self, bid: Bid) -> bool:
173 if bid is None:
174 return False
175
176 progress = self._progress.get(time.time() * 1000)
177 profile = self._profile.getProfile()
178
179 if progress < 0.85:
180 if float(profile.getUtility(bid)) > 1 - progress / 4.5:
181 return True
182 elif progress < 0.95:
183 if float(profile.getUtility(bid)) > 1 - progress / 2.8:
184 return True
185 elif progress < 0.99:
186 if float(profile.getUtility(bid)) > 1 - progress / 1.8:
187 return True
188 else:
189 return True
190
191 return False
192
193 def _findBid(self) -> Bid:
194
195 bid_offer = self.get_suitable_bid()[0][0]
196 if bid_offer is not None:
197 self.all_previously_offered_bids.append(bid_offer)
198 if bid_offer is None:
199 """ When we have not offered a bid, offer highest preference """
200 if len(self.all_previously_offered_bids) == 0:
201 bid = self.get_highest_bid()
202 else:
203 """ since no new good offers are available, start offering what we have already offered before
204 (starting from the best available offers) """
205 bid = self.all_previously_offered_bids.pop(0)
206 self.all_previously_offered_bids.append(bid)
207 else:
208 bid = bid_offer
209 # print("Bid utility------------", self._profile.getProfile().getUtility(bid))
210
211 return bid
212
213 """
214 Selects a favorable bid for the opponent based on issues that are not important to us,
215 but we consider them to be important fo the opponent.
216 """
217
218 def get_suitable_bid(self):
219 all_bids = self.all_available_bids_sorted
220
221 opponent_desired_bid = self.get_opponent_info_good()
222 not_important_issues = self.not_important_issues
223
224 chosen_bid = None
225 chosen_bid_utility = None
226
227 for bid, bid_utility in all_bids:
228 counter = 0
229 if bid not in self.all_previously_offered_bids:
230 if opponent_desired_bid is not None:
231 for not_important_issue in not_important_issues:
232 if bid.getIssueValues().get(not_important_issue) == opponent_desired_bid.get(
233 not_important_issue):
234 counter += 1
235
236 if (opponent_desired_bid is not None or counter == len(not_important_issues)) and self._isGood(bid):
237 chosen_bid = bid
238 chosen_bid_utility = bid_utility
239 break
240
241 return [(chosen_bid, chosen_bid_utility)]
242
243 """
244 Sorts all available bids on utility.
245 """
246 def order_bids(self):
247 domain = self._profile.getProfile().getDomain()
248 all_bids = AllBidsList(domain)
249
250 bids_with_utility = []
251
252 for bid in all_bids:
253 bids_with_utility.append((bid, self._profile.getProfile().getUtility(bid)))
254
255 bids_with_utility = sorted(bids_with_utility, key=lambda item: -item[1])
256 self.all_available_bids_sorted = bids_with_utility
257
258 """
259 Sorts all bids and selects the one with highest utility.
260 """
261 def get_highest_bid(self):
262 domain = self._profile.getProfile().getDomain()
263 all_bids = AllBidsList(domain)
264
265 bids_with_utility = []
266
267 for bid in all_bids:
268 bids_with_utility.append((bid, self._profile.getProfile().getUtility(bid)))
269
270 bids_with_utility = sorted(bids_with_utility, key=lambda item: -item[1])
271 return bids_with_utility[0][0]
272
273 """
274 Returns a dictionary where the keys are the issues
275 and the values are the most often occurring value for this issue
276 """
277 def get_opponent_info_good(self):
278 """ If we have too few of our opponent's bids, return nothing """
279 if len(self.all_bids) < 2:
280 return None
281
282 issues = self._profile.getProfile().getDomain().getIssues()
283
284 opponent_counts = self.opponent_value_count
285 demanded_best_offer = {}
286
287 """ For each issue, sort by the number of occurrences of each value and take the highest """
288 for issue in issues:
289 opponent_values = opponent_counts[issue]
290 sorted_dict = dict(sorted(opponent_values.items(), key=lambda item: item[1]))
291 opponent_val = list(sorted_dict.keys())[-1]
292 demanded_best_offer[issue] = opponent_val
293
294 return demanded_best_offer
295
296 """
297 Returns two arrays containing issues that are not important and issues that are somewhat
298 important respectively.
299 """
300
301 def get_not_important_issues(self):
302 domain = self._profile.getProfile().getDomain()
303 issues = domain.getIssues()
304 weights = []
305 not_important_issues = []
306 middle_issues = []
307
308 for issue in issues:
309 """ Weight by issue """
310 weights.append(self._profile.getProfile().getWeight(issue))
311
312 for issue in issues:
313 w = self._profile.getProfile().getWeight(issue)
314 if w < 0.15 * float(max(weights)):
315 not_important_issues.append(issue)
316 elif w < 0.5 * float(max(weights)):
317 middle_issues.append(issue)
318 self.not_important_issues = not_important_issues
319 self.middle_issues = middle_issues
320 return not_important_issues, middle_issues
Note: See TracBrowser for help on using the repository browser.