[75] | 1 | import logging
|
---|
| 2 | import time
|
---|
| 3 | from datetime import datetime
|
---|
| 4 | from decimal import Decimal
|
---|
| 5 | from random import randrange
|
---|
| 6 | from typing import cast
|
---|
| 7 |
|
---|
| 8 | from geniusweb.actions.Accept import Accept
|
---|
| 9 | from geniusweb.actions.Action import Action
|
---|
| 10 | from geniusweb.actions.Offer import Offer
|
---|
| 11 | from geniusweb.bidspace.AllBidsList import AllBidsList
|
---|
| 12 | from geniusweb.inform.ActionDone import ActionDone
|
---|
| 13 | from geniusweb.inform.Finished import Finished
|
---|
| 14 | from geniusweb.inform.Inform import Inform
|
---|
| 15 | from geniusweb.inform.Settings import Settings
|
---|
| 16 | from geniusweb.inform.YourTurn import YourTurn
|
---|
| 17 | from geniusweb.issuevalue.Bid import Bid
|
---|
| 18 | from geniusweb.party.Capabilities import Capabilities
|
---|
| 19 | from geniusweb.party.DefaultParty import DefaultParty
|
---|
| 20 | from geniusweb.profileconnection.ProfileConnectionFactory import (
|
---|
| 21 | ProfileConnectionFactory,
|
---|
| 22 | )
|
---|
| 23 | from geniusweb.progress.Progress import Progress
|
---|
| 24 | from geniusweb.progress.ProgressRounds import ProgressRounds
|
---|
| 25 | from tudelft_utilities_logging.Reporter import Reporter
|
---|
| 26 |
|
---|
| 27 | NUM_OF_MOVES_FOR_EXPLORE = 800
|
---|
| 28 |
|
---|
| 29 |
|
---|
| 30 | class 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]]
|
---|