source: geniuswebcore/geniusweb/profile/utilityspace/LinearAdditiveUtilitySpace.py@ 59

Last change on this file since 59 was 59, checked in by Wouter Pasman, 3 years ago

#44 manual commit of first public release, because this will cause the dist directory to move

File size: 5.5 KB
Line 
1from copy import copy
2from decimal import Decimal
3import re
4from typing import Dict, cast, Union
5
6from pyson.JsonGetter import JsonGetter
7
8from geniusweb.issuevalue.Bid import Bid
9from geniusweb.issuevalue.Domain import Domain
10from geniusweb.issuevalue.Value import Value
11from geniusweb.profile.utilityspace.LinearAdditive import LinearAdditive
12from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
13
14
15class LinearAdditiveUtilitySpace (LinearAdditive):
16 '''
17 Defines a UtilitySpace in terms of a weighted sum of per-issue preferences.
18 immutable. A {@link LinearAdditiveUtilitySpace} works with complete bids.
19
20 Constructor guarantees that
21 <ul>
22 <li>weights are normalized to 1
23 <li>the issues in the utility map and weights map match those in the domain
24 <li>The utilities for each issue are proper {@link ValueSetUtilities} objects
25 </ul>
26 '''
27
28
29 def __init__(self, domain:Domain , name:str, issueUtilities: Dict[str, ValueSetUtilities],\
30 issueWeights: Dict[str, Decimal] , reservationBid:Union[Bid,type(None)] = None ):
31 '''
32 @param domain the {@link Domain} in which this profile is defined.
33 @param name the name of this profile. Must be simple name (a-Z, 0-9)
34 @param issueUtilities a map with key: issue names (String) and value: the values
35 for that issue. There MUST NOT be a null issue. All values
36 MUST NOT be null.
37 @param issueWeights the weight of each issue in the computation of the
38 weighted sum. The issues must be the same as those in the
39 utils map. All weights MUST NOT be null. The weights MUST
40 sum to 1.
41 @param reservationBid the reservation bid. Only bids that are
42 {@link #isPreferredOrEqual(Bid, Bid)} should be accepted.
43 Can be None, meaning that there is no reservation bid and
44 any agreement is better than no agreement.
45 @throws NullPointerException if values are incorrectly null.
46 @throws IllegalArgumentException if preconditions not met.
47 '''
48 self._domain = domain
49 self._name = name
50 self._reservationBid=reservationBid
51 self._issueUtilities=copy(issueUtilities)
52 self._issueWeights=copy(issueWeights)
53
54 if domain == None:
55 raise ValueError("domain=null")
56
57 if issueUtilities == None :
58 raise ValueError("utils=null")
59
60 if issueWeights == None:
61 raise ValueError("weights=null");
62
63 if None in issueUtilities.values():
64 raise ValueError(\
65 "One of the ValueSetUtilities in issueUtilitiesis null:"\
66 + issueUtilities)
67
68 if None in issueWeights.values():
69 raise ValueError("One of the weights is null")
70
71 if None in issueUtilities.keys():
72 raise ValueError("One of the issue names is null");
73
74 if name == None or not re.match("[a-zA-Z0-9]+", name) :
75 raise ValueError("Name must be simple (a-Z, 0-9) but got " + name)
76
77 if issueUtilities.keys() != domain.getIssues():
78 raise ValueError( \
79 "The issues in utilityspace and domain do not match: utilityspace has issues "\
80 + str(issueUtilities.keys()) + " but domain contains "\
81 + str(domain.getIssues()))
82
83 if issueWeights.keys() != domain.getIssues():
84 raise ValueError(\
85 "The issues in weights and domain do not match: weights has "\
86 + str(issueWeights.keys()) + " but domain contains "\
87 + str(domain.getIssues()))
88
89 for issue in issueUtilities:
90 message:str = issueUtilities.get(issue).isFitting(domain.getValues(issue));
91 if message != None:
92 raise ValueError(message);
93
94 total:Decimal = sum(issueWeights.values())
95 if total!=Decimal(1):
96 raise ValueError("The sum of the weights ("
97 + str(issueWeights.values()) + ") must be 1")
98
99 if reservationBid != None:
100 message:str = domain.isFitting(reservationBid)
101 if message:
102 raise ValueError("reservationbid is not fitting domain: " + message)
103
104
105 #Override
106 def getUtility(self, bid:Bid ) -> Decimal:
107 return sum([ self._util(iss, bid.getValue(iss)) for iss in self._issueWeights.keys() ])
108
109 #Override
110 def getWeight(self,issue:str) -> Decimal :
111 return self._issueWeights[issue]
112
113 #Override
114 def __repr__(self):
115 return "LinearAdditive[" + str(self._issueUtilities) + "," + \
116 str(self._issueWeights) + "," + str(self._reservationBid) + "]"
117
118 #Override
119 def getReservationBid(self) -> Bid :
120 return self._reservationBid;
121
122
123 def __hash__(self):
124 return hash((self._domain, tuple(self._issueUtilities.items()), tuple(self._issueWeights.items()), self._name, self._reservationBid))
125
126 def __eq__(self, other):
127 return isinstance(other, self.__class__) and \
128 self._domain == other._domain and \
129 self._issueUtilities == other._issueUtilities and \
130 self._issueWeights == other._issueWeights and \
131 self._name == other._name and \
132 self._reservationBid == other._reservationBid
133
134 #Override
135 def getDomain(self)->Domain:
136 return self._domain
137
138 #Override
139 def getName(self)->str:
140 return self._name
141
142
143 #Override
144 @JsonGetter("issueUtilities")
145 def getUtilities(self) -> Dict[str, ValueSetUtilities] :
146 return copy(self._issueUtilities)
147
148
149 #Override
150 @JsonGetter("issueWeights")
151 def getWeights(self)->Dict[str, Decimal] :
152 return copy(self._issueWeights)
153
154 def _util(self, issue:str, value:Value ) ->Decimal:
155 '''
156 @param issue the issue to get weighted util for
157 @param value the issue value to use (typically coming from a bid)
158 @return weighted util of just the issue value:
159 issueUtilities[issue].utility(value) * issueWeights[issue)
160 '''
161 return self._issueWeights[issue] * self._issueUtilities[issue].getUtility(value)
162
Note: See TracBrowser for help on using the repository browser.