source: CSE3210/agent25/agent25.py

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

#6 Added CSE3210 parties

File size: 13.0 KB
Line 
1import logging
2import random
3import time
4from typing import cast, Dict, List, Union
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.inform.ActionDone import ActionDone
12from geniusweb.inform.Finished import Finished
13from geniusweb.inform.Inform import Inform
14from geniusweb.inform.Settings import Settings
15from geniusweb.inform.YourTurn import YourTurn
16from geniusweb.issuevalue.Bid import Bid
17from geniusweb.issuevalue.Value import Value
18from geniusweb.party.Capabilities import Capabilities
19from geniusweb.party.DefaultParty import DefaultParty
20from geniusweb.profile.Profile import Profile
21from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
22from geniusweb.profileconnection.ProfileConnectionFactory import ProfileConnectionFactory
23from geniusweb.profileconnection.ProfileInterface import ProfileInterface
24from geniusweb.progress.Progress import Progress
25from geniusweb.progress.ProgressRounds import ProgressRounds
26from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
27from tudelft.utilities.immutablelist.Outer import Outer
28
29import numpy as np
30from uri.uri import URI
31from geniusweb.progress.ProgressRounds import ProgressRounds
32from tudelft_utilities_logging.Reporter import Reporter
33
34
35class Agent25(DefaultParty):
36 def __init__(self, reporter: Reporter = None):
37 super().__init__(reporter)
38
39 # How linearly the agent concedes over time. 1 = linear concession.
40 # Concession parameter for ideal bid offer function.
41 self._offer_concession_param = 1.2
42 # Concession parameter for minimum acceptance function.
43 self._accept_concession_param = 1.2
44 # The amount of randomization in bids the agent makes. Usually values 0-10.
45 self._randomization_param = 3
46
47 self._opponent_bid_utilities: List[float] = []
48 self._opponent_model: Dict[str, Dict[Value, int]] = {}
49
50 self._best_bid = None
51
52 self._all_bids: List[(float, Dict[str, Value])] = []
53
54 self._min_utility: Union[float, None] = None
55 self._max_utility: Union[float, None] = None
56
57 self._id: Union[PartyId, None] = None
58 self._last_received_bid: Union[Bid, None] = None
59 self._profile: Union[ProfileInterface, None] = None
60 self._session_progress: Union[Progress, None] = None
61 self._session_settings: Union[Settings, None] = None
62 self._uri: Union[URI, None] = None
63
64 self.getReporter().log(logging.INFO, "Agent initialized")
65
66 # Informs the GeniusWeb system what protocols the agent supports and what type of profile it has.
67 def getCapabilities(self) -> Capabilities:
68 return Capabilities({"SAOP"}, {'geniusweb.profile.utilityspace.LinearAdditive'})
69
70 # Gives a description of the agent.
71 def getDescription(self) -> str:
72 return 'Custom agent created by CAI group 25.'
73
74 # Handles all interaction between the agent and the session.
75 def notifyChange(self, info: Inform):
76
77 # First message sent in the negotiation - informs the agent about details of the negotiation session.
78 if isinstance(info, Settings):
79 self._session_settings = cast(Settings, info)
80
81 self._id = self._session_settings.getID()
82 self._session_progress = self._session_settings.getProgress()
83 self._uri: str = str(self._session_settings.getProtocol().getURI())
84
85 if "Learn" == str(self._session_settings.getProtocol().getURI()):
86 self.getConnection().send(LearningDone(self._id))
87
88 else:
89 self._profile = ProfileConnectionFactory.create(info.getProfile().getURI(), self.getReporter())
90 profile = self._profile.getProfile()
91
92 # Finds all possible bids the agent can make and their corresponding utilities.
93 if isinstance(profile, UtilitySpace):
94 issues = list(profile.getDomain().getIssues())
95 values: List[ImmutableList[Value]] = [profile.getDomain().getValues(issue) for issue in issues]
96 all_bids: Outer = Outer[Value](values)
97
98 for i in range(all_bids.size()):
99 bid = {}
100 for j in range(all_bids.get(i).size()):
101 bid[issues[j]] = all_bids.get(i).get(j)
102
103 utility = float(profile.getUtility(Bid(bid)))
104 self._all_bids.append((utility, bid))
105
106 # Sorts by highest utility first.
107 self._all_bids = sorted(self._all_bids, key=lambda x: x[0], reverse=True)
108
109 self._max_utility = self._all_bids[0][0]
110 self._min_utility = self._all_bids[len(self._all_bids) - 1][0]
111
112 # Indicates that the opponent has ended their turn by accepting your last bid or offering a new one.
113 elif isinstance(info, ActionDone):
114 action: Action = cast(ActionDone, info).getAction()
115
116 if isinstance(action, Offer):
117 self._last_received_bid = cast(Offer, action).getBid()
118
119 # Indicates that it is the agent's turn. The agent can accept the opponent's last bid or offer a new one.
120 elif isinstance(info, YourTurn):
121 action = self._execute_turn()
122
123 if isinstance(self._session_progress, ProgressRounds):
124 self._session_progress = self._session_progress.advance()
125
126 self.getConnection().send(action)
127
128 # Indicates that the session is complete - either the time has expired or a bid has been agreed on.
129 elif isinstance(info, Finished):
130 finished = cast(Finished, info)
131 self.terminate()
132
133 # Indicates that the information received was of an unknown type.
134 else:
135 self.getReporter().log(logging.WARNING, "Ignoring unknown info: " + str(info))
136
137 # Terminates the agent and its connections.
138 def terminate(self):
139 self.getReporter().log(logging.INFO, "Agent is terminating...")
140
141 super().terminate()
142
143 if self._profile is not None:
144 self._profile.close()
145 self._profile = None
146
147 ###############################################################
148 # Functions below determine the agent's negotiation strategy. #
149 ###############################################################
150
151 # Processes the opponent's last bid and offers a new one if it isn't satisfactory.
152 def _execute_turn(self):
153 if self._last_received_bid is not None:
154 # Updates opponent preference profile model by incrementing issue values which appeared in the bid.
155 issues = self._last_received_bid.getIssues()
156 for issue in issues:
157 value = self._last_received_bid.getValue(issue)
158
159 if issue in self._opponent_model and value in self._opponent_model[issue]:
160 self._opponent_model[issue][value] += 1
161 else:
162 if issue not in self._opponent_model:
163 self._opponent_model[issue] = {}
164
165 self._opponent_model[issue][value] = 1
166
167 # Creates normalized opponent profile with updated values
168 opponent_normalized_model: Dict[str, dict[Value, float]] = {}
169 for issue, value in self._opponent_model.items():
170 opponent_normalized_model[issue] = {}
171
172 if len(self._opponent_model.get(issue).values()) > 0:
173 max_count = max(self._opponent_model.get(issue).values())
174
175 for discrete_value, count in self._opponent_model.get(issue).items():
176 opponent_normalized_model[issue][discrete_value] = count / max_count
177
178 # Calculates the predicted utility that the opponent gains from their last proposed bid.
179 opponent_utility = 0
180 for issue in self._last_received_bid.getIssues():
181 if issue in opponent_normalized_model:
182 value = self._last_received_bid.getValue(issue)
183 if value in opponent_normalized_model.get(issue):
184 opponent_utility += opponent_normalized_model.get(issue).get(value)
185 opponent_utility = opponent_utility / len(self._last_received_bid.getIssues())
186 self._opponent_bid_utilities.append(opponent_utility)
187
188 # Predicts how much the opponent is conceding based on best-fit line gradient of previous proposed bids.
189 opponent_concession_estimate = -1.0
190 self._session_settings.getProgress().getTerminationTime()
191 if self._session_progress.get(int(time.time())) > 0.1:
192 variables = np.polyfit(
193 [x for x in range(0, 20)],
194 self._opponent_bid_utilities[
195 len(self._opponent_bid_utilities) - 21:
196 len(self._opponent_bid_utilities) - 1
197 ],
198 1
199 )
200 opponent_concession_estimate = variables[0] * 10
201
202 # Checks if opponent is hard-lining and adjusts strategy.
203 if abs(opponent_concession_estimate) < 0.001:
204 self._accept_concession_param = self._accept_concession_param * 0.9
205 self._offer_concession_param = self._offer_concession_param * 0.9
206
207 if self._accept_bid(self._last_received_bid):
208 action = Accept(self._id, self._last_received_bid)
209
210 else:
211 bid = self._create_bid()
212 action = Offer(self._id, bid)
213
214 return action
215
216 # Checks if a bid should be accepted.
217 def _accept_bid(self, bid: Bid) -> bool:
218 if bid is None:
219 return False
220
221 profile: Profile = self._profile.getProfile()
222
223 if isinstance(profile, UtilitySpace):
224 time_modifier = self._session_progress.get(int(time.time())) ** self._accept_concession_param
225
226 # The minimum bid utility the agent can accept this round according to its strategy.
227 min_acceptance = 1.0 - time_modifier
228 min_acceptance = min_acceptance * (self._max_utility - self._min_utility)
229 min_acceptance = min_acceptance + self._min_utility
230
231 #This tracks the best possibile bid we have received from the opposing agent
232 if self._best_bid == None or profile.getUtility(bid) > profile.getUtility(self._best_bid):
233 self._best_bid = bid
234 return profile.getUtility(bid) > min_acceptance
235
236 raise Exception("Can not handle this type of profile")
237
238 # Creates a new bid to offer.
239 def _create_bid(self) -> Bid:
240 time_modifier = self._session_progress.get(int(time.time())) ** self._offer_concession_param
241
242 profile: Profile = self._profile.getProfile()
243
244 # The target utility for the agent's bid this round according to its strategy.
245 ideal_utility = 1.0 - time_modifier
246 ideal_utility = ideal_utility * (self._max_utility - self._min_utility)
247 ideal_utility = ideal_utility + self._min_utility
248
249 closest_bid = min(self._all_bids, key=lambda x: abs(x[0] - ideal_utility))
250 closest_bid_index = [y[0] for y in self._all_bids].index(closest_bid[0])
251
252 # Bids we can make this round which give the agent a utility close to the ideal utility.
253 possible_bids = self._all_bids[
254 max(closest_bid_index - self._randomization_param * 2, 0):
255 min(closest_bid_index + self._randomization_param * 2, len(self._all_bids) - 1)
256 ]
257
258 # Bids we can make this round sorted by expected opponent utility.
259 opponent_best_bids: List[(int, Dict[str, Value])] = []
260 for i in range(len(possible_bids)):
261 bid: Dict[str, Value] = possible_bids[i][1]
262 count = 0
263
264 for issue, value in bid.items():
265 if issue in self._opponent_model and value in self._opponent_model[issue]:
266 count += self._opponent_model[issue][value]
267
268 opponent_best_bids.append((count, bid))
269 opponent_best_bids = sorted(opponent_best_bids, key=lambda x: x[0], reverse=True)
270
271 # Small randomization in case the opponent's profile model isn't perfect.
272 opponent_best_bids = opponent_best_bids[0: self._randomization_param]
273
274 # Edge case for starting first - opponent's profile model isn't initialized.
275 if len(self._opponent_model) == 0:
276 final_bid = Bid(possible_bids[random.randint(0, len(possible_bids) - 1)][1])
277 else:
278 final_bid = Bid(opponent_best_bids[random.randint(0, len(opponent_best_bids) - 1)][1])
279
280 if self._best_bid != None and profile.getUtility(final_bid) <= profile.getUtility(self._best_bid):
281 return self._best_bid
282 return final_bid
Note: See TracBrowser for help on using the repository browser.