source: ANL2022/procrastin_agent/utils/opponent_model.py

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

#6 added ANAC2022 parties

File size: 5.8 KB
Line 
1from collections import defaultdict
2
3from geniusweb.issuevalue.Bid import Bid
4from geniusweb.issuevalue.DiscreteValueSet import DiscreteValueSet
5from geniusweb.issuevalue.Domain import Domain
6from geniusweb.issuevalue.Value import Value
7
8
9class OpponentModel:
10 def __init__(self, domain: Domain):
11 self.offers = []
12 self.domain = domain
13
14 self.issue_estimators = {
15 i: IssueEstimator(v) for i, v in domain.getIssuesValues().items()
16 }
17
18 def update(self, bid: Bid, time: float):
19 # keep track of all bids received
20 self.offers.append(bid)
21
22 # update all issue estimators with the value that is offered for that issue
23 for issue_id, issue_estimator in self.issue_estimators.items():
24 issue_estimator.update(bid.getValue(issue_id), time)
25
26 def get_predicted_utility(self, bid: Bid):
27 if len(self.offers) == 0 or bid is None:
28 return 0
29
30 # initiate
31 total_issue_weight = 0.0
32 value_utilities = []
33 value_counts = []
34 issue_weights = []
35
36 for issue_id, issue_estimator in self.issue_estimators.items():
37 # get the value that is set for this issue in the bid
38 value: Value = bid.getValue(issue_id)
39
40 # collect both the predicted weight for the issue and
41 # predicted utility of the value within this issue
42 value_utilities.append(issue_estimator.get_value_utility(value))
43 value_counts.append(issue_estimator.value_trackers[value].count)
44 issue_weights.append(issue_estimator.weight)
45
46 total_issue_weight += issue_estimator.weight
47
48 # normalise the issue weights such that the sum is 1.0
49 if total_issue_weight == 0.0:
50 issue_weights = [1 / len(issue_weights) for _ in issue_weights]
51 else:
52 issue_weights = [iw / total_issue_weight for iw in issue_weights]
53
54 # calculate predicted utility by multiplying all value utilities with their issue weight
55 predicted_utility = sum(
56 [iw * vu for iw, vu in zip(issue_weights, value_utilities)]
57 )
58 prediction_uncertainty = sum(
59 [iw * (2.0**(-max(vc, 1))) for iw, vc in zip(issue_weights, value_counts)]
60 )
61
62 return predicted_utility, prediction_uncertainty
63
64 def get_issue_weights(self):
65 total = sum(issue_estimator.weight for issue, issue_estimator in self.issue_estimators.items())
66 issue_weights = {issue: issue_estimator.weight / total for issue, issue_estimator in self.issue_estimators.items()}
67 return issue_weights
68
69 def get_value_utils(self, issue: str):
70 value_utils = {value: value_estimator.utility for value, value_estimator in self.issue_estimators[issue].value_trackers.items()}
71 return value_utils
72
73
74class IssueEstimator:
75 def __init__(self, value_set: DiscreteValueSet):
76 if not isinstance(value_set, DiscreteValueSet):
77 raise TypeError(
78 "This issue estimator only supports issues with discrete values"
79 )
80
81 self.bids_received = 0
82 self.total_adjusted_value_count = 0
83 self.max_value_count = 0
84 self.max_adjusted_value_count = 0
85 self.num_values = value_set.size()
86 self.value_trackers = defaultdict(ValueEstimator)
87 self.weight = 0
88
89 def update(self, value: Value, time: float):
90 self.bids_received += 1
91
92 # get the value tracker of the value that is offered
93 value_tracker = self.value_trackers[value]
94
95 # register that this value was offered
96 update_amount = value_tracker.update(time)
97 self.total_adjusted_value_count += update_amount
98
99 # update the count of the most common offered value
100 self.max_value_count = max([value_tracker.count, self.max_value_count])
101 self.max_adjusted_value_count = max([value_tracker.adjusted_count, self.max_adjusted_value_count])
102
103 # update predicted issue weight
104 # the intuition here is that if the values of the receiverd offers spread out over all
105 # possible values, then this issue is likely not important to the opponent (weight == 0.0).
106 # If all received offers proposed the same value for this issue,
107 # then the predicted issue weight == 1.0
108 equal_shares = self.bids_received / self.num_values
109 adjusted_equal_shares = self.total_adjusted_value_count / self.num_values
110 self.old_weight = (self.max_value_count - equal_shares) / (
111 self.bids_received - equal_shares
112 )
113 self.weight = (self.max_adjusted_value_count - adjusted_equal_shares) / (
114 self.total_adjusted_value_count - adjusted_equal_shares
115 )
116
117 # recalculate all value utilities
118 for value_tracker in self.value_trackers.values():
119 value_tracker.recalculate_utility(self.max_adjusted_value_count, self.weight)
120
121 def get_value_utility(self, value: Value):
122 if value in self.value_trackers:
123 return self.value_trackers[value].utility
124
125 return 0
126
127
128class ValueEstimator:
129 def __init__(self):
130 self.count = 0
131 self.adjusted_count = 0
132 self.utility = 0
133
134 def update(self, time):
135 self.count += 1
136 update_amount = (1.0 - 0.9 * time) / (self.count + 1.0)
137 # update_amount = 1 # TODO Consider Removing
138 self.adjusted_count += update_amount
139 return update_amount
140
141 def recalculate_utility(self, max_adjusted_value_count: int, weight: float):
142 if weight < 1:
143 mod_value_count = ((self.adjusted_count + 1) ** (1 - weight)) - 1
144 mod_max_value_count = ((max_adjusted_value_count + 1) ** (1 - weight)) - 1
145
146 self.utility = mod_value_count / mod_max_value_count
147 else:
148 self.utility = 1 if self.adjusted_count != 0 else 0
Note: See TracBrowser for help on using the repository browser.