[74] | 1 | import decimal
|
---|
| 2 | import logging
|
---|
| 3 | import time
|
---|
| 4 | import operator
|
---|
| 5 | from random import randint
|
---|
| 6 | from typing import cast, Set
|
---|
| 7 | from bisect import bisect_left
|
---|
| 8 |
|
---|
| 9 | from geniusweb.actions.Accept import Accept
|
---|
| 10 | from geniusweb.actions.Action import Action
|
---|
| 11 | from geniusweb.actions.Offer import Offer
|
---|
| 12 | from geniusweb.actions.PartyId import PartyId
|
---|
| 13 | from geniusweb.bidspace.AllBidsList import AllBidsList
|
---|
| 14 | from geniusweb.inform.ActionDone import ActionDone
|
---|
| 15 | from geniusweb.inform.Finished import Finished
|
---|
| 16 | from geniusweb.inform.Inform import Inform
|
---|
| 17 | from geniusweb.inform.Settings import Settings
|
---|
| 18 | from geniusweb.inform.YourTurn import YourTurn
|
---|
| 19 | from geniusweb.issuevalue.Bid import Bid
|
---|
| 20 | from geniusweb.issuevalue.Domain import Domain
|
---|
| 21 | from geniusweb.issuevalue.Value import Value
|
---|
| 22 | from geniusweb.issuevalue.ValueSet import ValueSet
|
---|
| 23 | from geniusweb.party.Capabilities import Capabilities
|
---|
| 24 | from geniusweb.party.DefaultParty import DefaultParty
|
---|
| 25 | from geniusweb.profile.FullOrdering import FullOrdering
|
---|
| 26 | from geniusweb.profile.PartialOrdering import PartialOrdering
|
---|
| 27 | from geniusweb.profile.utilityspace.DiscreteValueSetUtilities import DiscreteValueSetUtilities
|
---|
| 28 | from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
|
---|
| 29 | from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import LinearAdditiveUtilitySpace
|
---|
| 30 | from geniusweb.profile.utilityspace.NumberValueSetUtilities import NumberValueSetUtilities
|
---|
| 31 | from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
|
---|
| 32 | from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
|
---|
| 33 | from geniusweb.profileconnection.ProfileConnectionFactory import (
|
---|
| 34 | ProfileConnectionFactory,
|
---|
| 35 | )
|
---|
| 36 | from geniusweb.progress.ProgressRounds import ProgressRounds
|
---|
| 37 | from geniusweb.references.ProfileRef import ProfileRef
|
---|
| 38 | from tudelft_utilities_logging.Reporter import Reporter
|
---|
| 39 |
|
---|
| 40 |
|
---|
| 41 | class Agent7(DefaultParty):
|
---|
| 42 | """
|
---|
| 43 | Template agent that offers random bids until a bid with sufficient utility is offered.
|
---|
| 44 | """
|
---|
| 45 |
|
---|
| 46 | listOfUtil = [];
|
---|
| 47 | utilBidMap = dict();
|
---|
| 48 |
|
---|
| 49 | def __init__(self, reporter: Reporter = None):
|
---|
| 50 | super().__init__(reporter)
|
---|
| 51 | self.getReporter().log(logging.INFO, "party is initialized")
|
---|
| 52 | self._profile: ProfileRef = None
|
---|
| 53 | self._last_received_bid: Bid = None
|
---|
| 54 | self._last_offer: Bid = None
|
---|
| 55 |
|
---|
| 56 | self._all_offers: list = []
|
---|
| 57 | self._progress_offset = 0
|
---|
| 58 |
|
---|
| 59 | self._is_trading = False
|
---|
| 60 | self._trade_offers = []
|
---|
| 61 | self._trade_offer_index = 0
|
---|
| 62 |
|
---|
| 63 | def notifyChange(self, info: Inform):
|
---|
| 64 | """This is the entry point of all interaction with your agent after is has been initialised.
|
---|
| 65 |
|
---|
| 66 | Args:
|
---|
| 67 | info (Inform): Contains either a request for action or information.
|
---|
| 68 | """
|
---|
| 69 |
|
---|
| 70 | # a Settings message is the first message that will be send to your
|
---|
| 71 | # agent containing all the information about the negotiation session.
|
---|
| 72 | if isinstance(info, Settings):
|
---|
| 73 | self._settings: Settings = cast(Settings, info)
|
---|
| 74 | self._me = self._settings.getID()
|
---|
| 75 |
|
---|
| 76 | # progress towards the deadline has to be tracked manually through the use of the Progress object
|
---|
| 77 | self._progress = self._settings.getProgress()
|
---|
| 78 |
|
---|
| 79 | # the profile contains the preferences of the agent over the domain
|
---|
| 80 | self._profile = ProfileConnectionFactory.create(
|
---|
| 81 | info.getProfile().getURI(), self.getReporter()
|
---|
| 82 | )
|
---|
| 83 | ####Very important line to set up the list of possible values####
|
---|
| 84 | ####Takes a lot of time####
|
---|
| 85 | self._createLists()
|
---|
| 86 |
|
---|
| 87 | # ActionDone is an action send by an opponent (an offer or an accept)
|
---|
| 88 | elif isinstance(info, ActionDone):
|
---|
| 89 | action: Action = cast(ActionDone, info).getAction()
|
---|
| 90 |
|
---|
| 91 | # if it is an offer, set the last received bid
|
---|
| 92 | if isinstance(action, Offer):
|
---|
| 93 | self._last_received_bid = cast(Offer, action).getBid()
|
---|
| 94 | # YourTurn notifies you that it is your turn to act
|
---|
| 95 | elif isinstance(info, YourTurn):
|
---|
| 96 | action = self._myTurn()
|
---|
| 97 | if isinstance(self._progress, ProgressRounds):
|
---|
| 98 | self._progress = self._progress.advance()
|
---|
| 99 | self.getConnection().send(action)
|
---|
| 100 |
|
---|
| 101 | # Finished will be send if the negotiation has ended (through agreement or deadline)
|
---|
| 102 | elif isinstance(info, Finished):
|
---|
| 103 | # terminate the agent MUST BE CALLED
|
---|
| 104 | self.terminate()
|
---|
| 105 | else:
|
---|
| 106 | self.getReporter().log(
|
---|
| 107 | logging.WARNING, "Ignoring unknown info " + str(info)
|
---|
| 108 | )
|
---|
| 109 |
|
---|
| 110 | # lets the geniusweb system know what settings this agent can handle
|
---|
| 111 | # leave it as it is for this competition
|
---|
| 112 | def getCapabilities(self) -> Capabilities:
|
---|
| 113 | return Capabilities(
|
---|
| 114 | set(["SAOP"]),
|
---|
| 115 | set(["geniusweb.profile.utilityspace.LinearAdditive"]),
|
---|
| 116 | )
|
---|
| 117 |
|
---|
| 118 | # terminates the agent and its connections
|
---|
| 119 | # leave it as it is for this competition
|
---|
| 120 | def terminate(self):
|
---|
| 121 | self.getReporter().log(logging.INFO, "party is terminating:")
|
---|
| 122 | super().terminate()
|
---|
| 123 | if self._profile is not None:
|
---|
| 124 | self._profile.close()
|
---|
| 125 | self._profile = None
|
---|
| 126 |
|
---|
| 127 |
|
---|
| 128 |
|
---|
| 129 | # give a description of your agent
|
---|
| 130 | def getDescription(self) -> str:
|
---|
| 131 | return "Agent7"
|
---|
| 132 |
|
---|
| 133 | # execute a turn
|
---|
| 134 | def _myTurn(self):
|
---|
| 135 |
|
---|
| 136 | # check if the last received offer if the opponent is good enough
|
---|
| 137 | if self._isGood(self._last_received_bid):
|
---|
| 138 | # if so, accept the offer
|
---|
| 139 | action = Accept(self._me, self._last_received_bid)
|
---|
| 140 | else:
|
---|
| 141 | # if not, find a bid to propose as counter offer
|
---|
| 142 | bid = self._findBid()
|
---|
| 143 | action = Offer(self._me, bid)
|
---|
| 144 |
|
---|
| 145 | # send the action
|
---|
| 146 | return action
|
---|
| 147 |
|
---|
| 148 | # method that checks if we would agree with an offer
|
---|
| 149 | def _isGood(self, bid: Bid) -> bool:
|
---|
| 150 | if bid is None:
|
---|
| 151 | return False
|
---|
| 152 | profile = self._profile.getProfile()
|
---|
| 153 |
|
---|
| 154 | progress = self._progress.get(time.time() * 1000)
|
---|
| 155 |
|
---|
| 156 | accpet_value = [0.9, 0.8, 0.7, 0.9, 0.5]
|
---|
| 157 | next_step = [0, 0.2, 0.4, 0.9, 0.95, 1]
|
---|
| 158 | optimal_util =0
|
---|
| 159 | for i, x in enumerate(next_step):
|
---|
| 160 | if progress <= x and x != 0:
|
---|
| 161 | optimal_util = accpet_value[i-1]
|
---|
| 162 | break
|
---|
| 163 |
|
---|
| 164 |
|
---|
| 165 |
|
---|
| 166 |
|
---|
| 167 | # # very basic approach that accepts if the offer is valued above 0.6 and
|
---|
| 168 | # # 80% of the rounds towards the deadline have passed
|
---|
| 169 | # return profile.getUtility(bid) > 0.6 and progress > 0.8
|
---|
| 170 |
|
---|
| 171 | return profile.getUtility(bid) >= optimal_util #profile.getUtility(lowest_acceptable)
|
---|
| 172 |
|
---|
| 173 | def _findBid(self) -> Bid:
|
---|
| 174 | # Decide whether we are trading off or continuing with our strategy
|
---|
| 175 |
|
---|
| 176 | # If no last offer and not trading, go on with the strategy
|
---|
| 177 | bid = None
|
---|
| 178 | self._progress_offset = 0
|
---|
| 179 |
|
---|
| 180 | while bid is None or bid in self._all_offers and self._progress_offset < 0.05:
|
---|
| 181 | if self._last_offer == None or self._is_trading == False:
|
---|
| 182 | bid = self._findStategyBid()
|
---|
| 183 | else:
|
---|
| 184 | bid = self._findTradeOff()
|
---|
| 185 |
|
---|
| 186 | self._progress_offset += 0.001
|
---|
| 187 | #print(bid)
|
---|
| 188 |
|
---|
| 189 |
|
---|
| 190 | self._all_offers.append(bid)
|
---|
| 191 | if bid == None:
|
---|
| 192 | print("Returning bid: " + str(bid))
|
---|
| 193 |
|
---|
| 194 | return bid
|
---|
| 195 |
|
---|
| 196 | # Duyemo's strategy
|
---|
| 197 | def _findStategyBid(self):
|
---|
| 198 | self._issue_index = 0
|
---|
| 199 | self._value_index = 0
|
---|
| 200 |
|
---|
| 201 | upper_lower = [[1, 0.9], [1, 0.8], [1, 0.7], [1, 0.6], [0.9, 0.5], [0.8, 0.5], [1, 0.8], [1, 0.5]]
|
---|
| 202 | next_step = [0, 0.05, 0.15, 0.3, 0.5, 0.7, 0.85, 0.95, 1]
|
---|
| 203 | #upper_lower = [[1,0.8],[1,0.6],[1,0.7]]
|
---|
| 204 | #next_step = [0, 0.5,1]
|
---|
| 205 |
|
---|
| 206 | progress = self._progress.get(time.time() * 1000)
|
---|
| 207 |
|
---|
| 208 | for i, x in enumerate(next_step):
|
---|
| 209 | if progress <= x and x != 0:
|
---|
| 210 | step_size = x - next_step[i - 1]
|
---|
| 211 | step_progress = progress - next_step[i - 1]
|
---|
| 212 | upper_lower_factor = step_progress / step_size
|
---|
| 213 | optimal_util = (1 - upper_lower_factor) * upper_lower[i - 1][0] + (upper_lower_factor) * \
|
---|
| 214 | upper_lower[i - 1][1] + self._progress_offset
|
---|
| 215 |
|
---|
| 216 | # print("####################")
|
---|
| 217 | # print(step_size)
|
---|
| 218 | # print(step_progress)
|
---|
| 219 | # print(upper_lower_factor)
|
---|
| 220 | # print(optimal_util)
|
---|
| 221 | # print("####################")
|
---|
| 222 | closest = self._take_closest(self.listOfUtil, decimal.Decimal(optimal_util))
|
---|
| 223 | bid = self.utilBidMap[closest]
|
---|
| 224 |
|
---|
| 225 | self._last_offer = bid
|
---|
| 226 | self._is_trading = True
|
---|
| 227 | self._trade_offers = []
|
---|
| 228 |
|
---|
| 229 | return bid
|
---|
| 230 |
|
---|
| 231 | def _createIssueMap(self):
|
---|
| 232 | lau: LinearAdditive = cast(LinearAdditive, self._profile.getProfile())
|
---|
| 233 |
|
---|
| 234 | issues = self._profile.getProfile().getDomain().getIssues();
|
---|
| 235 |
|
---|
| 236 | for issue in issues:
|
---|
| 237 | self._issue_by_weight[issue] = lau.getWeight(issue)
|
---|
| 238 |
|
---|
| 239 | self._issue_by_weight = sorted(self._issue_by_weight.items(), key=operator.itemgetter(1))
|
---|
| 240 |
|
---|
| 241 | print(self._issue_by_weight)
|
---|
| 242 |
|
---|
| 243 | def _findTradeOff(self):
|
---|
| 244 | if len(self._trade_offers) == 0:
|
---|
| 245 | self._trade_offer_index = 0
|
---|
| 246 | # Find all trade offers possible with same utility
|
---|
| 247 | current_bid = self._last_offer.getIssueValues()
|
---|
| 248 |
|
---|
| 249 | lau: LinearAdditive = cast(LinearAdditive, self._profile.getProfile())
|
---|
| 250 |
|
---|
| 251 | issues = self._profile.getProfile().getDomain().getIssues();
|
---|
| 252 | utils = lau.getUtilities()
|
---|
| 253 |
|
---|
| 254 | for issue in issues:
|
---|
| 255 | weight = lau.getWeight(issue)
|
---|
| 256 | values = self._profile.getProfile().getDomain().getIssuesValues()[issue]
|
---|
| 257 | valueSet: ValueSetUtilities = utils.get(issue)
|
---|
| 258 |
|
---|
| 259 | for value in values:
|
---|
| 260 | newUtil = valueSet.getUtility(value)
|
---|
| 261 | if current_bid.get(issue) != value and \
|
---|
| 262 | abs(newUtil - valueSet.getUtility(current_bid.get(issue))) * weight < 0.05:
|
---|
| 263 | # New bid has almost same util
|
---|
| 264 | newBid = self._last_offer.getIssueValues()
|
---|
| 265 |
|
---|
| 266 | newBid[issue] = value
|
---|
| 267 |
|
---|
| 268 | self._trade_offers.append(Bid(newBid))
|
---|
| 269 | if self._trade_offer_index < len(self._trade_offers):
|
---|
| 270 | bid = self._trade_offers[self._trade_offer_index];
|
---|
| 271 |
|
---|
| 272 | self._trade_offer_index += 1
|
---|
| 273 | return bid
|
---|
| 274 |
|
---|
| 275 | self._is_trading = False
|
---|
| 276 | return self._findStategyBid()
|
---|
| 277 | """
|
---|
| 278 | # Get all weight to calculate util change
|
---|
| 279 |
|
---|
| 280 |
|
---|
| 281 |
|
---|
| 282 | # Find a bid with same util, but different offers.
|
---|
| 283 |
|
---|
| 284 | current_bid = self._last_offer.getIssueValues()
|
---|
| 285 |
|
---|
| 286 | issues: Set[str] = self._last_offer.getIssues()
|
---|
| 287 | values: Set[str] = self._profile.getProfile().getDomain().getIssuesValues()
|
---|
| 288 |
|
---|
| 289 | # Check if cast is valid, should always be
|
---|
| 290 | # if isinstance(self._profile.getProfile(), LinearAdditiveUtilitySpace):
|
---|
| 291 | # print("Tewst")
|
---|
| 292 |
|
---|
| 293 | lau: LinearAdditive = cast(LinearAdditive, self._profile.getProfile())
|
---|
| 294 |
|
---|
| 295 | current_issue = self._issue_by_weight[self._issue_index]
|
---|
| 296 |
|
---|
| 297 | utils = lau.getUtilities()
|
---|
| 298 | # Get value set of the lowest weight issue
|
---|
| 299 | valueSet: ValueSetUtilities = utils.get(current_issue[0])
|
---|
| 300 |
|
---|
| 301 | values = self._profile.getProfile().getDomain().getIssuesValues()[current_issue[0]]
|
---|
| 302 | sorted_values = {}
|
---|
| 303 |
|
---|
| 304 | for value in values:
|
---|
| 305 | sorted_values[value] = valueSet.getUtility(value)
|
---|
| 306 |
|
---|
| 307 | sorted_values = sorted(sorted_values.items(), key=operator.itemgetter(1), reverse=True)
|
---|
| 308 |
|
---|
| 309 | current_bid[current_issue[0]] = list(sorted_values)[self._value_index][0];
|
---|
| 310 |
|
---|
| 311 | self._value_index += 1
|
---|
| 312 |
|
---|
| 313 | if self._value_index >= len(sorted_values):
|
---|
| 314 | self._value_index = 0
|
---|
| 315 | self._issue_index += 1
|
---|
| 316 |
|
---|
| 317 | print(current_bid)
|
---|
| 318 |
|
---|
| 319 | return Bid(current_bid)"""
|
---|
| 320 |
|
---|
| 321 | def _createLists(self):
|
---|
| 322 | profile = self._profile.getProfile()
|
---|
| 323 | domain = self._profile.getProfile().getDomain()
|
---|
| 324 | all_bids = AllBidsList(domain)
|
---|
| 325 |
|
---|
| 326 | for bid in all_bids:
|
---|
| 327 | self.utilBidMap[profile.getUtility(bid)] = bid
|
---|
| 328 | self.listOfUtil = sorted(self.utilBidMap)
|
---|
| 329 |
|
---|
| 330 | clostest = self._take_closest(self.listOfUtil, decimal.Decimal(0.7))
|
---|
| 331 |
|
---|
| 332 | def _take_closest(self, myList, myNumber) -> decimal.Decimal:
|
---|
| 333 | """
|
---|
| 334 | Assumes myList is sorted. Returns closest value to myNumber.
|
---|
| 335 |
|
---|
| 336 | If two numbers are equally close, return the smallest number.
|
---|
| 337 | """
|
---|
| 338 | pos = bisect_left(myList, myNumber)
|
---|
| 339 | if pos == 0:
|
---|
| 340 | return myList[0]
|
---|
| 341 | if pos == len(myList):
|
---|
| 342 | return myList[-1]
|
---|
| 343 | before = myList[pos - 1]
|
---|
| 344 | after = myList[pos]
|
---|
| 345 | if after - myNumber < myNumber - before:
|
---|
| 346 | return after
|
---|
| 347 | else:
|
---|
| 348 | return before
|
---|