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

Last change on this file since 100 was 100, checked in by ruud, 14 months ago

python installs also wheel to avoid error messages

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