source: exampleparties/timedependentparty/timedependentparty/TimeDependentParty.py@ 81

Last change on this file since 81 was 81, checked in by Bart Vastenhouw, 2 years ago

Added python timedependent parties (conceder, hardliner, etc)

File size: 12.1 KB
Line 
1import logging
2from random import randint, random
3import traceback
4from typing import cast, Dict, List, Set, Collection
5
6from geniusweb.actions.Accept import Accept
7from geniusweb.actions.Action import Action
8from geniusweb.actions.LearningDone import LearningDone
9from geniusweb.actions.Offer import Offer
10from geniusweb.actions.PartyId import PartyId
11from geniusweb.actions.Vote import Vote
12from geniusweb.actions.Votes import Votes
13from geniusweb.bidspace.AllBidsList import AllBidsList
14from geniusweb.inform.ActionDone import ActionDone
15from geniusweb.inform.Finished import Finished
16from geniusweb.inform.Inform import Inform
17from geniusweb.inform.OptIn import OptIn
18from geniusweb.inform.Settings import Settings
19from geniusweb.inform.Voting import Voting
20from geniusweb.inform.YourTurn import YourTurn
21from geniusweb.issuevalue.Bid import Bid
22from geniusweb.issuevalue.Domain import Domain
23from geniusweb.issuevalue.Value import Value
24from geniusweb.issuevalue.ValueSet import ValueSet
25from geniusweb.party.Capabilities import Capabilities
26from geniusweb.party.DefaultParty import DefaultParty
27from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
28from geniusweb.profileconnection.ProfileConnectionFactory import ProfileConnectionFactory
29from geniusweb.progress.ProgressRounds import ProgressRounds
30from geniusweb.utils import val
31from geniusweb.profileconnection.ProfileInterface import ProfileInterface
32from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
33from geniusweb.progress.Progress import Progress
34from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
35from time import sleep, time as clock
36from decimal import Decimal
37import sys
38from timedependentparty.ExtendedUtilSpace import ExtendedUtilSpace
39from tudelft_utilities_logging.Reporter import Reporter
40
41class TimeDependentParty (DefaultParty):
42 """
43 General time dependent party. This is a simplistic implementation that does
44 brute-force search through the bidspace and can handle bidspace sizes up to
45 2^31 (approx 1 billion bids). It may take excessive time and run out of time
46 on bidspaces > 10000 bids. In special cases it may even run out of memory,
47
48 <p>
49 Supports parameters as follows
50 <table>
51 <caption>parameters</caption>
52 <tr>
53 <td>e</td>
54 <td>e determines how fast the party makes concessions with time. Typically
55 around 1. 0 means no concession, 1 linear concession, &gt;1 faster than
56 linear concession.</td>
57 </tr>
58
59 <tr>
60 <td>minPower</td>
61 <td>This value is used as minPower for placed {@link Vote}s. Default value is
62 1.</td>
63 </tr>
64
65 <tr>
66 <td>maxPower</td>
67 <td>This value is used as maxPower for placed {@link Vote}s. Default value is
68 infinity.</td>
69 </tr>
70
71 <tr>
72 <td>delay</td>
73 <td>The average time in seconds to wait before responding to a YourTurn. The
74 actual waiting time will be random in [0.5*time, 1.5*time]. This can be used
75 to simulate human users that take thinking time.</td>
76 </tr>
77
78 </table>
79 <p>
80 TimeDependentParty requires a {@link UtilitySpace}
81 """
82 def __init__(self, reporter:Reporter=None):
83 super().__init__(reporter)
84 self._profileint:ProfileInterface = None #ttype:ignore
85 self._utilspace:LinearAdditive = None # last received space
86 self._me:PartyId =None #ttype:ignore
87 self._progress:Progress =None #ttype:ignore
88 self._lastReceivedBid:Bid = None
89 self._extendedspace:ExtendedUtilSpace =None
90 self._e:float = 1.2
91 self._lastvotes:Votes =None
92 self._settings:Settings =None
93 self.getReporter().log(logging.INFO,"party is initialized")
94
95 # Override
96 def getCapabilities(self) -> Capabilities:
97 return Capabilities( set([ "SAOP", "Learn", "MOPAC"]),
98 set(['geniusweb.profile.utilityspace.LinearAdditive']))
99
100 # Override
101 def notifyChange(self, info: Inform):
102 try:
103 if isinstance(info,Settings):
104 self._settings = info
105 self._me = self._settings.getID()
106 self._progress = self._settings.getProgress()
107 newe = self._settings.getParameters().get("e")
108 if newe != None:
109 if isinstance(newe,float):
110 self._e = newe
111 else:
112 self.getReporter().log(logging.WARNING,
113 "parameter e should be Double but found "
114 + str(newe))
115 protocol:str = str(self._settings.getProtocol().getURI())
116 if "Learn"==protocol:
117 val(self.getConnection()).send(LearningDone(self._me))
118 else:
119 self._profileint = ProfileConnectionFactory.create(
120 self._settings.getProfile().getURI(), self.getReporter())
121
122 elif isinstance(info,ActionDone):
123 otheract:Action = info.getAction()
124 if isinstance(otheract,Offer):
125 self._lastReceivedBid = otheract.getBid()
126 elif isinstance(info ,YourTurn):
127 self._delayResponse()
128 self._myTurn()
129 elif isinstance(info ,Finished):
130 self.getReporter().log(logging.INFO, "Final ourcome:" + str(info))
131 self.terminate(); # stop this party and free resources.
132 elif isinstance(info, Voting):
133 lastvotes = self._vote(info);
134 val(self.getConnection()).send(lastvotes)
135 elif isinstance(info, OptIn):
136 val(self.getConnection()).send(lastvotes)
137 except Exception as ex:
138 self.getReporter().log(logging.CRITICAL, "Failed to handle info", ex)
139 self._updateRound(info);
140
141
142 def getE(self) -> float:
143 '''
144 @return the E value that controls the party's behaviour. Depending on the
145 value of e, extreme sets show clearly different patterns of
146 behaviour [1]:
147
148 1. Boulware: For this strategy e &lt; 1 and the initial offer is
149 maintained till time is almost exhausted, when the agent concedes
150 up to its reservation value.
151
152 2. Conceder: For this strategy e &gt; 1 and the agent goes to its
153 reservation value very quickly.
154
155 3. When e = 1, the price is increased linearly.
156
157 4. When e = 0, the agent plays hardball.
158 '''
159 return self._e
160
161 # Override
162 def getDescription(self) -> str:
163 return "Time-dependent conceder. Aims at utility u(t) = scale * t^(1/e) " \
164 + "where t is the time (0=start, 1=end), e is the concession speed parameter (default 1.1), and scale such that u(0)=minimum and " \
165 + "u(1) = maximum possible utility. Parameters minPower (default 1) and maxPower (default infinity) are used " \
166 + "when voting"
167
168 # Override
169 def terminate(self):
170 self.getReporter().log(logging.INFO,"party is terminating:")
171 super().terminate()
172 if self._profileint != None:
173 self._profileint.close()
174 self._profileint = None
175
176 ##################### private support funcs #########################
177
178 def _updateRound(self, info:Inform ):
179 '''
180 Update {@link #progress}, depending on the protocol and last received
181 {@link Inform}
182
183 @param info the received info.
184 '''
185 if self._settings == None: # not yet initialized
186 return
187 protocol:str = str(self._settings.getProtocol().getURI())
188
189 if "SAOP"==protocol or "SHAOP"==protocol:
190 if not isinstance(info, YourTurn):
191 return
192 elif "MOPAC"==protocol:
193 if not isinstance(info , OptIn):
194 return
195 else:
196 return
197 # if we get here, round must be increased.
198 if isinstance(self._progress , ProgressRounds):
199 self._progress = self._progress.advance()
200
201 def _myTurn(self):
202 self._updateUtilSpace();
203 bid = self._makeBid()
204
205 myAction:Action
206 if bid == None or (self._lastReceivedBid != None
207 and self._utilspace.getUtility(self._lastReceivedBid)
208 >= self._utilspace.getUtility(bid)):
209 # if bid==null we failed to suggest next bid.
210 myAction = Accept(self._me, self._lastReceivedBid)
211 else :
212 myAction = Offer(self._me, bid)
213 self.getConnection().send(myAction)
214
215 def _updateUtilSpace(self)-> LinearAdditive:# throws IOException
216 newutilspace = self._profileint.getProfile()
217 if not newutilspace==self._utilspace:
218 self._utilspace = cast(LinearAdditive, newutilspace)
219 self._extendedspace = ExtendedUtilSpace(self._utilspace)
220 return self._utilspace
221
222
223 def _makeBid(self)->Bid :
224 '''
225 @return next possible bid with current target utility, or null if no such
226 bid.
227 '''
228 time = self._progress.get(round(clock()*1000))
229
230 utilityGoal = self._getUtilityGoal(time, self.getE(),
231 self._extendedspace.getMin(), self._extendedspace.getMax())
232 options:ImmutableList[Bid] = self._extendedspace.getBids(utilityGoal)
233 if options.size() == 0:
234 # if we can't find good bid, get max util bid....
235 options = self._extendedspace.getBids(self._extendedspace.getMax())
236 # pick a random one.
237 return options.get(randint(0,options.size()-1))
238
239
240 def _getUtilityGoal(self, t:float, e:float, minUtil:Decimal,
241 maxUtil:Decimal) ->Decimal :
242 '''
243 @param t the time in [0,1] where 0 means start of nego and 1 the
244 end of nego (absolute time/round limit)
245 @param e the e value that determinses how fast the party makes
246 concessions with time. Typically around 1. 0 means no
247 concession, 1 linear concession, &gt;1 faster than linear
248 concession.
249 @param minUtil the minimum utility possible in our profile
250 @param maxUtil the maximum utility possible in our profile
251 @return the utility goal for this time and e value
252 '''
253
254 ft1 = Decimal(1);
255 if e != 0:
256 ft1 = round(Decimal(1 - pow(t, 1 / e)),6) # defaults ROUND_HALF_UP
257 return max(min((minUtil + (maxUtil-minUtil)*ft1), maxUtil), minUtil)
258
259 def _vote(self, voting:Voting) -> Votes: # throws IOException
260 '''
261 @param voting the {@link Voting} object containing the options
262
263 @return our next Votes.
264 '''
265 val = self._settings.getParameters().get("minPower")
266 # max utility requires smallest possible group/power
267 minpower = val if isinstance(val,int) else 1
268 val = self._settings.getParameters().get("maxPower")
269 maxpower = val if isinstance(val ,int) else sys.maxsize
270
271 votes:Set[Vote] = { Vote(self._me, offer.getBid(), minpower, maxpower)
272 for offer in set(voting.getOffers())
273 if self._isGood(offer.getBid()) }
274
275 return Votes(self._me, votes)
276
277
278
279
280 def _isGood(self, bid:Bid) -> bool:
281 '''
282 @param bid the bid to check
283 @return true iff bid is good for us.
284 '''
285 if bid == None or self._profileint == None:
286 return False
287 profile = cast(LinearAdditive, self._profileint.getProfile())
288 # the profile MUST contain UtilitySpace
289 time = self._progress.get(round(clock()*1000))
290 return profile.getUtility(bid) >= \
291 self._getUtilityGoal(time, self.getE(), self._extendedspace.getMin(),
292 self._extendedspace.getMax())
293
294 def _delayResponse(self):# throws InterruptedException
295 '''
296 Do random delay of provided delay in seconds, randomized by factor in
297 [0.5, 1.5]. Does not delay if set to 0.
298
299 @throws InterruptedException
300 '''
301 delay = self._settings.getParameters().getDouble("delay", 0, 0, 10000000)
302 if delay > 0:
303 sleep(delay * (0.5 + random()))
Note: See TracBrowser for help on using the repository browser.