source: CSE3210/agent11/agent11.py

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

#6 Added CSE3210 parties

File size: 11.5 KB
Line 
1import logging
2import random
3import time
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.party.Capabilities import Capabilities
17from geniusweb.party.DefaultParty import DefaultParty
18from geniusweb.profileconnection.ProfileConnectionFactory import (
19 ProfileConnectionFactory,
20)
21
22from .MyOpponentModel import MyOpponentModel
23from geniusweb.progress.ProgressRounds import ProgressRounds
24from tudelft_utilities_logging.Reporter import Reporter
25
26
27class Agent11(DefaultParty):
28 """
29 Template agent that offers random bids until a bid with sufficient utility is offered.
30 """
31
32 def __init__(self, reporter: Reporter = None):
33 super().__init__(reporter)
34 self.getReporter().log(logging.INFO, "party is initialized")
35 self._profile = None
36 self._last_received_bid = None
37
38 self.sorted_bids = None
39 self.opponent_model = None
40 self.concede_range = 0.7
41 self._second_to_last_received_bid = None
42 self._last_offered_bid = None
43 self._window_size = 20
44 self._bias_correction = 0.015
45 self.concede_count = 0
46 self.non_concede_count = 0
47 self.concede_strategy = 10
48 self.received_bids = []
49 # list that tries to keep track of the movement of the opponent bids
50 self.opponent_delta = []
51
52 def notifyChange(self, info: Inform):
53 """This is the entry point of all interaction with your agent after is has been initialised.
54
55 Args:
56 info (Inform): Contains either a request for action or information.
57 """
58
59 # a Settings message is the first message that will be send to your
60 # agent containing all the information about the negotiation session.
61 if isinstance(info, Settings):
62 self._settings: Settings = cast(Settings, info)
63 self._me = self._settings.getID()
64
65 # progress towards the deadline has to be tracked manually through the use of the Progress object
66 self._progress = self._settings.getProgress()
67
68 # the profile contains the preferences of the agent over the domain
69 self._profile = ProfileConnectionFactory.create(
70 info.getProfile().getURI(), self.getReporter()
71 )
72 # initialize opponent model
73 self.opponent_model = MyOpponentModel.create().With(self._profile.getProfile().getDomain(), None)
74 # self.opponent_model = FrequencyOpponentModel.create().With(self._profile.getProfile().getDomain(), None)
75
76 # ActionDone is an action send by an opponent (an offer or an accept)
77 elif isinstance(info, ActionDone):
78 action: Action = cast(ActionDone, info).getAction()
79
80 # if it is an offer, set the last received bid
81 if isinstance(action, Offer) and action.getActor() != self._me:
82 self._last_received_bid = cast(Offer, action).getBid()
83 self.received_bids.append(self._last_received_bid)
84
85 # YourTurn notifies you that it is your turn to act
86 elif isinstance(info, YourTurn):
87 action = self._myTurn()
88 if isinstance(self._progress, ProgressRounds):
89 self._progress = self._progress.advance()
90 self.getConnection().send(action)
91
92 # Finished will be send if the negotiation has ended (through agreement or deadline)
93 elif isinstance(info, Finished):
94 # terminate the agent MUST BE CALLED
95 self.terminate()
96 else:
97 self.getReporter().log(
98 logging.WARNING, "Ignoring unknown info " + str(info)
99 )
100
101 # lets the geniusweb system know what settings this agent can handle
102 # leave it as it is for this competition
103 def getCapabilities(self) -> Capabilities:
104 return Capabilities(
105 set(["SAOP"]),
106 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
107 )
108
109 # terminates the agent and its connections
110 # leave it as it is for this competition
111 def terminate(self):
112 self.getReporter().log(logging.INFO, "party is terminating:")
113 super().terminate()
114 if self._profile is not None:
115 self._profile.close()
116 self._profile = None
117
118 # give a description of your agent
119 def getDescription(self) -> str:
120 return "Agent11"
121
122 # execute a turn
123 def _myTurn(self):
124 progress = self._progress.get(time.time() * 1000)
125 # register bet with the opponent model
126 if self._last_received_bid:
127 self.opponent_model = self.opponent_model.WithAction(Offer(None, self._last_received_bid), progress)
128
129 self._recognize_move()
130 # Check if last received offer is good enough and more than 80% passed
131 if self._isGood(self._last_received_bid):
132 action = Accept(self._me, self._last_received_bid)
133 else:
134 # Opponents bid was not good enough, we make a bid.
135 bid = self._findBid()
136
137 self._secondToLast_offered_bid = self._last_offered_bid
138 self._last_offered_bid = bid
139
140 action = Offer(self._me, bid)
141
142 return action
143
144 def _isGood(self, bid: Bid) -> bool:
145 """
146 Checks if a bid is good enough to accept.
147 @param bid the bid to consider
148 @return true if the bid is good enough, false otherwise
149 """
150 if bid is None:
151 return False
152
153 utilities = self._calculate_utilities(bid, verbose=True) # returns a tuple with (total, ours, theirs)
154 utilities_prev_offered_bid = self._calculate_utilities(self._last_offered_bid)
155
156 # We evaluate the following boolean conditions and base our final decision on them
157 good_for_me = utilities[0] >= 0.6
158 time_spend = self._progress.get(time.time() * 1000) >= 0.8
159 better_than_last_offered = self._last_offered_bid and utilities_prev_offered_bid[0] <= utilities[0]
160 better_than_reservation_value = self._profile.getProfile().getReservationBid() and self._profile \
161 .getProfile().getReservationBid() <= good_for_me
162
163 return ((good_for_me and time_spend) and better_than_reservation_value) or better_than_last_offered
164
165 def _findBid(self) -> Bid:
166 # compose a list of all possible bids
167 domain = self._profile.getProfile().getDomain()
168 all_bids = AllBidsList(domain)
169 if not self.sorted_bids:
170 self.sorted_bids = sorted(all_bids, key=lambda x: self._profile.getProfile().getUtility(x), reverse=True)
171
172 if self._progress.get(time.time() * 1000) < self.concede_range:
173 bid_index = random.randint(0, int(len(self.sorted_bids) * 0.005))
174
175 bid = self.sorted_bids[bid_index]
176
177 return bid
178 else:
179 delta = 0.1
180
181 # Every x rounds, we sum the last x deltas to check what the opponents strategy is.
182 # From experiments, an agent that concedes will get a score around -0.5 to -0.3
183 # An agent that hard lines will get a score around 0 or bigger than 0
184 # Against conceding agents, we will be more conservative and take on an hard lining strategy
185 # Against hard lining agents, we will concede more.
186 # A lower value of concede_strategy means we concede more
187 # A higher value means we are more conservative
188 if len(self.received_bids) % 10 == 0:
189 opponent_trend = sum(self.opponent_delta[-10:])
190 if opponent_trend < -0.2:
191 self.concede_strategy += 1
192 else:
193 self.concede_strategy -= 1 if self._progress.get(time.time() * 1000) < 0.8 \
194 else 2 # start conceding more towards the end
195 # we make sure this constant falls between a reasonable range, so it doesn't get too crazy
196 self.concede_strategy = max(min(self.concede_strategy, 15), 3)
197 # concede range = time after we concede
198 start_index = int(
199 len(self.sorted_bids) * (self._progress.get(time.time() * 1000) - self.concede_range) / self.concede_strategy)
200 # start_index = 0
201 # delta = randomness parameter for the generation of the bids
202 end_index = int(len(self.sorted_bids) * (self._progress.get(time.time() * 1000) - self.concede_range + delta))
203
204 bidding_range = self.sorted_bids[start_index:end_index]
205 potential_bids = []
206 # pick n random bids
207 n = 50
208 for _ in range(n):
209 potential_bids.append(bidding_range[random.randint(0, len(bidding_range) - 1)])
210
211 # find the best one according to ur best utility
212 potential_bids = sorted(potential_bids,
213 key=lambda x: self._evaluate_utilities(self._calculate_utilities(x)),
214 reverse=True)
215 bid = potential_bids[0]
216
217 return bid
218
219 def _calculate_utilities(self, bid: Bid, verbose=False):
220 """
221 Returns a tuple of the utility of an bid. The tuple consists of (total utility, own utility, opponent utility)
222 """
223 if not bid:
224 return 0, 0
225 own_utility = self._profile.getProfile().getUtility(bid)
226 opponent_utility = self.opponent_model.getUtility(bid)
227 if verbose:
228 self.getReporter().log(logging.INFO,
229 'Own utility ' + str(own_utility) + ' Opponent utility ' + str(opponent_utility))
230
231 return own_utility, opponent_utility
232
233 @staticmethod
234 def _evaluate_utilities(utilities: tuple[float, float]) -> float:
235 ratio = 0.6
236 return ratio * float(utilities[0]) + (1-ratio) * float(utilities[1])
237
238 def _recognize_move(self):
239 """
240 Evaluates the last x bids received where x is equal to the window size.
241 It then calculates of this window was a conceding window or a non conceding window by calculating the delta
242 It adds the delta to a list and this list will then be used to adjust our concession rate.
243 """
244
245 if len(self.received_bids) < self._window_size:
246 return
247
248 start = len(self.received_bids) - self._window_size
249 half = len(self.received_bids) - int(self._window_size / 2)
250
251 # function to map the bids to the estimated utility
252
253 first_half = list(map(lambda x: self.opponent_model.getUtility(x), self.received_bids[start:half]))
254 second_half = list(map(lambda x: self.opponent_model.getUtility(x), self.received_bids[half:]))
255
256 # now we calculate the average utility
257 first_avg = sum(first_half) / len(first_half)
258 second_avg = sum(second_half) / len(second_half)
259
260 # It takes into account that an opponent model will naturally be a little conceding over time
261 # hence the bias correction
262 delta = float(second_avg - first_avg) + self._bias_correction
263 self.opponent_delta.append(delta)
Note: See TracBrowser for help on using the repository browser.