source: CSE3210/agent61/agent61.py

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

#6 Added CSE3210 parties

File size: 10.3 KB
Line 
1import copy
2import logging
3import time
4from random import randint, choice
5from typing import cast, Dict
6
7from geniusweb.actions.Accept import Accept
8from geniusweb.actions.Action import Action
9from geniusweb.actions.Offer import Offer
10from geniusweb.bidspace.AllBidsList import AllBidsList
11from geniusweb.inform.ActionDone import ActionDone
12from geniusweb.inform.Finished import Finished
13from geniusweb.inform.Inform import Inform
14from geniusweb.inform.Settings import Settings
15from geniusweb.inform.YourTurn import YourTurn
16from geniusweb.issuevalue import Value
17from geniusweb.issuevalue.Bid import Bid
18from geniusweb.opponentmodel.FrequencyOpponentModel import FrequencyOpponentModel
19from geniusweb.party.Capabilities import Capabilities
20from geniusweb.party.DefaultParty import DefaultParty
21from geniusweb.profile.utilityspace import LinearAdditiveUtilitySpace
22from geniusweb.profileconnection.ProfileConnectionFactory import (
23 ProfileConnectionFactory,
24)
25from geniusweb.progress.ProgressRounds import ProgressRounds
26from tudelft_utilities_logging.Reporter import Reporter
27
28
29class Agent61(DefaultParty):
30 """
31 Template agent that offers random bids until a bid with sufficient utility is offered.
32 """
33
34 def __init__(self, reporter: Reporter = None):
35 super().__init__(reporter)
36 self.getReporter().log(logging.INFO, "party is initialized")
37 self._profile = None
38 self._received_bids = list()
39 self._sent_bids = list()
40 self._best_bid = None
41 self._last_received_bid: Bid = None
42 self._last_sent_bid: Bid = None
43 self._opponent_model: FrequencyOpponentModel = None
44 self._reservation_value = None
45
46 def notifyChange(self, info: Inform):
47 """This is the entry point of all interaction with your agent after is has been initialised.
48
49 Args:
50 info (Inform): Contains either a request for action or information.
51 """
52
53 # a Settings message is the first message that will be send to your
54 # agent containing all the information about the negotiation session.
55 if isinstance(info, Settings):
56 self._settings: Settings = cast(Settings, info)
57 self._me = self._settings.getID()
58
59 # progress towards the deadline has to be tracked manually through the use of the Progress object
60 self._progress: ProgressRounds = self._settings.getProgress()
61
62 # the profile contains the preferences of the agent over the domain
63 self._profile = ProfileConnectionFactory.create(
64 info.getProfile().getURI(), self.getReporter()
65 )
66 # ActionDone is an action send by an opponent (an offer or an accept)
67 elif isinstance(info, ActionDone):
68 action: Action = cast(ActionDone, info).getAction()
69
70 # if it is an offer, set the last received bid
71 if isinstance(action, Offer) and self._me.getName() != action.getActor().getName():
72 self._last_received_bid = cast(Offer, action).getBid()
73
74 # Add the action to the opponent model, create one if it doesn't exist
75 if self._opponent_model is None:
76 self._opponent_model = FrequencyOpponentModel.create()
77 self._opponent_model = self._opponent_model \
78 .With(newDomain=(self._profile.getProfile()).getDomain(), newResBid=None)
79 self._opponent_model = FrequencyOpponentModel.WithAction(self._opponent_model, action,
80 self._progress)
81 else:
82 self._opponent_model = FrequencyOpponentModel.WithAction(self._opponent_model, action,
83 self._progress)
84
85 # YourTurn notifies you that it is your turn to act
86 elif isinstance(info, YourTurn):
87 action = self._myTurn()
88 if isinstance(self._progress, ProgressRounds):
89 self._progress = self._progress.advance()
90 self.getConnection().send(action)
91
92 # Finished will be send if the negotiation has ended (through agreement or deadline)
93 elif isinstance(info, Finished):
94 # terminate the agent MUST BE CALLED
95 self.terminate()
96 else:
97 self.getReporter().log(
98 logging.WARNING, "Ignoring unknown info " + str(info)
99 )
100
101 # lets the geniusweb system know what settings this agent can handle
102 # leave it as it is for this competition
103 def getCapabilities(self) -> Capabilities:
104 return Capabilities(
105 set(["SAOP"]),
106 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
107 )
108
109 # terminates the agent and its connections
110 # leave it as it is for this competition
111 def terminate(self):
112 self.getReporter().log(logging.INFO, "party is terminating:")
113 super().terminate()
114 if self._profile is not None:
115 self._profile.close()
116 self._profile = None
117
118
119
120 # give a description of your agent
121 def getDescription(self) -> str:
122 return "Agent61"
123
124 # Creates the ideal bid for the agent
125 def _createBestBid(self):
126 own_prof = self._profile.getProfile()
127
128 bidVals: dict[str, Value] = dict()
129 prof_vals = own_prof.getDomain().getIssuesValues()
130
131 for issue in prof_vals.keys():
132 utilvals = own_prof.getUtilities()[issue]
133 bidVals[issue] = max(utilvals.getUtilities(), key=utilvals.getUtilities().get)
134
135 self._best_bid = Bid(bidVals)
136
137 # execute a turn
138 def _myTurn(self):
139
140 if self._best_bid is None:
141 self._createBestBid()
142
143 # If a reservation bid exists, its utility is the lower bound for accepting / sending offers
144 if self._reservation_value is None:
145 if self._profile.getProfile().getReservationBid() is None:
146 self._reservation_value = 0
147 else:
148 self._reservation_value = self._profile.getProfile().getUtility(self._profile.getProfile().getReservationBid())
149
150 # check if the last received offer if the opponent is good enough
151 if self._isGood(self._last_received_bid):
152 # if so, accept the offer
153 action = Accept(self._me, self._last_received_bid)
154 else:
155 # if not, find a bid to propose as counter offer
156 # bid = self._findBid()
157 bid = self._findCounterBid()
158 action = Offer(self._me, bid)
159
160 # send the action
161 return action
162
163 # method that checks if we would agree with an offer
164 def _isGood(self, bid: Bid) -> bool:
165 if bid is None:
166 return False
167 profile = self._profile.getProfile()
168 progress = self._progress.get(time.time() * 1000)
169
170 diff = (self._opponent_model.getUtility(bid) - profile.getUtility(bid))
171
172 # Ensure that bid is above reservation value. Additionally, in the early-mid game,
173 # the utility should be above a time-dependent threshold. After that, the difference
174 # in utilities between the agent and the opponent should be lower that 0.1
175 return (profile.getUtility(bid) > self._reservation_value) and \
176 (profile.getUtility(bid) > (0.9 - 0.1 * progress) or \
177 (progress > 0.8 and diff < 0.1))
178
179 # Defines the bidding strategy of the agent, returing the ideal
180 # bid at the beginning, and otherwise generating intelligent counter-bids.
181 # The method also saves sent bids for future bidding.
182 def _findCounterBid(self) -> Bid:
183
184 if self._progress.get(time.time() * 1000) < 0.1:
185 selected_bid = self._best_bid
186 else:
187 selected_bid = self._findCounterBidMutate()
188
189 self._last_sent_bid = selected_bid
190 self._sent_bids.append(copy.deepcopy(selected_bid))
191 return selected_bid
192
193 # Creates a bid by mutating the agent's ideal bid to fit closer
194 # to what the opponent model believes is beneficial to the other
195 # party. The more time has passed, the more the ideal bid is mutated
196 def _mutateBid(self, bid: Bid) -> Bid:
197
198 own_prof = self._profile.getProfile()
199 bw = own_prof.getWeights()
200
201 sorted_weights = sorted(bw, key=bw.get)
202 issues_vals = copy.deepcopy(bid.getIssueValues())
203 current_index = int((len(sorted_weights) - 1.0) * self._progress.get(time.time() * 1000))
204
205 while current_index >= 0 and own_prof.getUtility(Bid(issues_vals)) > self._reservation_value:
206 sel_issue_vals = own_prof.getDomain().getIssuesValues()[sorted_weights[current_index]]
207 issues_vals[sorted_weights[current_index]] = sel_issue_vals.get(randint(0, sel_issue_vals.size() - 1))
208 current_index = current_index - 1
209
210 return Bid(issues_vals)
211
212 # Finds an intelligent counter bid, relying on opponent modelling and the
213 # mutateBid function to find a bid that maximizes the Nash product, tries
214 # to equalize both parties' utility value and that is above reservation
215 def _findCounterBidMutate(self) -> Bid:
216
217 own_prof = self._profile.getProfile()
218
219 selected_bid = copy.deepcopy(self._last_sent_bid)
220 max_nash_prod = (own_prof.getUtility(selected_bid) * self._opponent_model.getUtility(selected_bid))
221
222 for _ in range(50):
223 newbid = self._mutateBid(copy.deepcopy(self._best_bid))
224 new_nash_prod = (own_prof.getUtility(newbid) * self._opponent_model.getUtility(newbid))
225
226 diff = (self._opponent_model.getUtility(newbid) - own_prof.getUtility(newbid))
227
228 if new_nash_prod > max_nash_prod and diff < 0.1 and own_prof.getUtility(newbid) > self._reservation_value:
229 # print("OLD: " + str(max_nash_prod) + ", NEW: " + str(new_nash_prod))
230
231 max_nash_prod = new_nash_prod
232 selected_bid = copy.deepcopy(newbid)
233
234 if self._progress.get(time.time() * 1000) > 0.95:
235 for bid in self._sent_bids:
236 if abs(self._opponent_model.getUtility(bid) - own_prof.getUtility(bid)) < 0.1 and \
237 self._opponent_model.getUtility(bid) > self._opponent_model.getUtility(selected_bid) and \
238 own_prof.getUtility(bid) > self._reservation_value:
239 selected_bid = bid
240
241 return selected_bid
Note: See TracBrowser for help on using the repository browser.