source: ANL2022/charging_boul/charging_boul.py

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

#6 added ANAC2022 parties

File size: 10.6 KB
Line 
1from .extended_util_space import ExtendedUtilSpace
2from .utils.opponent_model import OpponentModel
3from decimal import Decimal
4from geniusweb.actions.Accept import Accept
5from geniusweb.actions.Action import Action
6from geniusweb.actions.Offer import Offer
7from geniusweb.actions.PartyId import PartyId
8from geniusweb.inform.ActionDone import ActionDone
9from geniusweb.inform.Finished import Finished
10from geniusweb.inform.Inform import Inform
11from geniusweb.inform.Settings import Settings
12from geniusweb.inform.YourTurn import YourTurn
13from geniusweb.issuevalue.Bid import Bid
14from geniusweb.issuevalue.Domain import Domain
15from geniusweb.party.Capabilities import Capabilities
16from geniusweb.party.DefaultParty import DefaultParty
17from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
18from geniusweb.profileconnection.ProfileConnectionFactory import ProfileConnectionFactory
19from geniusweb.profileconnection.ProfileInterface import ProfileInterface
20from geniusweb.progress.Progress import Progress
21from geniusweb.references.Parameters import Parameters
22from json import dump, load
23from random import randint
24from statistics import mean
25from time import time as clock
26from tudelft.utilities.immutablelist.ImmutableList import ImmutableList
27from tudelft_utilities_logging.Reporter import Reporter
28import logging
29
30
31class ChargingBoul(DefaultParty):
32 def __init__(self, reporter: Reporter = None):
33 super().__init__(reporter)
34 self.best_received_bid: Bid = None
35 self.best_received_util: Decimal = Decimal(0)
36 self.domain: Domain = None
37 self.e: float = 0.1
38 self.extended_space: ExtendedUtilSpace = None
39 self.filepath: str = None
40 self.final_rounds: int = 90
41 self.last_received_bid: Bid = None
42 self.last_received_util: Decimal = None
43 self.max_util = Decimal(1)
44 self.me: PartyId = None
45 self.min_util = Decimal(0.5)
46 self.opponent_model: OpponentModel = None
47 self.opponent_strategy: str = None
48 self.other: str = None
49 self.parameters: Parameters = None
50 self.profile_int: ProfileInterface = None
51 self.progress: Progress = None
52 self.received_bids: list = []
53 self.received_utils: list = []
54 self.settings: Settings = None
55 self.storage_dir: str = None
56 self.summary: dict = None
57 self.util_space: LinearAdditive = None
58 self.getReporter().log(logging.INFO, "party is initialized")
59
60 def notifyChange(self, info: Inform):
61 """MUST BE IMPLEMENTED
62 This is the entry point of all interaction with your agent after is has been initialised.
63 How to handle the received data is based on its class type.
64
65 Args:
66 info (Inform): Contains either a request for action or information.
67 """
68 try:
69 if isinstance(info, Settings):
70 self.settings = info
71 self.me = self.settings.getID()
72 self.parameters = self.settings.getParameters()
73 self.profile_int = ProfileConnectionFactory.create(
74 self.settings.getProfile().getURI(), self.getReporter()
75 )
76 self.progress = self.settings.getProgress()
77 self.storage_dir = self.parameters.get("storage_dir")
78 self.util_space = self.profile_int.getProfile()
79 self.domain = self.util_space.getDomain()
80 self.extended_space = ExtendedUtilSpace(self.util_space)
81 self.detect_strategy()
82 elif isinstance(info, ActionDone):
83 other_act: Action = info.getAction()
84 actor = other_act.getActor()
85 if actor != self.me:
86 self.other = str(actor).rsplit("_", 1)[0]
87 self.filepath = f"{self.storage_dir}/{self.other}.json"
88 if isinstance(other_act, Offer):
89 # create opponent model if it was not yet initialised
90 if self.opponent_model is None:
91 self.opponent_model = OpponentModel(self.domain)
92 self.last_received_bid = other_act.getBid()
93 self.last_received_util = self.util_space.getUtility(self.last_received_bid)
94 # update opponent model with bid
95 self.opponent_model.update(self.last_received_bid)
96 elif isinstance(info, YourTurn):
97 self.my_turn()
98 elif isinstance(info, Finished):
99 self.getReporter().log(logging.INFO, "Final outcome:" + str(info))
100 self.terminate()
101 # stop this party and free resources.
102 except Exception as ex:
103 self.getReporter().log(logging.CRITICAL, "Failed to handle info", ex)
104
105 def getCapabilities(self) -> Capabilities:
106 """MUST BE IMPLEMENTED
107 Method to indicate to the protocol what the capabilities of this agent are.
108 Leave it as is for the ANL 2022 competition
109
110 Returns:
111 Capabilities: Capabilities representation class
112 """
113 return Capabilities(
114 set(["SAOP"]),
115 set(["geniusweb.profile.utilityspace.LinearAdditive"]),
116 )
117
118 def getDescription(self) -> str:
119 """MUST BE IMPLEMENTED
120 Returns a description of your agent. 1 or 2 sentences.
121
122 Returns:
123 str: Agent description
124 """
125 return (
126 "Increasingly random Boulwarish agent. Last second concessions based on opponent's strategy."
127 )
128
129 ##################### private support funcs #########################
130
131 def detect_strategy(self):
132 if self.filepath is not None:
133 with open(self.filepath, "w") as f:
134 self.summary = load(f)
135 if self.summary["ubi"] >= 5:
136 self.opponent_strategy = "boulware"
137 self.e = 0.2 * 2**(5 - self.summary["ubi"])
138 elif self.summary["aui"] <= 2:
139 self.opponent_strategy = "hardline"
140 else:
141 self.opponent_strategy = "concede"
142 self.min_util = Decimal(0.4)
143
144 def my_turn(self):
145 # Keep history of received bids and best alternative
146 if self.last_received_bid is not None:
147 self.received_bids.append(self.last_received_bid)
148 self.received_utils.append(self.last_received_util)
149 if self.last_received_util > self.best_received_util:
150 self.best_received_bid = self.last_received_bid
151 self.best_received_util = self.last_received_util
152 # Create new bid based on the point in the negotiation and opponent's strategy
153 t = self.progress.get(clock() * 1000)
154 if self.summary is not None and self.opponent_strategy == "boulware" and t > 1 - (1/2)**min(self.summary["ubi"], 10):
155 # If Boulware opponent is not going to concede much more, try to make a reasonable concession
156 bid = self.make_concession()
157 else:
158 bid = self.make_bid()
159 # Check if we've previously gotten a better bid already
160 if self.best_received_util >= self.util_space.getUtility(bid):
161 i = self.received_utils.index(self.best_received_util)
162 bid = self.received_bids.pop(i)
163 self.received_utils.pop(i)
164 # Find next bests
165 self.best_received_util = max(self.received_utils)
166 i = self.received_utils.index(self.best_received_util)
167 self.best_received_bid = self.received_bids[i]
168 # Take action
169 my_action: Action
170 if bid == None or (
171 self.last_received_bid != None
172 and self.util_space.getUtility(self.last_received_bid)
173 >= self.util_space.getUtility(bid)
174 ):
175 # if bid==null we failed to suggest next bid.
176 my_action = Accept(self.me, self.last_received_bid)
177 else:
178 my_action = Offer(self.me, bid)
179 self.getConnection().send(my_action)
180
181 def make_concession(self):
182 self.min_util = Decimal(0.3)
183 opponent_util = self.opponent_model.get_predicted_utility(self.best_received_util)
184 if self.best_received_util > self.min_util and opponent_util < 2*self.min_util:
185 bid = self.best_received_bid
186 else:
187 bid = self.make_bid()
188 return bid
189
190 def make_bid(self) -> Bid:
191 time = self.progress.get(clock() * 1000)
192 utility_goal = self.get_utility_goal(time)
193 options: ImmutableList[Bid] = self.extended_space.getBids(utility_goal, time)
194 if options.size() == 0:
195 # if we can't find good bid, get max util bid....
196 options = self.extended_space.getBids(self.max_util, time)
197 # pick a random one.
198 return options.get(randint(0, options.size() - 1))
199
200 def get_utility_goal(self, t: float) -> Decimal:
201 ft1 = Decimal(1)
202 if self.e != 0:
203 ft1 = round(Decimal(1 - pow(t, 1 / self.e)), 6) # defaults ROUND_HALF_UP
204 return max(
205 min((self.min_util + (self.max_util - self.min_util) * ft1), self.max_util),
206 self.min_util
207 )
208
209 def terminate(self):
210 self.save_data()
211 self.getReporter().log(logging.INFO, "party is terminating:")
212 super().terminate()
213 if self.profile_int != None:
214 self.profile_int.close()
215 self.profile_int = None
216
217 def save_data(self):
218 ubi, aui = self.summarize_opponent()
219 with open(self.filepath, "w") as f:
220 dump({
221 "ubi": ubi,
222 "aui": aui
223 }, f)
224
225 def summarize_opponent(self):
226 # Detect how much the number of unique bids is increasing
227 unique_bid_index = 0
228 s = round(len(self.received_bids)/2)
229 left = self.received_bids[:s]
230 right = self.received_bids[s:]
231 while len(set(left)) > 0 and len(set(right)) > 0 and len(set(left)) < len(set(right)):
232 unique_bid_index += 1
233 s = round(len(right)/2)
234 left = right[:s]
235 right = right[s:]
236 # Detect how much average utility is increasing
237 avg_utility_index = 0
238 s = round(len(self.received_utils)/2)
239 left = self.received_utils[:s]
240 right = self.received_utils[s:]
241 while len(set(left)) > 0 and len(set(right)) > 0 and mean(left) < mean(right):
242 avg_utility_index += 1
243 s = round(len(right)/2)
244 left = right[:s]
245 right = right[s:]
246 return unique_bid_index, avg_utility_index
Note: See TracBrowser for help on using the repository browser.