source: ANL2022/super_agent/super_agent.py

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

#6 added ANAC2022 parties

File size: 23.1 KB
Line 
1import logging
2import math
3import os.path
4import random
5import pickle
6from time import time
7from typing import cast
8from collections import defaultdict
9from typing import List
10from geniusweb.profileconnection.ProfileInterface import ProfileInterface
11from geniusweb.actions.Accept import Accept
12from geniusweb.actions.Action import Action
13from geniusweb.actions.Offer import Offer
14from geniusweb.actions.PartyId import PartyId
15from geniusweb.inform.ActionDone import ActionDone
16from geniusweb.inform.Finished import Finished
17from geniusweb.inform.Inform import Inform
18from geniusweb.inform.Settings import Settings
19from geniusweb.inform.YourTurn import YourTurn
20from geniusweb.issuevalue.Bid import Bid
21from geniusweb.bidspace.AllBidsList import AllBidsList
22from geniusweb.party.Capabilities import Capabilities
23from geniusweb.party.DefaultParty import DefaultParty
24from geniusweb.utils import val
25from geniusweb.issuevalue.Value import Value
26from geniusweb.issuevalue import DiscreteValue
27from geniusweb.issuevalue import NumberValue
28from geniusweb.inform.Agreements import Agreements
29from geniusweb.references.Parameters import Parameters
30from geniusweb.profileconnection.ProfileConnectionFactory import (
31 ProfileConnectionFactory,
32)
33from geniusweb.progress.ProgressRounds import ProgressRounds
34
35from .utils.utils import get_ms_current_time
36from .utils.pair import Pair
37from .utils.persistent_data import PersistentData
38from .utils.negotiation_data import NegotiationData
39
40
41class SuperAgent(DefaultParty):
42 """
43 A Super party that places empty bids because it can't download the profile,
44 and accepts the first incoming offer.
45 """
46
47 def __init__(self):
48 super().__init__()
49 self.getReporter().log(logging.INFO, "party is initialized")
50 self._last_received_bid: Bid = None
51 self._me = None
52 self._profile_interface: ProfileInterface = None
53 self._progress = None
54 self._protocol = None
55 self._parameters: Parameters = None
56 self._utility_space = None
57 self._domain = None
58 self._settings: Settings = None
59
60 self._best_offer_bid: Bid = None
61 self._profile = None
62 self._persistent_path: str = None
63 self._persistent_data: PersistentData = None
64 # NeogtiationData
65 self._negotiation_data: NegotiationData = None
66 self._data_paths_raw: List[str] = []
67 # self._data_paths: List[str] = []
68 self._negotiation_data_paths: List[str] = []
69 self._opponent_name = None
70 self._freq_map = defaultdict()
71 self._avg_utility = 0.95
72 self._std_utility = 0.15
73 self._util_threshold = 0.95
74 self._min_utility = 0.6
75 self.default_alpha = 10.7
76 self.alpha = self.default_alpha
77 self.t_split = 40
78 self.op_counter = [0] * self.t_split
79 self.op_sum = [0.0] * self.t_split
80 self.op_threshold = [0.0] * self.t_split
81 self.t_phase = 0.2
82 self.t_social_welfare = 0.997
83
84 self._max_bid_space_iteration = 50000
85 self._optimal_bid: Bid = None
86 self._all_bid_list: AllBidsList = None
87 self._sorted_bid_list: List = None
88 self._len_sorted_bid_list: int = 0
89 self._storage_dir: str = None
90
91 def create_empty_negotiation_data(self, opponent_name):
92 self._negotiation_data = NegotiationData(opponent_name=opponent_name)
93
94 def initialize_negotiation_data(self, opponent_name):
95 self._negotiation_data_paths = []
96 data_path_raw = os.path.join(self._storage_dir, f"negotiation_data_{opponent_name}.log")
97 self._negotiation_data_paths.append(data_path_raw)
98 if self._negotiation_data is not None and os.path.exists(data_path_raw):
99 # print("non-empty NegotiationData")
100 with open(data_path_raw, "rb") as negotiation_data_file:
101 self._negotiation_data: NegotiationData = pickle.load(negotiation_data_file)
102 else:
103 # print("empty NegotiationData")
104 self.create_empty_negotiation_data(opponent_name=opponent_name)
105
106 def initialize_persistent_data(self, opponent_name):
107 self._persistent_path = os.path.join(self._storage_dir, f"persistent_data_{opponent_name}.log")
108 if self._persistent_path is not None and os.path.exists(self._persistent_path):
109 # json load
110 # print("non-empty PersistentData")
111 with open(self._persistent_path, "rb") as persistent_file:
112 self._persistent_data: PersistentData = pickle.load(persistent_file)
113 self._avg_utility = self._persistent_data.get_avg_utility()
114 self._std_utility = self._persistent_data.get_std_utility()
115 else:
116 self._persistent_data: PersistentData = PersistentData()
117
118 def first_better_then(self, utility):
119 idx = None
120 try:
121 idx = next(len(self._sorted_bid_list) - 1 - x for x, val in enumerate(self._sorted_bid_list[-1::-1]) if
122 self.calc_utility(val) > utility)
123 except StopIteration:
124 pass
125 finally:
126 return idx
127
128 def last_bids(self, good_bid: int):
129 # this session's max utility got
130 if self._progress.get(get_ms_current_time()) <= 0.97 and self.is_good(self._best_offer_bid):
131 return self._best_offer_bid
132 # all session's max utility got
133 avg_max_util = self._persistent_data.get_avg_max_utility(self._opponent_name)
134 if not avg_max_util:
135 return self._best_offer_bid
136 if self._progress.get(get_ms_current_time()) <= 0.99:
137 idx = self.first_better_then(avg_max_util)
138 if idx != None:
139 self.getReporter().log(logging.INFO, "avg_max_util: {0}, bid_utility: {1}".format(avg_max_util,
140 self._utility_space.getUtility(
141 self._sorted_bid_list[
142 idx])))
143 if self.is_good(self._sorted_bid_list[idx]):
144 return self._sorted_bid_list[idx]
145
146 # last try we give him the best possible suggestion we have
147 if good_bid == 0:
148 bid = self._optimal_bid
149 else:
150 bid = max(self._sorted_bid_list[0:good_bid], key=self.calc_op_value)
151
152 self.getReporter().log(logging.INFO, "chosen bid utility: {}".format(self._utility_space.getUtility(bid)))
153 return bid
154
155 @classmethod
156 def parse_opponent_name(cls, full_opponent_name):
157 agent_index = full_opponent_name.rindex("_")
158 if agent_index != -1:
159 return full_opponent_name[:agent_index]
160 return None
161
162 def initialize_storage(self, opponent_name):
163 if self._storage_dir is not None:
164 self.initialize_persistent_data(opponent_name=opponent_name)
165 self.initialize_negotiation_data(opponent_name=opponent_name)
166 else:
167 self.create_empty_negotiation_data(opponent_name=opponent_name)
168 self._persistent_data: PersistentData = PersistentData()
169
170 # Override
171 def notifyChange(self, info: Inform):
172 # self.getReporter().log(logging.INFO, "received info:" + str(info))
173 if isinstance(info, Settings):
174 # self.getReporter().log(logging.WARNING, "SETTINGS")
175 settings: Settings = cast(Settings, info)
176 self._settings = settings
177 self._me: PartyId = settings.getID()
178 self._progress = settings.getProgress()
179 self._protocol = str(settings.getProtocol().getURI())
180 self._parameters = settings.getParameters()
181 if "storage_dir" in self._parameters.getParameters():
182 self.getReporter().log(logging.INFO, "storage_dir is on parameters")
183 self._storage_dir = self._parameters.get("storage_dir")
184
185 try:
186 self._profile_interface: ProfileInterface = ProfileConnectionFactory.create(
187 settings.getProfile().getURI(), self.getReporter()
188 )
189 self._profile = self._profile_interface.getProfile()
190 self._domain = self._profile.getDomain()
191
192 if self._freq_map is None:
193 self._freq_map = defaultdict()
194 else:
195 self._freq_map.clear()
196
197 issues = self._domain.getIssues()
198 for issue in issues:
199 p = Pair()
200 vs = self._domain.getValues(issue)
201 if isinstance(vs.get(0), DiscreteValue.DiscreteValue):
202 p.value_type = 0
203 elif isinstance(vs.get(0), NumberValue.NumberValue):
204 p.value_type = 1
205 for v in vs:
206 vstr = self.value_to_str(v, p)
207 p.vlist[vstr] = 0
208 self._freq_map[issue] = p
209
210 self._utility_space = self._profile_interface.getProfile()
211 self._all_bid_list: AllBidsList = AllBidsList(domain=self._domain)
212 self._sorted_bid_list = sorted(AllBidsList(domain=self._domain),
213 key=self._utility_space.getUtility, reverse=True)
214 self._len_sorted_bid_list = len(self._sorted_bid_list)
215 # after sort of bid list the optimal bid is in the first element
216 self._optimal_bid = self._sorted_bid_list[0]
217
218 except Exception as e:
219 print("error in settings:{}", e)
220 self.getReporter().log(logging.WARNING, "Error in {}".format(str(e)))
221
222 elif isinstance(info, ActionDone):
223 # self.getReporter().log(logging.WARNING, "ActionDone")
224 # TODO: initalizie with negotiaiondata
225 action: Action = cast(ActionDone, info).getAction()
226 if self._me is not None and self._me != action.getActor():
227 opponent_name = self.parse_opponent_name(full_opponent_name=action.getActor().getName())
228 if self._opponent_name is None and opponent_name is not None:
229 self.initialize_storage(opponent_name)
230 # which means index found
231 self._opponent_name = opponent_name
232 self._negotiation_data.set_opponent_name(self._opponent_name)
233
234 self.op_threshold = self._persistent_data.get_smooth_threshold_over_time(self._opponent_name
235 )
236 if self.op_threshold is not None:
237 for i in range(1, self.t_split):
238 self.op_threshold[i] = self.op_threshold[i] if self.op_threshold[i] > 0 else \
239 self.op_threshold[i - 1]
240 self.alpha = self._persistent_data.get_opponent_alpha(self._opponent_name)
241 self.alpha = self.alpha if self.alpha > 0.0 else self.default_alpha
242 self.process_action(action)
243
244 elif isinstance(info, YourTurn):
245 # This is a super party
246 # self.getReporter().log(logging.WARNING, "YourTurn")
247 if isinstance(self._progress, ProgressRounds):
248 self._progress = self._progress.advance()
249 # self.initialize_storage(self._opponent_name)
250 action = self._my_turn()
251 val(self.getConnection()).send(action)
252
253 elif isinstance(info, Finished):
254 # TODO:: handle NEGOTIATIONDATA
255 finished_info = cast(Finished, info)
256 agreements: Agreements = finished_info.getAgreements()
257 self.process_agreements(agreements)
258 self.learn()
259 if self._negotiation_data_paths is not None and len(
260 self._negotiation_data_paths) > 0 and self._negotiation_data is not None:
261 for negotiation_path in self._negotiation_data_paths:
262 try:
263 with open(negotiation_path, "wb") as negotiation_file:
264 pickle.dump(self._negotiation_data, negotiation_file)
265 except Exception as e:
266 self.getReporter().log(logging.WARNING, "Error in {}".format(str(e)))
267 self.terminate()
268 else:
269 self.getReporter().log(
270 logging.WARNING, "Ignoring unknown info " + str(info)
271 )
272
273 # Override
274 def getCapabilities(self) -> Capabilities:
275 return Capabilities(
276 set(["SAOP", "Learn"]),
277 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
278 )
279
280 # Override
281 def getDescription(self) -> str:
282 return "This is a party of ANL 2022. It can handle the Learn protocol and learns simple characteristics of the opponent."
283
284 # Override
285 def terminate(self):
286 self.getReporter().log(logging.INFO, "party is terminating:")
287 super().terminate()
288 if self._profile_interface is not None:
289 self._profile_interface.close()
290
291 def value_to_str(self, v: Value, p: Pair) -> str:
292 v_str = ""
293 if p.value_type == 0:
294 v_str = str(cast(DiscreteValue, v).getValue())
295 elif p.value_type == 1:
296 v_str = str(cast(NumberValue, v).getValue())
297
298 if v_str == "":
299 self.getReporter().log(logging.WARNING, "Warning: Value wasn't found")
300 return v_str
301
302 def process_action(self, action: Action):
303 if isinstance(action, Offer):
304 self._last_received_bid = cast(Offer, action).getBid()
305 self.update_freq_map(self._last_received_bid)
306 util_value = float(self._utility_space.getUtility(self._last_received_bid))
307 self._negotiation_data.add_bid_util(util_value)
308
309 def update_freq_map(self, bid: Bid):
310 if bid is not None:
311 issues = bid.getIssues()
312 for issue in issues:
313 p: Pair = self._freq_map[issue]
314 v: Value = bid.getValue(issue)
315 vs: str = self.value_to_str(v, p)
316 p.vlist[vs] = p.vlist[vs] + 1
317
318 def calc_op_value(self, bid: Bid):
319 value: float = 0
320 issues: set[str] = bid.getIssues()
321 val_util: list[float] = [0] * len(issues)
322 is_weight: list[float] = [0] * len(issues)
323 k: int = 0
324 for issue in issues:
325 p: Pair = self._freq_map[issue]
326 v: Value = bid.getValue(issue)
327 vs: str = self.value_to_str(v=v, p=p)
328 sum_of_values = 0
329 max_value = 1
330 for vString in p.vlist.keys():
331 sum_of_values = sum_of_values + p.vlist.get(vString)
332 max_value = max(max_value, p.vlist.get(vString))
333 val_util[k] = float(p.vlist.get(vs)) / max_value
334 mean = sum_of_values / len(p.vlist)
335 for v_string in p.vlist.keys():
336 is_weight[k] = is_weight[k] + math.pow(p.vlist.get(v_string) - mean, 2)
337 is_weight[k] = 1 / math.sqrt((is_weight[k] + 0.1) / len(p.vlist))
338 k = k + 1
339 sum_of_weight = 0
340 for k in range(len(issues)):
341 value = value + val_util[k] * is_weight[k]
342 sum_of_weight = sum_of_weight + is_weight[k]
343 return value / sum_of_weight
344
345 def is_op_good(self, bid: Bid):
346 if bid is None:
347 return False
348 value = self.calc_op_value(bid=bid)
349 index = int(
350 ((self.t_split - 1) / (1 - self.t_phase) * (self._progress.get(get_ms_current_time()) - self.t_phase)))
351 op_threshold = max(1 - 2 * self.op_threshold[index], 0.2) if self.op_threshold is not None else 0.6
352 return value > op_threshold
353 # index = (int)((t_split - 1) / (1 - t_phase) * (progress.get(System.currentTimeMillis()) - t_phase));
354
355 def is_last_turn(self):
356 return self._progress.get(time() * 1000) > 0.997
357
358 def is_near_negotiation_end(self):
359 return self._progress.get(time() * 1000) > self.t_phase
360
361 def is_social_welfare_time(self):
362 return self._progress.get(time() * 1000) > self.t_social_welfare
363
364 def calc_utility(self, bid):
365 # get utility from utility space
366 return self._utility_space.getUtility(bid)
367
368 def calc_social_welfare(self, bid: Bid):
369 return 0.8 * float(self.calc_utility(bid)) + math.fabs(1 - 0.8) * float(self.calc_op_value(bid))
370
371 def cmp_social_welfare(self, first_bid, second_bid):
372 return self.calc_social_welfare(first_bid) >= self.calc_social_welfare(second_bid)
373
374 def is_good(self, bid):
375 if bid is None:
376 return False
377 max_value = 0.95 if self._optimal_bid is None else 0.95 * float(self.calc_utility(self._optimal_bid))
378 avg_max_utility = self._persistent_data.get_avg_max_utility(self._opponent_name) \
379 if self._persistent_data._known_opponent(self._opponent_name) \
380 else self._avg_utility
381 self._util_threshold = max_value - (
382 max_value - 0.55 * self._avg_utility - 0.4 * avg_max_utility + 0.5 * pow(self._std_utility, 2)) * \
383 (math.exp(self.alpha * self._progress.get(get_ms_current_time())) - 1) / (math.exp(
384 self.alpha) - 1)
385 if self._util_threshold < self._min_utility:
386 self._util_threshold = self._min_utility
387 return float(self.calc_utility(bid)) >= self._util_threshold
388
389 def first_is_good_idx(self):
390 for i in range(len(self._sorted_bid_list)):
391 if not self.is_good(self._sorted_bid_list[i]):
392 return i
393 return len(self._sorted_bid_list) - 1
394
395 def on_negotiation_near_end(self):
396 slice_idx = self.first_is_good_idx()
397 end_slice = int(min(slice_idx + 0.005 * self._len_sorted_bid_list - 1, self._len_sorted_bid_list - 1))
398 idx = random.randint(0, slice_idx)
399
400 if self._progress.get(get_ms_current_time()) >= 0.95:
401 bid = self.last_bids(idx)
402 if self.calc_utility(bid) <= 0.5:
403 bid = self._optimal_bid
404 return bid
405 # if self._progress.get(get_ms_current_time()) > 0.992 and self.is_good(self._best_offer_bid):
406 # return self._best_offer_bid
407 return self._sorted_bid_list[idx]
408
409 def on_negotiation_continues(self):
410 bid: Bid = None
411
412 slice_idx = self.first_is_good_idx()
413 end_slice = int(min(slice_idx + 0.005 * self._len_sorted_bid_list - 1, self._len_sorted_bid_list - 1))
414 for i in range(slice_idx, 0, -1):
415 tmp_bid = self._sorted_bid_list[i]
416 if tmp_bid == self._optimal_bid or self.is_op_good(tmp_bid):
417 bid = tmp_bid
418 break
419 if self._progress.get(get_ms_current_time()) > 0.992 and self.is_good(self._best_offer_bid):
420 bid = self._best_offer_bid
421 if bid is None or not self.is_good(bid):
422 idx = random.randint(0, slice_idx)
423 bid = self._sorted_bid_list[idx]
424 return bid
425
426 def cmp_utility(self, first_bid, second_bid):
427 # return 1 if first_bid with higher utility, 0 else
428 return self._utility_space.getUtility(first_bid) > self._utility_space.getUtility(second_bid)
429
430 def _find_bid(self):
431 bid: Bid = None
432 if self._best_offer_bid is None:
433 self._best_offer_bid = self._last_received_bid
434 elif self.cmp_utility(self._last_received_bid, self._best_offer_bid):
435 self._best_offer_bid = self._last_received_bid
436 # if self.is_social_welfare_time():
437 # self.on_negotiation_social_welfare()
438 if self.is_near_negotiation_end():
439 bid = self.on_negotiation_near_end()
440 else:
441 bid = self.on_negotiation_continues()
442
443 action: Offer = Offer(self._me, bid)
444 return action
445
446 def _my_turn(self):
447 # save average of the last avgSplit offers (only when frequency table is stabilized)
448 if self._opponent_name is None:
449 return Offer(self._me, self._optimal_bid)
450
451 if self.is_near_negotiation_end():
452 index = int(
453 (self.t_split - 1) / (1 - self.t_phase) * (self._progress.get(get_ms_current_time()) - self.t_phase))
454 self.op_sum[index] += self.calc_op_value(self._last_received_bid)
455 self.op_counter[index] += 1
456 if self.is_good(self._last_received_bid) or (self.is_last_turn() and self.calc_utility(self._last_received_bid)>=0.5):
457 # if the last bid is good - accept it.
458 action = Accept(self._me, self._last_received_bid)
459 else:
460 action = self._find_bid()
461 return action
462
463 def learn(self):
464 self.getReporter().log(logging.INFO, "party is learning")
465 # probably have to shift to self._negotiation_data_paths
466 for path in self._negotiation_data_paths:
467 try:
468 with open(path, "rb") as f:
469 nego_data = pickle.load(f)
470 self._persistent_data.update(nego_data)
471 except Exception as e:
472 print("error in learn function - persistent data update, error:{}", str(e))
473
474 try:
475 with open(self._persistent_path, "wb") as pers_file:
476 pickle.dump(self._persistent_data, pers_file)
477 except Exception as e:
478 print("error in persistent path dump:{}", str(e))
479
480 def process_agreements(self, agreements: Agreements):
481 # Check if we reached an agreement (walking away or passing the deadline
482 # results in no agreement)
483 self.getReporter().log(logging.INFO, "Length of agreements: {} :{}".format(len(agreements.getMap().items()),
484 agreements.getMap()))
485 if len(agreements.getMap().items()) > 0:
486 # Get the bid that is agreed upon and add it's value to our negotiation data
487 agreement: Bid = agreements.getMap().values().__iter__().__next__()
488 self._negotiation_data.add_agreement_util(float(self.calc_utility(agreement)))
489 self._negotiation_data.set_opponent_util(self.calc_op_value(agreement))
490 self.getReporter().log(logging.INFO, "Agreement in time: {} percent".format(self._progress.get(get_ms_current_time())))
491 self.getReporter().log(logging.INFO, "MY OWN THRESHOLD: {}".format(self._util_threshold))
492 self.getReporter().log(logging.INFO, "MY OWN UTIL:{}".format(self.calc_utility(agreement)))
493 self.getReporter().log(logging.INFO, "EXP OPPONENT UTIL:{}".format(self.calc_op_value(agreement)))
494 else:
495 if self._best_offer_bid is not None:
496 self._negotiation_data.add_agreement_util(float(self.calc_utility(self._best_offer_bid)))
497 self.getReporter().log(logging.INFO,
498 "!!!!!!!!!!!!!! NO AGREEMENT !!!!!!!!!!!!!!! /// MY THRESHOLD: {}".format(
499 self._util_threshold))
500
501 self.getReporter().log(logging.INFO, "TIME OF AGREEMENT: {}".format(self._progress.get(get_ms_current_time())))
502 # update the opponent offers map, regardless of achieving agreement or not
503 try:
504 self._negotiation_data.update_opponent_offers(self.op_sum, self.op_counter)
505 except Exception as e:
506 self.getReporter().log(logging.INFO, "Error in process_agreements,{}".format(str(e)))
Note: See TracBrowser for help on using the repository browser.