source: ANL2022/agent4410/agent_4410.py

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

#6 added ANAC2022 parties

File size: 10.2 KB
Line 
1import logging
2import time
3from datetime import datetime
4from decimal import Decimal
5from random import randrange
6from typing import cast
7
8from geniusweb.actions.Accept import Accept
9from geniusweb.actions.Action import Action
10from geniusweb.actions.Offer import Offer
11from geniusweb.bidspace.AllBidsList import AllBidsList
12from geniusweb.inform.ActionDone import ActionDone
13from geniusweb.inform.Finished import Finished
14from geniusweb.inform.Inform import Inform
15from geniusweb.inform.Settings import Settings
16from geniusweb.inform.YourTurn import YourTurn
17from geniusweb.issuevalue.Bid import Bid
18from geniusweb.party.Capabilities import Capabilities
19from geniusweb.party.DefaultParty import DefaultParty
20from geniusweb.profileconnection.ProfileConnectionFactory import (
21 ProfileConnectionFactory,
22)
23from geniusweb.progress.Progress import Progress
24from geniusweb.progress.ProgressRounds import ProgressRounds
25from tudelft_utilities_logging.Reporter import Reporter
26
27NUM_OF_MOVES_FOR_EXPLORE = 800
28
29
30class Agent4410(DefaultParty):
31 _bid_to_utility = {}
32 _sorted_bids = []
33 _top_10_present_utility = -1
34 _top_5_present_utility = -1
35 _explore_state = True
36 _received_issues_count = {}
37 _num_of_counter_bids = 0
38 _last_received_bid: Bid = None
39 _my_weights = None
40 _opponent_weights = None
41 _precent_of_bids = 0.02
42
43 def __init__(self, reporter: Reporter = None):
44 super().__init__(reporter)
45
46 def notifyChange(self, info: Inform):
47 # a Settings message is the first message that will be send to your
48 # agent containing all the information about the negotiation session.
49
50 if isinstance(info, Settings):
51 self._settings: Settings = cast(Settings, info)
52 self._me = self._settings.getID()
53
54 # progress towards the deadline has to be tracked manually through the use of the Progress object
55 self._progress: Progress = self._settings.getProgress()
56
57 # the profile contains the preferences of the agent over the domain
58 self._profile = ProfileConnectionFactory.create(
59 info.getProfile().getURI(), self.getReporter()
60 )
61
62 start = time.time()
63 self._generate_run_data()
64 end = time.time()
65 self.getReporter().log(logging.WARNING, f"Init took: {end - start} secs")
66 self.getReporter().log(logging.WARNING, "")
67 # ActionDone is an action send by an opponent (an offer or an accept)
68 elif isinstance(info, ActionDone):
69 action: Action = cast(ActionDone, info).getAction()
70 # if it is an offer, set the last received bid
71 if isinstance(action, Offer):
72 self._last_received_bid = cast(Offer, action).getBid()
73 # YourTurn notifies you that it is your turn to act
74 elif isinstance(info, YourTurn):
75 # execute a turn
76 action = self._myTurn()
77
78 if isinstance(self._progress, ProgressRounds):
79 self._progress = self._progress.advance()
80
81 self.getConnection().send(action)
82 elif isinstance(info, Finished):
83 # terminate the agent MUST BE CALLED
84 self.terminate()
85 else:
86 self.getReporter().log(
87 logging.WARNING, "Ignoring unknown info " + str(info)
88 )
89
90 # lets the geniusweb system know what settings this agent can handle
91 # leave it as it is for this competition
92 def getCapabilities(self) -> Capabilities:
93 return Capabilities(
94 set(["SAOP"]),
95 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
96 )
97
98 # terminates the agent and its connections
99 # leave it as it is for this competition
100 def terminate(self):
101 self.getReporter().log(logging.INFO, "party is terminating:")
102 super().terminate()
103 if self._profile is not None:
104 self._profile.close()
105 self._profile = None
106
107 # give a description of your agent
108 def getDescription(self) -> str:
109 return "Agent 4410"
110
111 def _update_precent_of_bids(self):
112 curr = datetime.now()
113 terminationTime = self._progress.getTerminationTime()
114 diff = terminationTime - curr
115 diff_in_milli_secs = diff.total_seconds() * 1000
116
117 remaining_time_share = diff_in_milli_secs / self._progress._duration
118
119 if remaining_time_share <= 1 / 12:
120 self._precent_of_bids = 0.05
121 elif remaining_time_share <= 1 / 3:
122 self._precent_of_bids = 0.08
123 elif remaining_time_share <= 1 / 2:
124 self._precent_of_bids = 0.05
125 elif remaining_time_share <= 2 / 3:
126 self._precent_of_bids = 0.03
127 else:
128 self._precent_of_bids = 0.01
129
130 def _myTurn(self):
131 profile = self._profile.getProfile()
132
133 self._update_precent_of_bids()
134
135 bid_size = len(self._sorted_bids)
136 top_5_present_index = round(bid_size * self._precent_of_bids)
137
138 top_5_present_utility = self._bid_to_utility[self._sorted_bids[top_5_present_index]]
139
140 if self._last_received_bid:
141 last_offer_utility = profile.getUtility(self._last_received_bid)
142 if last_offer_utility >= top_5_present_utility:
143 # This is our top 5% - accepting it!
144 self.getReporter().log(logging.INFO, f"Accepting offer with utility: {last_offer_utility}")
145
146 return Accept(self._me, self._last_received_bid)
147
148 if self._explore_state:
149 return self._explore()
150 else:
151 return self._exploitation()
152
153 def _explore(self):
154 if self._last_received_bid:
155 self._update_response_tracking()
156
157 # Pick a random bid from the 10% and offer it
158 bid_index = randrange(round(len(self._sorted_bids) * self._precent_of_bids))
159 next_bid = self._sorted_bids[bid_index]
160
161 # Update state
162 self._explore_state = self._num_of_counter_bids <= NUM_OF_MOVES_FOR_EXPLORE
163
164 if not self._explore_state:
165 self.getReporter().log(logging.INFO, "Changing state to exploit!")
166
167 return Offer(self._me, next_bid)
168
169 def _update_response_tracking(self):
170 last_bid_issue_values = self._last_received_bid.getIssueValues()
171
172 # Track number of values per issues
173 for issue in last_bid_issue_values:
174 value = last_bid_issue_values[issue]
175 if self._received_issues_count.get(issue) is None:
176 self._received_issues_count[issue] = {}
177
178 if self._received_issues_count[issue].get(value) is None:
179 self._received_issues_count[issue][value] = 0
180
181 self._received_issues_count[issue][value] += 1
182 self._num_of_counter_bids += 1
183
184 def _exploitation(self):
185 # Keep updating our data
186 if self._last_received_bid:
187 self._update_response_tracking()
188
189 return self._recalculate_our_weights()
190
191 def _recalculate_our_weights(self, ):
192 for bid in self._sorted_bids_to_utility.keys():
193 for issue in bid.getIssues():
194 if bid.getValue(issue) in self._received_issues_count[issue].keys():
195 weight = Decimal(
196 self._received_issues_count[issue][bid.getValue(issue)] / self._num_of_counter_bids)
197 if self._received_issues_count[issue][bid.getValue(issue)] < self._num_of_counter_bids / 2:
198 self._sorted_bids_to_utility[bid] += Decimal(0.02) * weight
199 else:
200 self._sorted_bids_to_utility[bid] += Decimal(0.005) * weight
201 else:
202 self._sorted_bids_to_utility[bid] -= Decimal(0.001)
203
204 self._sorted_bids = sorted(self._sorted_bids_to_utility, key=lambda bid: self._sorted_bids_to_utility[bid],
205 reverse=True)
206 # TODO: Smart randomaization by time left (maybe add sleep if we have lots of time (to scare timebase opponents))
207 return Offer(self._me, self._sorted_bids[randrange(round(len(self._sorted_bids) * self._precent_of_bids))])
208
209 def _load_opponent_weights(self):
210 self._opponent_weights = {}
211
212 # Calculation of the weights
213 for issue in self._received_issues_count:
214 self._opponent_weights[issue] = {}
215 issue_values = self._received_issues_count[issue]
216
217 for value in issue_values:
218 self._opponent_weights[issue][value] = issue_values[value] / self._num_of_counter_bids
219
220 def _load_my_weights(self):
221 self._my_weights = {}
222
223 # Change to The TOP NUM_OF_MOVES_FOR_EXPLORE
224 num_of_items = round(len(self._sorted_bids) / 10)
225
226 # Summing the top 10% items
227 for i in range(num_of_items):
228 bid = self._sorted_bids[i].getIssueValues()
229
230 # Takes all our bids and sums up the ocurrences of each issue
231 for issue in bid:
232 value = bid[issue]
233 if self._my_weights.get(issue) is None:
234 self._my_weights[issue] = {}
235
236 if self._my_weights[issue].get(value) is None:
237 self._my_weights[issue][value] = 0
238
239 self._my_weights[issue][value] += 1
240
241 # Calculation of the weights
242 for issue in self._my_weights:
243 issue_values = self._my_weights[issue]
244
245 for value in issue_values:
246 self._my_weights[issue][value] /= num_of_items
247
248 def _generate_run_data(self):
249 profile = self._profile.getProfile()
250 domain = self._profile.getProfile().getDomain()
251
252 all_bids = AllBidsList(domain)
253 self._bid_to_utility = {bid: profile.getUtility(bid) for bid in all_bids}
254
255 # For Future uses
256 self._sorted_bids_to_utility = {k: v for k, v in
257 sorted(self._bid_to_utility.items(), key=lambda item: item[1], reverse=True)}
258
259 self._sorted_bids = sorted(self._bid_to_utility, key=lambda bid: self._bid_to_utility[bid], reverse=True)
260
261 bid_size = len(self._sorted_bids)
262 top_10_present_index = round(bid_size / 100 * 10)
263 top_5_present_index = round(bid_size / 100 * 5)
264
265 self._top_10_present_utility = self._bid_to_utility[self._sorted_bids[top_10_present_index]]
266 self._top_5_present_utility = self._bid_to_utility[self._sorted_bids[top_5_present_index]]
Note: See TracBrowser for help on using the repository browser.