1 | from collections import OrderedDict
|
---|
2 | from decimal import Decimal
|
---|
3 | import json
|
---|
4 | from pathlib import Path
|
---|
5 | from typing import List, Dict
|
---|
6 | import unittest
|
---|
7 | from unittest.mock import Mock
|
---|
8 |
|
---|
9 | from pyson.ObjectMapper import ObjectMapper
|
---|
10 | from unitpy.GeneralTests import GeneralTests
|
---|
11 |
|
---|
12 | from geniusweb.issuevalue.Bid import Bid
|
---|
13 | from geniusweb.issuevalue.DiscreteValue import DiscreteValue
|
---|
14 | from geniusweb.issuevalue.DiscreteValueSet import DiscreteValueSet
|
---|
15 | from geniusweb.issuevalue.Domain import Domain
|
---|
16 | from geniusweb.issuevalue.NumberValue import NumberValue
|
---|
17 | from geniusweb.issuevalue.Value import Value
|
---|
18 | from geniusweb.issuevalue.ValueSet import ValueSet
|
---|
19 | from geniusweb.profile.Profile import Profile
|
---|
20 | from geniusweb.profile.utilityspace.DiscreteValueSetUtilities import DiscreteValueSetUtilities
|
---|
21 | from geniusweb.profile.utilityspace.LinearAdditiveUtilitySpace import LinearAdditiveUtilitySpace
|
---|
22 | from geniusweb.profile.utilityspace.UtilitySpace import UtilitySpace
|
---|
23 | from geniusweb.profile.utilityspace.ValueSetUtilities import ValueSetUtilities
|
---|
24 |
|
---|
25 |
|
---|
26 | class LinearAdditiveTest(unittest.TestCase, GeneralTests[LinearAdditiveUtilitySpace]):
|
---|
27 | NAME = "test"
|
---|
28 | I1V1UTIL = Decimal("0.3")
|
---|
29 | I1V2UTIL = Decimal("0.2")
|
---|
30 | I2V1UTIL = Decimal("0.6")
|
---|
31 | I2V2UTIL = Decimal("0.8")
|
---|
32 | WEIGHT1 = Decimal("0.4")
|
---|
33 | WEIGHT2 = Decimal("0.6")
|
---|
34 | ISS1 = "issue1"
|
---|
35 | ISS2 = "issue2"
|
---|
36 | ISS3 = "issue3"
|
---|
37 | i1v1 = DiscreteValue("issue1value1")
|
---|
38 | i1v2 = DiscreteValue("issue1value2")
|
---|
39 | i2v1 = DiscreteValue("issue2value1")
|
---|
40 | i2v2 = DiscreteValue("issue2value2")
|
---|
41 | i3v1 = DiscreteValue("issue3value1")
|
---|
42 | i3v2 = DiscreteValue("issue3value2")
|
---|
43 |
|
---|
44 | pyson = ObjectMapper()
|
---|
45 |
|
---|
46 | WEIGHT1a = Decimal("0.3")
|
---|
47 | WEIGHT2a = Decimal("0.7")
|
---|
48 |
|
---|
49 | values:Dict[str, ValueSet] = OrderedDict()
|
---|
50 | values[ISS1]=DiscreteValueSet([i1v1, i1v2])
|
---|
51 | smalldomain = Domain(NAME, values)
|
---|
52 | values[ISS2]= DiscreteValueSet([i2v1, i2v2])
|
---|
53 | domain = Domain(NAME, values)
|
---|
54 |
|
---|
55 | # build utilspace for string and equals testing.
|
---|
56 | utils:Dict[str, ValueSetUtilities] = OrderedDict()
|
---|
57 | valueUtils = OrderedDict()
|
---|
58 | valueUtils[i2v2]= I2V2UTIL
|
---|
59 | valueUtils[i2v1]= I2V1UTIL
|
---|
60 | value2Utils = DiscreteValueSetUtilities(valueUtils)
|
---|
61 | utils[ISS2]= value2Utils
|
---|
62 | valueUtils = OrderedDict()
|
---|
63 | valueUtils[i1v1]= I1V1UTIL
|
---|
64 | valueUtils[i1v2]= I1V2UTIL
|
---|
65 | value1Utils = DiscreteValueSetUtilities(valueUtils)
|
---|
66 | utils[ISS1]= value1Utils
|
---|
67 |
|
---|
68 | # build utilspaceb, mix up the utilities a bit.
|
---|
69 | utilsb:Dict[str, ValueSetUtilities] = OrderedDict()
|
---|
70 | valueUtils = OrderedDict()
|
---|
71 | valueUtils[i2v2] =I1V2UTIL
|
---|
72 | valueUtils[i2v1]= I1V1UTIL
|
---|
73 | value2UtilsB = DiscreteValueSetUtilities(valueUtils)
|
---|
74 | utilsb[ISS2]= value2UtilsB
|
---|
75 | valueUtils = OrderedDict()
|
---|
76 | valueUtils[i1v1]= I2V1UTIL
|
---|
77 | valueUtils[i1v2]= I2V2UTIL
|
---|
78 | value1UtilsB = DiscreteValueSetUtilities(valueUtils)
|
---|
79 | utilsb[ISS1] =value1UtilsB
|
---|
80 |
|
---|
81 | # weight map
|
---|
82 | weights = OrderedDict()
|
---|
83 | weights[ISS2]= WEIGHT2
|
---|
84 | weights[ISS1]= WEIGHT1
|
---|
85 |
|
---|
86 | # weight map 2
|
---|
87 | weightsb = OrderedDict()
|
---|
88 | weightsb[ISS2]= WEIGHT2a
|
---|
89 | weightsb[ISS1]= WEIGHT1a
|
---|
90 |
|
---|
91 | # bid with lowest utility
|
---|
92 | issuevalues:Dict[str, Value] = OrderedDict()
|
---|
93 | issuevalues[ISS2]= i2v1
|
---|
94 | issuevalues[ISS1]=i1v2
|
---|
95 | reservationBid = Bid(issuevalues)
|
---|
96 |
|
---|
97 | issuevalues = OrderedDict()
|
---|
98 | issuevalues[ISS2]= i2v1
|
---|
99 | issuevalues[ISS1]= i1v1
|
---|
100 | reservationBid2 = Bid(issuevalues)
|
---|
101 |
|
---|
102 | # make the utilspaces
|
---|
103 | utilspace1 = LinearAdditiveUtilitySpace(domain, NAME, utils,
|
---|
104 | weights, reservationBid)
|
---|
105 | utilspace1a = LinearAdditiveUtilitySpace(domain, NAME, utils,
|
---|
106 | weights, reservationBid);
|
---|
107 |
|
---|
108 | utilspace2a = LinearAdditiveUtilitySpace(domain, NAME, utils,
|
---|
109 | weightsb, reservationBid);
|
---|
110 | utilspace2b = LinearAdditiveUtilitySpace(domain, NAME, utilsb,
|
---|
111 | weights, reservationBid);
|
---|
112 | utilspace3 = LinearAdditiveUtilitySpace(domain, NAME, utilsb,
|
---|
113 | weights, reservationBid2);
|
---|
114 |
|
---|
115 |
|
---|
116 | #Override
|
---|
117 | def getGeneralTestData(self)->List[List[LinearAdditiveUtilitySpace]] :
|
---|
118 | return [ [self.utilspace1, self.utilspace1a], [self.utilspace2a],
|
---|
119 | [self.utilspace2b], [self.utilspace3]]
|
---|
120 |
|
---|
121 | #Override
|
---|
122 | def getGeneralTestStrings(self)-> List[str] :
|
---|
123 | return [
|
---|
124 | "LinearAdditive\\[\\{issue2=DiscreteValueSetUtilities\\{\"issue2value2\"=0.8, \"issue2value1\"=0.6\\}, issue1=DiscreteValueSetUtilities\\{\"issue1value1\"=0.3, \"issue1value2\"=0.2\\}\\},\\{issue2=0.6, issue1=0.4\\},Bid\\{issue2=\"issue2value1\", issue1=\"issue1value2\"\\}\\]",
|
---|
125 | "LinearAdditive\\[\\{issue2=DiscreteValueSetUtilities\\{\"issue2value2\"=0.8, \"issue2value1\"=0.6\\}, issue1=DiscreteValueSetUtilities\\{\"issue1value1\"=0.3, \"issue1value2\"=0.2\\}\\},\\{issue2=0.7, issue1=0.3\\},Bid\\{issue2=\"issue2value1\", issue1=\"issue1value2\"\\}\\]",
|
---|
126 | "LinearAdditive\\[\\{issue2=DiscreteValueSetUtilities\\{\"issue2value2\"=0.2, \"issue2value1\"=0.3\\}, issue1=DiscreteValueSetUtilities\\{\"issue1value1\"=0.6, \"issue1value2\"=0.8\\}\\},\\{issue2=0.6, issue1=0.4\\},Bid\\{issue2=\"issue2value1\", issue1=\"issue1value2\"\\}\\]",
|
---|
127 | "LinearAdditive\\[\\{issue2=DiscreteValueSetUtilities\\{\"issue2value2\"=0.2, \"issue2value1\"=0.3\\}, issue1=DiscreteValueSetUtilities\\{\"issue1value1\"=0.6, \"issue1value2\"=0.8\\}\\},\\{issue2=0.6, issue1=0.4\\},Bid\\{issue2=\"issue2value1\", issue1=\"issue1value1\"\\}\\]"
|
---|
128 | ]
|
---|
129 |
|
---|
130 |
|
---|
131 | def testConstructorNullIssues(self):
|
---|
132 | self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, None, None,self.reservationBid))
|
---|
133 |
|
---|
134 | def testConstructorNullDomain(self):
|
---|
135 | self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(None, self.NAME, self.utils, self.weights,
|
---|
136 | self.reservationBid))
|
---|
137 |
|
---|
138 | # Empty profile is not allowed since the weights then don't sum up to 1
|
---|
139 | def testConstructorEmpty(self):
|
---|
140 | self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, {}, self.weights,
|
---|
141 | self.reservationBid))
|
---|
142 |
|
---|
143 | def testConstructorOneIssue(self):
|
---|
144 | utilset = {}
|
---|
145 | weightset = {}
|
---|
146 | utilset[self.ISS1]= self.value1Utils
|
---|
147 | weightset[self.ISS1]=Decimal(1)
|
---|
148 | LinearAdditiveUtilitySpace(self.smalldomain, self.NAME, utilset, weightset,
|
---|
149 | None)
|
---|
150 |
|
---|
151 | # Empty profile is not allowed since the weights then don't sum up to 1
|
---|
152 | def testConstructoroneIssueWrongWeight(self):
|
---|
153 | utilset = {}
|
---|
154 | weightset = {}
|
---|
155 | utilset[self.ISS1]= self.value1Utils
|
---|
156 | weightset[self.ISS1]=self.WEIGHT1
|
---|
157 | self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.smalldomain, self.NAME, utilset, weightset,
|
---|
158 | self.reservationBid))
|
---|
159 |
|
---|
160 | # Try creating a domain and check isFitting
|
---|
161 | #@Test
|
---|
162 | def testCheckCoversDomain(self):
|
---|
163 | utilset = {}
|
---|
164 | weightset = {}
|
---|
165 |
|
---|
166 | utilset[self.ISS1]=self.value1Utils
|
---|
167 | weightset[self.ISS1]=Decimal(1)
|
---|
168 | space = LinearAdditiveUtilitySpace(
|
---|
169 | self.smalldomain, self.NAME, utilset, weightset, None)
|
---|
170 |
|
---|
171 |
|
---|
172 | # Try creating a domain and check isFitting but there is more issues in our
|
---|
173 | # map than in the actual domain
|
---|
174 | def testCheckCoversWrongDomain(self):
|
---|
175 | utilset = {}
|
---|
176 | weightset = {}
|
---|
177 |
|
---|
178 | utilset[self.ISS1]=self.value1Utils
|
---|
179 | weightset[self.ISS1]= Decimal(1)
|
---|
180 | self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(
|
---|
181 | self.domain, self.NAME, utilset, weightset, self.reservationBid))
|
---|
182 |
|
---|
183 | # Empty profile is not allowed since the weights then don't sum up to 1
|
---|
184 | def testUtility(self):
|
---|
185 | space = LinearAdditiveUtilitySpace(
|
---|
186 | self.domain, self.NAME, self.utils, self.weights, self.reservationBid)
|
---|
187 |
|
---|
188 | bid = Mock()
|
---|
189 | issues = [self.ISS1]
|
---|
190 | bid.getIssues = Mock(return_value = issues)
|
---|
191 | bid.getValue = Mock(return_value = self.i1v1)
|
---|
192 | self.assertEqual(self.WEIGHT1 * self.I1V1UTIL, space.getUtility(bid))
|
---|
193 |
|
---|
194 | def testPreferred(self) :
|
---|
195 | issuevalues = {}
|
---|
196 | issuevalues[self.ISS1]=self.i1v1
|
---|
197 | issuevalues[self.ISS2]=self.i2v2
|
---|
198 | bid1 = Bid(issuevalues)
|
---|
199 | issuevalues1 = {}
|
---|
200 | issuevalues1[self.ISS1]= self.i1v2
|
---|
201 | issuevalues1[self.ISS2]= self.i2v1
|
---|
202 | bid2 = Bid(issuevalues1)
|
---|
203 | self.assertTrue(self.utilspace1.isPreferredOrEqual(bid1, bid2))
|
---|
204 | self.assertTrue(self.utilspace1.isPreferredOrEqual(bid1, bid1))
|
---|
205 | self.assertTrue(self.utilspace1.isPreferredOrEqual(bid2, bid2))
|
---|
206 | self.assertFalse(self.utilspace1.isPreferredOrEqual(bid2, bid1))
|
---|
207 | self.assertFalse(self.utilspace1.isPreferredOrEqual(Bid({}), bid1))
|
---|
208 |
|
---|
209 | def testPartialBidUtilityTest(self):
|
---|
210 | space = LinearAdditiveUtilitySpace(
|
---|
211 | self.domain, self.NAME, self.utils, self.weights, self.reservationBid)
|
---|
212 | issuevalues = {}
|
---|
213 | issuevalues[self.ISS1]= self.i1v1
|
---|
214 | bid = Bid(issuevalues)
|
---|
215 | self.assertEqual(self.WEIGHT1* self.I1V1UTIL, space.getUtility(bid))
|
---|
216 |
|
---|
217 | def testLoadFullWithJson(self) :
|
---|
218 | serialized = Path("test/resources/party1.json").read_text("utf-8")
|
---|
219 | jsonobj=json.loads(serialized)
|
---|
220 | space = self.pyson.parse(jsonobj, Profile)
|
---|
221 |
|
---|
222 | def testLoadFullWithJsonNumber(self) :
|
---|
223 | serialized = Path("test/resources/japantrip1.json").read_text("utf-8")
|
---|
224 | jsonobj=json.loads(serialized)
|
---|
225 | profile:Profile = self.pyson.parse(jsonobj, Profile)
|
---|
226 |
|
---|
227 |
|
---|
228 | def testResBidWithNonsenseIssue(self) :
|
---|
229 | resBid = Bid({"nonsense": self.i1v1})
|
---|
230 | self.assertRaises(ValueError,
|
---|
231 | lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, self.utils, self.weights, resBid))
|
---|
232 |
|
---|
233 | def testResBidWithWrongValueType(self):
|
---|
234 | resBid = Bid({self.ISS1: NumberValue(Decimal(0))})
|
---|
235 | self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, self.utils, self.weights, resBid))
|
---|
236 |
|
---|
237 | def testResBidWithNonsenseValue(self):
|
---|
238 | resBid = Bid({self.ISS1: DiscreteValue("nonsense")})
|
---|
239 | self.assertRaises(ValueError, lambda:LinearAdditiveUtilitySpace(self.domain, self.NAME, self.utils, self.weights, resBid))
|
---|
240 |
|
---|