source: CSE3210/agent22/agent22.py

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

#6 Added CSE3210 parties

File size: 14.3 KB
Line 
1import logging
2import time
3import numpy as np
4import copy
5from geniusweb.progress.Progress import Progress
6from scipy.stats import chisquare
7from random import randint
8from typing import cast
9from time import time as clock
10from geniusweb.actions.Accept import Accept
11from geniusweb.actions.Action import Action
12from geniusweb.actions.Offer import Offer
13from geniusweb.inform.ActionDone import ActionDone
14from geniusweb.inform.Finished import Finished
15from geniusweb.inform.Inform import Inform
16from geniusweb.inform.Settings import Settings
17from geniusweb.inform.YourTurn import YourTurn
18from geniusweb.issuevalue.Bid import Bid
19from geniusweb.issuevalue.Value import Value
20from decimal import Decimal
21from geniusweb.party.Capabilities import Capabilities
22from geniusweb.party.DefaultParty import DefaultParty
23from geniusweb.profile.utilityspace import LinearAdditive
24from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
25from geniusweb.profileconnection import ProfileInterface
26from geniusweb.profileconnection.ProfileConnectionFactory import (
27 ProfileConnectionFactory,
28)
29from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
30
31from .extended_util_space import ExtendedUtilSpace
32from geniusweb.progress.ProgressRounds import ProgressRounds
33from tudelft_utilities_logging.Reporter import Reporter
34
35
36class Agent22(DefaultParty):
37
38 def __init__(self, reporter: Reporter = None):
39 super().__init__(reporter)
40 self.getReporter().log(logging.INFO, "party is initialized")
41 self._profile: ProfileInterface = None
42 self._last_received_bid: Bid = None
43 self._progress: Progress = None # type:ignore
44 self._extendedspace: ExtendedUtilSpace = None
45 self.issue_names = []
46 self.bidList: list[Bid] = []
47 self.bidListOpp: list[Bid] = []
48 self.weightList: dict[str, Decimal] = {}
49 self.weightListOpp: dict[str, Decimal] = {}
50 self.issue_value_frequencies = {}
51 self.prev_issue_value_frequencies = {}
52 self.cc = 1 # concession constant
53
54 def notifyChange(self, info: Inform):
55 """This is the entry point of all interaction with your agent after is has been initialised.
56
57 Args:
58 info (Inform): Contains either a request for action or information.
59 """
60
61 # a Settings message is the first message that will be send to your
62 # agent containing all the information about the negotiation session.
63 if isinstance(info, Settings):
64 self._settings: Settings = cast(Settings, info)
65 self._me = self._settings.getID()
66
67 # progress towards the deadline has to be tracked manually through the use of the Progress object
68 self._progress: Progress = self._settings.getProgress()
69
70 # the profile contains the preferences of the agent over the domain
71 self._profile = ProfileConnectionFactory.create(
72 info.getProfile().getURI(), self.getReporter()
73 )
74
75 profile: LinearAdditive = self._profile.getProfile()
76 self.weightList = profile.getWeights()
77 self.issue_names = list(self.weightList.keys())
78 n = len(self.issue_names)
79 self.weightListOpp = dict(zip(self.issue_names, np.full(n, Decimal(round(1 / n, 6)))))
80 self.issue_value_frequencies = dict(zip(self.issue_names, {}))
81 # ActionDone is an action send by an opponent (an offer or an accept)
82 elif isinstance(info, ActionDone):
83 action: Action = cast(ActionDone, info).getAction()
84 # if it is an offer, set the last received bid
85 if isinstance(action, Offer):
86 self._last_received_bid = cast(Offer, action).getBid()
87 self.bidListOpp.append(self._last_received_bid)
88 self._updateFrequencies(self._last_received_bid)
89 self.update_weight_every_window()
90
91
92
93 # YourTurn notifies you that it is your turn to act
94 elif isinstance(info, YourTurn):
95 # execute a turn
96 action = self._myTurn()
97 if action is Offer:
98 self.bidList.append(action.getBid())
99 if isinstance(self._progress, ProgressRounds):
100 self._progress = self._progress.advance()
101 self.getConnection().send(action)
102
103 # Finished will be send if the negotiation has ended (through agreement or deadline)
104 elif isinstance(info, Finished):
105 # terminate the agent MUST BE CALLED
106 self.terminate()
107 else:
108 self.getReporter().log(
109 logging.WARNING, "Ignoring unknown info " + str(info)
110 )
111
112 # lets the geniusweb system know what settings this agent can handle
113 # leave it as it is for this competition
114 def getCapabilities(self) -> Capabilities:
115 return Capabilities(
116 set(["SAOP"]),
117 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
118 )
119
120 # terminates the agent and its connections
121 # leave it as it is for this competition
122 def terminate(self):
123 self.getReporter().log(logging.INFO, "party is terminating:")
124 super().terminate()
125 if self._profile is not None:
126 self._profile.close()
127 self._profile = None
128
129
130
131 # give a description of your agent
132 # Overrride
133 def getDescription(self) -> str:
134 return "Agent22"
135
136 # execute a turn
137 # Override
138 def _myTurn(self):
139 self._updateExtUtilSpace()
140 # check if the last received offer if the opponent is good enough
141 ourBid = self._findBid()
142 if self._isGoodNew(self._last_received_bid, ourBid):
143 # if so, accept the offer
144 action = Accept(self._me, self._last_received_bid)
145 else:
146 # if not, find a bid to propose as counter offer
147 bid = ourBid
148 action = Offer(self._me, bid)
149
150 # send the action
151 return action
152 return action
153
154 def _updateExtUtilSpace(self): # throws IOException
155 new_utilspace: LinearAdditive = self._profile.getProfile()
156 self._extendedspace = ExtendedUtilSpace(new_utilspace)
157
158 def _findBid(self) -> Bid:
159 beta = self._checkStrategyOpp()
160 return self.time_dependent_bidding(beta)
161
162 def _getTheirUtility(self, bid: Bid):
163 value_estimation = self.val_estimation()
164 utility = 0
165 for issue in bid.getIssues():
166 value = bid.getValue(issue)
167 if issue in value_estimation and value in value_estimation[issue]:
168 utility += float(self.weightListOpp[issue]) * value_estimation[issue][value]
169
170 return Decimal(utility)
171
172 def _updateFrequencies(self, bid: Bid):
173 issue_values = bid.getIssueValues()
174 for issue in issue_values.keys():
175 value = issue_values[issue]
176 if not (issue in self.issue_value_frequencies):
177 self.issue_value_frequencies[issue] = {}
178 if not (value in self.issue_value_frequencies[issue]):
179 self.issue_value_frequencies[issue][value] = 0
180
181 self.issue_value_frequencies[issue][value] += 1
182
183 def _evaluate_bid(self, bid: Bid):
184 profile = self._profile.getProfile()
185 progress = self._progress.get(time.time() * 1000)
186
187 U_mine = profile.getUtility(bid)
188 U_theirs = self._getTheirUtility(bid)
189 a = Decimal(1 - progress)
190
191 if a < 1.0 / 2: return U_mine
192 if a >= 1.0 / 2: return (a * U_mine + (1 - a) * U_theirs) / 2
193
194
195 def _checkStrategyOpp(self) -> float:
196 opp_bids_length = len(self.bidListOpp)
197 if opp_bids_length > 0:
198 unique_opp_bids_length = len(set(self.bidListOpp))
199 t1 = unique_opp_bids_length / opp_bids_length
200 # print(t1)
201 if t1 > 0.35:
202 return 0.2
203 else:
204 return 1.8
205 else:
206 return 0.2
207
208 # Acceptance condition
209 def _isGoodNew(self, bid: Bid, plannedBid: Bid) -> bool:
210 # the offer is acceptable if it is better than
211 # all offers received in the previous time window W
212 # or the offer is better than our next planned offer
213 # W = [T - (1 - T), T]
214 if bid is None:
215 return False
216 profile = self._profile.getProfile()
217
218 progress = self._progress.get(time.time() * 1000)
219 bidsFromW = []
220 maxBidFromW = 0
221 W = 0.02
222 T = 0.98
223 if isinstance(profile, UtilitySpace):
224 reservation_bid = profile.getReservationBid()
225 if reservation_bid is None and progress >= T:
226 return True
227 reservation_value = 0.3
228 if reservation_bid is not None:
229 reservation_value = profile.getUtility(reservation_bid)
230
231 receivedBid = self._evaluate_bid(bid)
232 # If the opponent's bid is better than our next planned bid, accept
233 if (receivedBid > self._evaluate_bid(plannedBid)):
234 return True
235
236 # Save bids from window W and save the best one
237 if (progress >= T - W and progress < T):
238 bidsFromW.append(receivedBid)
239 if (receivedBid > maxBidFromW):
240 maxBidFromW = receivedBid
241
242 utility_target = reservation_value * 3 / 2
243 # After time T, accept the bid if it is better from the best bid recieved
244 # in the previous time window W
245 if (progress >= T and receivedBid < utility_target and receivedBid >= maxBidFromW):
246 return True
247
248 return receivedBid >= utility_target
249
250 def time_dependent_bidding(self, beta: float) -> Bid:
251 progress: float = self._progress.get(time.time() * 1000)
252 profile = self._profile.getProfile()
253
254 reservation_bid: Bid = profile.getReservationBid()
255 min_util = Decimal(0.6) # reservation value
256 if reservation_bid is not None:
257 min_util = Decimal(profile.getUtility(reservation_bid))
258
259 max_util: Decimal = Decimal(1)
260
261 ft1 = Decimal(1)
262 if beta != 0:
263 ft1 = round(Decimal(1 - pow(progress, 1 / beta)), 6) # defaults ROUND_HALF_UP
264 utilityGoal: Decimal = min_util + (max_util - min_util) * ft1
265
266 options: ImmutableList[Bid] = self._extendedspace.getBids(utilityGoal)
267 if options.size() == 0:
268 # if we can't find good bid, get max util bid....
269 options = self._extendedspace.getBids(self._extendedspace.getMax())
270
271 for bid in options:
272 if self._isGoodNew(self._last_received_bid, bid):
273 return bid
274
275 # else pick a random one.
276 return options.get(randint(0, options.size() - 1))
277
278 def update_weight_every_window(self):
279 k = 10
280 if len(self.bidListOpp) % k == 0:
281 self.weightListOpp = self.oppWeights()
282 self.prev_issue_value_frequencies = copy.deepcopy(self.issue_value_frequencies)
283
284 def val_estimation(self) -> dict[str, dict[Value, float]]:
285 gamma = 0.5
286 freqs = copy.deepcopy(self.issue_value_frequencies)
287 value_func = copy.deepcopy(self.issue_value_frequencies)
288 for issue in freqs.keys():
289 max_value = max(freqs[issue], key=freqs[issue].get)
290 for value in freqs[issue].keys():
291 value_func[issue][value] = ((1 + freqs[issue][value]) ** gamma) / (
292 (1 + freqs[issue][max_value]) ** gamma)
293
294 return value_func
295
296 def oppWeights(self) -> dict[str, Decimal]:
297 alpha = 10 # alpha denotes how much importance is added to weights
298 beta = 5 # beta denotes how much this importance matters over time
299 e = [] # list of issues that did not change significantly in frequency
300 concession = False
301 new_weights: dict[str, Decimal] = copy.deepcopy(self.weightListOpp)
302 issue_list = self.prev_issue_value_frequencies.keys()
303 value_func = self.val_estimation()
304 progress = self._progress.get(round(clock() * 1000))
305 n = len(issue_list)
306 for issue in issue_list:
307 # Calculate the frequencies from the currently found values
308 frequencies = copy.deepcopy(self.issue_value_frequencies[issue])
309 N = sum(frequencies.values())
310 for value in frequencies.keys():
311 frequencies[value] /= float(N)
312
313 prev_frequencies = copy.deepcopy(self.prev_issue_value_frequencies[issue])
314 # Add the newly found values to the previous dictionary
315 for value in frequencies.keys():
316 if value not in prev_frequencies:
317 prev_frequencies[value] = 0
318 # Calculate the frequencies from the previous found values
319 N = sum(prev_frequencies.values())
320 for value in prev_frequencies.keys():
321 prev_frequencies[value] /= float(N)
322
323 # Do a chi squared distribution test on the frequencies to check if they have changed significantly
324 obs = list(frequencies.values())
325 exp = list(prev_frequencies.values())
326 _, p_val = chisquare(f_obs=obs, f_exp=exp)
327 # If our frequencies did not change significantely add this issue to e
328 if p_val > 0.05:
329 e.append(issue)
330 else:
331 # Calculate the expected value for the utility for each issue value and compare with the previous found one
332 prev_expected = {k: prev_frequencies[k] * value_func[issue][k] for k in prev_frequencies}
333 expected = {k: frequencies[k] * value_func[issue][k] for k in frequencies}
334 if sum(expected.values()) < sum(prev_expected.values()):
335 concession = True
336
337 if len(e) != len(issue_list) and concession:
338 for issue in e:
339 delta_t = Decimal(alpha * (1 - progress ** beta))
340 new_weights[issue] += delta_t
341
342 # Normalize weights
343 summed = sum(new_weights.values())
344 for key in new_weights:
345 new_weights[key] = Decimal(round(new_weights[key] / summed, 6))
346
347 # print(new_weights)
348 return new_weights
Note: See TracBrowser for help on using the repository browser.