source: CSE3210/agent18/agent18.py

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

#6 Added CSE3210 parties

File size: 12.8 KB
Line 
1import logging
2import time
3import random
4from random import randint, choices
5from typing import cast
6
7import geniusweb.opponentmodel.FrequencyOpponentModel as freq_opp_mod
8import numpy as np
9from geniusweb.actions.Accept import Accept
10from geniusweb.actions.Action import Action
11from geniusweb.actions.Offer import Offer
12from geniusweb.bidspace.AllBidsList import AllBidsList
13from geniusweb.inform.ActionDone import ActionDone
14from geniusweb.inform.Finished import Finished
15from geniusweb.inform.Inform import Inform
16from geniusweb.inform.Settings import Settings
17from geniusweb.inform.YourTurn import YourTurn
18from geniusweb.issuevalue.Bid import Bid
19from geniusweb.party.Capabilities import Capabilities
20from geniusweb.party.DefaultParty import DefaultParty
21from geniusweb.profileconnection.ProfileConnectionFactory import (
22 ProfileConnectionFactory,
23)
24from geniusweb.progress.Progress import Progress
25from .acceptance_strategy import AcceptanceStrategy
26from geniusweb.progress.ProgressRounds import ProgressRounds
27from tudelft_utilities_logging.Reporter import Reporter
28
29
30# A custom agent that combines different strategies and changes between them based on time
31# At first the agent enters an exploration phase where it acts as a very strict random walker
32# After the exploration phase the agent starts behaving like the Agreeable agent, picking bids based on minimum
33# utility and roulette selection based on social welfare
34# After that, if the agents still did not find an agreement, the agent will start looking for the best nash product
35# Lastly the agent will start sending bids that it already received, maximizing its utility
36class Agent18(DefaultParty):
37 """
38 -- Shreker --
39 The Shreker agent is an agent that changes its strategy depending on the time in the following order:
40 - Random walker: initially explores opponent utility space while prevent opponent from getting our best bids
41 - Agreeable: agent by Sahar Mirzayi from ANAC 2018; offers the highest utility bid that concedes on one issue
42 from the offer
43 - Social welfare: late into the negotiation optimizes social welfare if opponent still has not conceded much
44 - Received bids: very late into the negotiation return one of the best bids out of the 20 last received bids
45 """
46
47 def __init__(self, reporter: Reporter = None):
48 super().__init__(reporter)
49 self.getReporter().log(logging.INFO, "party is initialized")
50 self._profile = None
51 # Stores the last received bid
52 self._last_received_bid: Bid = None
53 # List of all received bids
54 self._received_bids: list[Bid] = []
55 # Stores the last sent bid
56 self._last_sent_bid = None
57 # Stores the best utility stored so far
58 self._best_received_utility = 0.0
59 # Stores all the thresholds used throughout the agent
60 # 0 -> Threshold for acceptance strategy
61 # 1 -> Threshold for random walker | RandomWalker
62 # 2 -> Minimum target utility | Agreeable
63 # 3 -> Factor of the time dependent utility | Agreeable
64 # 4,5,6 -> Time splits for changing strategies
65 self.thresholds: list[float] = [0.99, 0.980278280105376, 0.9586147509907781, 3.846489410609955,
66 0.5702511194471804, 0.8702511194471804, 0.99]
67 # Ranges for the thresholds for optimization purposes
68 self.threshold_checks = [[0.8, 1], [0.7, 1], [0.7, 1], [2, 4],
69 [0.3, 0.7], [0.7, 0.9], [0.9, 1]]
70
71 def notifyChange(self, info: Inform):
72 """This is the entry point of all interaction with your agent after is has been initialised.
73
74 Args:
75 info (Inform): Contains either a request for action or information.
76 """
77
78 # a Settings message is the first message that will be send to your
79 # agent containing all the information about the negotiation session.
80 if isinstance(info, Settings):
81 self._settings: Settings = cast(Settings, info)
82 self._me = self._settings.getID()
83
84 # progress towards the deadline has to be tracked manually through the use of the Progress object
85 self._progress: Progress = self._settings.getProgress()
86
87 # the profile contains the preferences of the agent over the domain
88 self._profile = ProfileConnectionFactory.create(
89 info.getProfile().getURI(), self.getReporter()
90 )
91
92 self._bid_list = sorted(AllBidsList(self._profile.getProfile().getDomain()),
93 key=self._profile.getProfile().getUtility, reverse=True)
94 self._opponent_model = freq_opp_mod.FrequencyOpponentModel(self._profile.getProfile().getDomain(), {}, 0,
95 None).With(
96 self._profile.getProfile().getDomain(), None)
97 # ActionDone is an action send by an opponent (an offer or an accept)
98 elif isinstance(info, ActionDone):
99 action: Action = cast(ActionDone, info).getAction()
100
101 # if it is an offer, set the last received bid
102 if isinstance(action, Offer):
103 bid = cast(Offer, action).getBid()
104 if self._last_sent_bid is None or bid != self._last_sent_bid:
105 self._last_received_bid = bid
106 self._received_bids.append(self._last_received_bid)
107 self._opponent_model = self._opponent_model.WithAction(action, self._progress)
108 # YourTurn notifies you that it is your turn to act
109 elif isinstance(info, YourTurn):
110 action = self._myTurn()
111 if isinstance(self._progress, ProgressRounds):
112 self._progress = self._progress.advance()
113 self.getConnection().send(action)
114
115 # Finished will be send if the negotiation has ended (through agreement or deadline)
116 elif isinstance(info, Finished):
117 # terminate the agent MUST BE CALLED
118 self.terminate()
119 else:
120 self.getReporter().log(
121 logging.WARNING, "Ignoring unknown info " + str(info)
122 )
123
124 # lets the geniusweb system know what settings this agent can handle
125 # leave it as it is for this competition
126 def getCapabilities(self) -> Capabilities:
127 return Capabilities(
128 {"SAOP"},
129 {"geniusweb.profile.utilityspace.LinearAdditive"},
130 )
131
132 # terminates the agent and its connections
133 # leave it as it is for this competition
134 def terminate(self):
135 self.getReporter().log(logging.INFO, "party is terminating:")
136 super().terminate()
137 if self._profile is not None:
138 self._profile.close()
139 self._profile = None
140
141
142
143 # give a description of your agent
144 def getDescription(self) -> str:
145 return """
146 -- Shreker --
147 The Shreker agent is an agent that changes its strategy depending on the time in the following order:
148 - Random walker: initially explores opponent utility space while prevent opponent from getting our best bids
149 - Agreeable: agent by Sahar Mirzayi from ANAC 2018; offers the highest utility bid that concedes on one issue
150 from the offer
151 - Social welfare: late into the negotiation optimizes social welfare if opponent still has not conceded much
152 - Received bids: very late into the negotiation return one of the best bids out of the 20 last received bids"""
153
154 # execute a turn
155 def _myTurn(self):
156 # Update best received utility
157 if self._last_received_bid is not None and self._best_received_utility < self._profile.getProfile().getUtility(
158 self._last_received_bid):
159 self._best_received_utility = self._profile.getProfile().getUtility(self._last_received_bid)
160 # Find the next bid to send
161 next_sent_bid = self._findBid()
162
163 # Check whether the bid the bid to be offered follows some specific strategy based on received bids
164 # We do pass a bid we create, it is not an error :)
165 if self._isGood(next_sent_bid):
166 # If the next bid we would send wouldn't improve our chances of getting a better outcome, accept the last
167 # received bid
168 action = Accept(self._me, self._last_received_bid)
169 else:
170 # Otherwise, sent the bid, remove it so we do not send the same bid over and over
171 if next_sent_bid in self._bid_list:
172 self._bid_list.remove(next_sent_bid)
173 self._last_sent_bid = next_sent_bid
174 action = Offer(self._me, next_sent_bid)
175
176 # send the action
177 return action
178
179 # Method to check if we want to end the negotiation based on our next bid
180 def _isGood(self, next_sent_bid) -> bool:
181 if len(self._received_bids) == 0:
182 return False
183 profile = self._profile.getProfile()
184
185 progress = self._progress.get(time.time() * 1000)
186
187 # Create an acceptance profile and check the metrics used
188 ac = AcceptanceStrategy(progress, profile, self._received_bids, next_sent_bid, self._last_sent_bid)
189 return ac.combi_max_w(self.thresholds[0], 1, 0)
190
191 # Finds the next bid to send to the opponent
192 # Until threshold[4] -> RandomWalker
193 # threshold[4] until threshold[5] -> AgreeableAgent
194 # threshold[5] until threshold[6] -> SocialWelfareAgent
195 # After threshold[7] -> Send bids we received with best utility
196 def _findBid(self):
197 progress = self._progress.get(time.time() * 1000)
198 profile = self._profile.getProfile()
199 opponent = self._opponent_model
200 # Random Walker above specific threshold
201 if progress < self.thresholds[4]:
202 return self._generateRandomBidAbove(lambda x: x >= self.thresholds[1], self._bid_list, profile.getUtility)
203 # Agreeable agent based on ANAC 2018 agent
204 if progress < self.thresholds[5]:
205 return self._agreeable()
206 # Agent that maximizes the nash product
207 if progress < self.thresholds[6]:
208 return self._socialWelfare(lambda x: (self._profile.getProfile().getUtility(x)) * opponent.getUtility(x))
209 # Send bids that we received and maximize our utility
210 return self._sendReceived()
211
212 # Function to generate a random bid using a specific thresholding function
213 # threshold_function -> lambda function that returns a boolean used to filter bids
214 # bid_list -> list of bids to chose from
215 # utility_function -> lambda function that computes the utility of a bid
216 def _generateRandomBidAbove(self, threshold_function, bid_list, utility_function):
217 for _ in range(50):
218 bid = self._getRandomBid(bid_list)
219 if threshold_function(utility_function(bid)):
220 return bid
221 return self._bid_list[0]
222
223 # Generate a random element of the input list
224 def _getRandomBid(self, bid_list) -> Bid:
225 return bid_list[randint(0, len(bid_list) - 1)]
226
227 # Finds the next bid in the behaviour of the agreeable agent
228 # - gets all bids above a specific time threshold
229 # - selects one of them based on the social welfare (roulette selection)
230 def _agreeable(self) -> Bid:
231 # To collect enough data start by sending the best offers for us
232 target_utility = min(self.thresholds[2], (1 - self._progress.get(time.time() * 1000)) * self.thresholds[3])
233 profile = self._profile.getProfile()
234 bids = []
235 for bid in self._bid_list:
236 if profile.getUtility(bid) > target_utility:
237 bids.append(bid)
238 bids = sorted(bids, key=self._opponent_model.getUtility, reverse=True)
239 if len(bids) == 0:
240 return self._bid_list[0]
241 weights = np.array(
242 [float(profile.getUtility(bid)) + float(self._opponent_model.getUtility(bid)) for bid in bids])
243 return choices(bids, weights=weights / np.sum(weights))[0]
244
245 # Picks one bid from the bid list that maximizes a specific metric
246 def _socialWelfare(self, metric):
247 best_bid = self._bid_list[0]
248 for bid in self._bid_list:
249 if metric(best_bid) < metric(bid):
250 best_bid = bid
251 return best_bid
252
253 # Sends bid we have received while maximizing our utility gained from them
254 # Used at the very end to get as much as we can from the negotiation
255 def _sendReceived(self):
256 # Get top 20 received bids and select randomly based on our utility
257 profile = self._profile.getProfile()
258 top_20 = sorted(self._received_bids, key=profile.getUtility, reverse=True)[:20]
259 weights = np.array([float(profile.getUtility(bid)) for bid in top_20])
260 return random.choices(top_20, k=1, weights=weights / np.sum(weights))[0]
Note: See TracBrowser for help on using the repository browser.