source: geniuswebcore/geniusweb/opponentmodel/FrequencyOpponentModel.py@ 94

Last change on this file since 94 was 90, checked in by Bart Vastenhouw, 3 years ago

Refactor to help reusing partiesserver.

File size: 5.6 KB
Line 
1from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
2from geniusweb.opponentmodel.OpponentModel import OpponentModel
3from decimal import Decimal
4from geniusweb.issuevalue.Domain import Domain
5from geniusweb.issuevalue.Bid import Bid
6from typing import Dict, Optional
7from geniusweb.issuevalue.Value import Value
8from geniusweb.actions.Action import Action
9from geniusweb.progress.Progress import Progress
10from geniusweb.actions.Offer import Offer
11from geniusweb.references.Parameters import Parameters
12from geniusweb.utils import val, HASH, toStr
13
14class FrequencyOpponentModel(UtilitySpace, OpponentModel):
15 '''
16 implements an {@link OpponentModel} by counting frequencies of bids placed by
17 the opponent.
18 <p>
19 NOTE: {@link NumberValue}s are also treated as 'discrete', so the frequency
20 of one value does not influence the influence the frequency of nearby values
21 (as you might expect as {@link NumberValueSetUtilities} is only affected by
22 the endpoints).
23 <p>
24 immutable.
25 '''
26
27 _DECIMALS = 4 # accuracy of our computations.
28
29 def __init__(self, domain:Optional[Domain],
30 freqs:Dict[str, Dict[Value, int]] , total:int,
31 resBid:Optional[Bid] ) :
32 '''
33 internal constructor. DO NOT USE, see create. Assumes the freqs keyset is
34 equal to the available issues.
35
36 @param domain the domain. Should not be None
37 @param freqs the observed frequencies for all issue values. This map is
38 assumed to be a fresh private-access only copy.
39 @param total the total number of bids contained in the freqs map. This
40 must be equal to the sum of the Integer values in the
41 {@link #bidFrequencies} for each issue (this is not
42 checked).
43 @param resBid the reservation bid. Can be null
44 '''
45 self._domain = domain
46 self._bidFrequencies = freqs
47 self._totalBids = total
48 self._resBid = resBid
49
50 @staticmethod
51 def create() -> "FrequencyOpponentModel":
52 return FrequencyOpponentModel(None, {}, 0 ,None )
53
54 #Override
55 def With(self, newDomain:Domain, newResBid:Optional[Bid]) -> "FrequencyOpponentModel":
56 if newDomain == None:
57 raise ValueError("domain is not initialized")
58 # FIXME merge already available frequencies?
59 return FrequencyOpponentModel(newDomain,
60 { iss: {} for iss in newDomain.getIssues() },
61 0, newResBid)
62
63 #Override
64 def getUtility(self,bid:Bid ) -> Decimal:
65 if self._domain == None:
66 raise ValueError("domain is not initialized")
67 if self._totalBids == 0:
68 return Decimal(1)
69 sum = Decimal(0)
70 # Assume all issues have equal weight.
71 for issue in val(self._domain).getIssues():
72 if issue in bid.getIssues():
73 sum = sum + self._getFraction(issue, val(bid.getValue(issue)))
74 return round(sum / len(self._bidFrequencies), FrequencyOpponentModel._DECIMALS)
75
76 #Override
77 def getName(self)->str:
78 if self._domain == None:
79 raise ValueError("domain is not initialized")
80 return "FreqOppModel" + str(hash(self)) + "For" + str(self._domain)
81
82 #Override
83 def getDomain(self)->Domain :
84 return val(self._domain)
85
86 #Override
87 def WithAction(self, action:Action, progress:Progress) ->"FrequencyOpponentModel":
88 if self._domain == None:
89 raise ValueError("domain is not initialized")
90
91 if not isinstance(action,Offer):
92 return self
93
94 bid:Bid = action.getBid()
95 newFreqs:Dict[str, Dict[Value, int]] = self.cloneMap(self._bidFrequencies)
96 for issue in self._domain.getIssues(): #type:ignore
97 freqs:Dict[Value, int] = newFreqs[issue]
98 value = bid.getValue(issue)
99 if value != None:
100 oldfreq=0
101 if value in freqs:
102 oldfreq = freqs[value]
103 freqs[value]=oldfreq + 1 #type:ignore
104
105 return FrequencyOpponentModel(self._domain, newFreqs,
106 self._totalBids+1, self._resBid)
107
108 def getCounts(self, issue:str) -> Dict[Value, int] :
109 '''
110 @param issue the issue to get frequency info for
111 @return a map containing a map of values and the number of times that
112 value was used in previous bids. Values that are possible but not
113 in the map have frequency 0.
114 '''
115 if self._domain == None:
116 raise ValueError("domain is not initialized")
117 if not issue in self._bidFrequencies:
118 return {}
119 return dict(self._bidFrequencies.get(issue)) #type:ignore
120
121 #Override
122 def WithParameters(self,parameters:Parameters ) ->OpponentModel :
123 return self # ignore parameters
124
125 def _getFraction(self, issue:str, value:Value ) -> Decimal :
126 '''
127 @param issue the issue to check
128 @param value the value to check
129 @return the fraction of the total cases that bids contained given value
130 for the issue.
131 '''
132 if self._totalBids == 0:
133 return Decimal(1)
134 if not (issue in self._bidFrequencies and value in self._bidFrequencies[issue]):
135 return Decimal(0)
136 freq:int = self._bidFrequencies[issue][value]
137 return round(Decimal(freq) / self._totalBids, FrequencyOpponentModel._DECIMALS) #type:ignore
138
139 @staticmethod
140 def cloneMap(freqs: Dict[str, Dict[Value, int]]) -> Dict[str, Dict[Value, int]] :
141 '''
142 @param freqs
143 @return deep copy of freqs map.
144 '''
145 map:Dict[str, Dict[Value, int]] = {}
146 for issue in freqs:
147 map[issue]=dict(freqs[issue])
148 return map
149
150 #Override
151 def getReservationBid(self) -> Optional[Bid] :
152 return self._resBid
153
154
155 def __eq__(self, other):
156 return isinstance(other, self.__class__) and \
157 self._domain == other._domain and\
158 self._bidFrequencies == other._bidFrequencies and\
159 self._totalBids == other._totalBids and\
160 self._resBid == other._resBid
161
162
163 def __hash__(self):
164 return HASH((self._domain, self._bidFrequencies, self._totalBids, self._resBid))
165 #Override
166
167 #Override
168 def __repr__(self)->str:
169 return "FrequencyOpponentModel[" + str(self._totalBids) + "," + \
170 toStr(self._bidFrequencies) + "]"
Note: See TracBrowser for help on using the repository browser.