1 | package geniusweb.opponentmodel.bayesian;
|
---|
2 |
|
---|
3 | import static org.junit.Assert.assertEquals;
|
---|
4 | import static org.junit.Assert.assertNotNull;
|
---|
5 | import static org.junit.Assert.assertTrue;
|
---|
6 | import static org.mockito.Mockito.mock;
|
---|
7 |
|
---|
8 | import java.io.IOException;
|
---|
9 | import java.math.BigDecimal;
|
---|
10 | import java.nio.charset.StandardCharsets;
|
---|
11 | import java.nio.file.Files;
|
---|
12 | import java.nio.file.Paths;
|
---|
13 | import java.util.ArrayList;
|
---|
14 | import java.util.Arrays;
|
---|
15 | import java.util.Collection;
|
---|
16 | import java.util.Collections;
|
---|
17 | import java.util.HashMap;
|
---|
18 | import java.util.LinkedList;
|
---|
19 | import java.util.List;
|
---|
20 | import java.util.Map;
|
---|
21 | import java.util.Random;
|
---|
22 | import java.util.TreeMap;
|
---|
23 | import java.util.stream.Collectors;
|
---|
24 |
|
---|
25 | import org.junit.Ignore;
|
---|
26 | import org.junit.Test;
|
---|
27 |
|
---|
28 | import com.fasterxml.jackson.databind.ObjectMapper;
|
---|
29 |
|
---|
30 | import geniusweb.actions.Offer;
|
---|
31 | import geniusweb.actions.PartyId;
|
---|
32 | import geniusweb.issuevalue.Bid;
|
---|
33 | import geniusweb.issuevalue.DiscreteValue;
|
---|
34 | import geniusweb.issuevalue.DiscreteValueSet;
|
---|
35 | import geniusweb.issuevalue.Domain;
|
---|
36 | import geniusweb.issuevalue.NumberValue;
|
---|
37 | import geniusweb.issuevalue.NumberValueSet;
|
---|
38 | import geniusweb.issuevalue.Value;
|
---|
39 | import geniusweb.issuevalue.ValueSet;
|
---|
40 | import geniusweb.profile.utilityspace.UtilitySpace;
|
---|
41 | import geniusweb.progress.Progress;
|
---|
42 | import tudelft.utilities.immutablelist.ImmutableList;
|
---|
43 | import tudelft.utilities.immutablelist.Outer;
|
---|
44 | import tudelft.utilities.junit.GeneralTests;
|
---|
45 |
|
---|
46 | public class BayesianOppModelTest extends GeneralTests<BayesianOpponentModel> {
|
---|
47 | private final static String ISS1 = "issue1";
|
---|
48 | private final static String ISS2 = "issue2";
|
---|
49 | private static final DiscreteValue I1V1 = new DiscreteValue("i1v1");
|
---|
50 | private static final DiscreteValue I1V2 = new DiscreteValue("i1v2");
|
---|
51 | private static final DiscreteValue I2V1 = new DiscreteValue("i2v1");
|
---|
52 | private static final DiscreteValue I2V2 = new DiscreteValue("i2v2");
|
---|
53 | private static final DiscreteValue I1V2b = new DiscreteValue("i1v2b");
|
---|
54 | private static final Progress progress = mock(Progress.class);
|
---|
55 | private static final PartyId other = new PartyId("other");
|
---|
56 | private static final Random rnd = new Random();
|
---|
57 |
|
---|
58 | private static Domain domain, domain2, domain3;
|
---|
59 | private static BayesianOpponentModel oppModel1, oppModel1b, oppModel2,
|
---|
60 | oppModel3;
|
---|
61 |
|
---|
62 | private final static List<List<Bid>> list = new LinkedList<>();
|
---|
63 |
|
---|
64 | private final static Bid bid1, bid2, bid3;
|
---|
65 | private static final BigDecimal HALF = new BigDecimal("0.5");
|
---|
66 |
|
---|
67 | static {
|
---|
68 | Map<String, ValueSet> issues = new HashMap<>();
|
---|
69 | Collection<DiscreteValue> discretevalues1 = new LinkedList<>();
|
---|
70 | discretevalues1.add(I1V1);
|
---|
71 | discretevalues1.add(I1V2);
|
---|
72 | DiscreteValueSet values1 = new DiscreteValueSet(discretevalues1);
|
---|
73 | issues.put(ISS1, values1);
|
---|
74 | NumberValueSet values2 = new NumberValueSet(BigDecimal.ZERO,
|
---|
75 | BigDecimal.TEN, new BigDecimal("0.3"));
|
---|
76 | issues.put(ISS2, values2);
|
---|
77 | domain = new Domain("test", issues);
|
---|
78 | domain2 = new Domain("test2", issues);
|
---|
79 |
|
---|
80 | // slightly different issue1
|
---|
81 | issues = new HashMap<>();
|
---|
82 | discretevalues1 = new LinkedList<>();
|
---|
83 | discretevalues1.add(I1V1);
|
---|
84 | discretevalues1.add(I1V2b);
|
---|
85 | values1 = new DiscreteValueSet(discretevalues1);
|
---|
86 | issues.put(ISS1, values1);
|
---|
87 | values2 = new NumberValueSet(BigDecimal.ZERO, BigDecimal.TEN,
|
---|
88 | new BigDecimal("0.3"));
|
---|
89 | issues.put(ISS2, values2);
|
---|
90 | domain3 = new Domain("test", issues);
|
---|
91 |
|
---|
92 | // all bids are for domain
|
---|
93 | Map<String, Value> issuevalues = new HashMap<>();
|
---|
94 | issuevalues.put(ISS1, I1V1);
|
---|
95 | issuevalues.put(ISS2, new NumberValue(new BigDecimal("1.2")));
|
---|
96 | bid1 = new Bid(issuevalues);
|
---|
97 |
|
---|
98 | issuevalues.put(ISS1, I1V1);
|
---|
99 | issuevalues.put(ISS2, new NumberValue(new BigDecimal("1.5")));
|
---|
100 | bid2 = new Bid(issuevalues);
|
---|
101 |
|
---|
102 | issuevalues.put(ISS1, I1V2);
|
---|
103 | issuevalues.put(ISS2, new NumberValue(new BigDecimal("1.5")));
|
---|
104 | bid3 = new Bid(issuevalues);
|
---|
105 |
|
---|
106 | oppModel1 = new BayesianOpponentModel(domain);
|
---|
107 | oppModel1b = new BayesianOpponentModel(domain);
|
---|
108 | oppModel2 = new BayesianOpponentModel(domain)
|
---|
109 | .with(new Offer(other, bid1), progress);
|
---|
110 | oppModel3 = new BayesianOpponentModel(domain)
|
---|
111 | .with(new Offer(other, bid2), progress);
|
---|
112 |
|
---|
113 | }
|
---|
114 |
|
---|
115 | private ObjectMapper jackson = new ObjectMapper();
|
---|
116 |
|
---|
117 | @Override
|
---|
118 | public List<List<BayesianOpponentModel>> getGeneralTestData() {
|
---|
119 | return Arrays.asList(Arrays.asList(oppModel1, oppModel1b),
|
---|
120 | Arrays.asList(oppModel2), Arrays.asList(oppModel3));
|
---|
121 | }
|
---|
122 |
|
---|
123 | @Override
|
---|
124 | public List<String> getGeneralTestStrings() {
|
---|
125 | // not much we can test. The internals are too detailed and situation
|
---|
126 | // specific
|
---|
127 | return Arrays.asList("BayesianOpponentModel.*",
|
---|
128 | "BayesianOpponentModel.*", "BayesianOpponentModel.*");
|
---|
129 | }
|
---|
130 |
|
---|
131 | @Test(expected = NullPointerException.class)
|
---|
132 | public void smokeTestNull() {
|
---|
133 | new BayesianOpponentModel().with((Domain) null, null);
|
---|
134 | }
|
---|
135 |
|
---|
136 | @Test
|
---|
137 | public void testNormalization() {
|
---|
138 | System.out.println(oppModel1.getWeights());
|
---|
139 | }
|
---|
140 |
|
---|
141 | @SuppressWarnings("unused")
|
---|
142 | @Test
|
---|
143 | public void smokeTest() {
|
---|
144 | new BayesianOpponentModel(domain);
|
---|
145 | }
|
---|
146 |
|
---|
147 | @Test
|
---|
148 | public void testEmptyModel() {
|
---|
149 | BayesianOpponentModel oppModel = new BayesianOpponentModel()
|
---|
150 | .with(domain, null);
|
---|
151 | // somewhere 0.4-0.6 should be fine.
|
---|
152 | // exact value varies with the exact hypotheses
|
---|
153 | assertEquals(0.5, oppModel.getUtility(bid1).doubleValue(), 0.1);
|
---|
154 | assertEquals(0.5, oppModel.getUtility(bid2).doubleValue(), 0.1);
|
---|
155 | }
|
---|
156 |
|
---|
157 | @Test
|
---|
158 | public void testEmptyBid() {
|
---|
159 | Bid bid = new Bid(Collections.emptyMap());
|
---|
160 | assertEquals(0d, oppModel1.getUtility(bid).doubleValue(), 0.000001);
|
---|
161 | }
|
---|
162 |
|
---|
163 | @Test
|
---|
164 | public void testUpdate() {
|
---|
165 |
|
---|
166 | BayesianOpponentModel newOppModel = oppModel1
|
---|
167 | .with(new Offer(other, bid1), progress);
|
---|
168 | assertTrue(oppModel1.getUtility(bid1).doubleValue() < newOppModel
|
---|
169 | .getUtility(bid1).doubleValue());
|
---|
170 | assertTrue(newOppModel.getBidHistory().size() == 1);
|
---|
171 |
|
---|
172 | // and if we continue with next bid, the first should still hold
|
---|
173 | // and the next bid should also improve
|
---|
174 | BayesianOpponentModel newOppModel2 = newOppModel
|
---|
175 | .with(new Offer(other, bid2), progress);
|
---|
176 | assertTrue(oppModel1.getUtility(bid1).doubleValue() < newOppModel2
|
---|
177 | .getUtility(bid1).doubleValue());
|
---|
178 | assertTrue(oppModel1.getUtility(bid2).doubleValue() < newOppModel2
|
---|
179 | .getUtility(bid2).doubleValue());
|
---|
180 |
|
---|
181 | assertTrue(newOppModel2.getBidHistory().size() == 2);
|
---|
182 |
|
---|
183 | // bid1= (i1v1, 1.2), bid2= (i1v1, 1.5)
|
---|
184 | // so bidding keeps ISS1 but changes ISS2.
|
---|
185 | // check that ISS1 increases weight and ISS2 decreases
|
---|
186 | assertTrue(oppModel1.getWeights().get(ISS1).doubleValue() < newOppModel2
|
---|
187 | .getWeights().get(ISS1).doubleValue());
|
---|
188 |
|
---|
189 | // it may not work like this, due to renormalization steps.
|
---|
190 | // assertTrue(oppModel1.getWeights().get(ISS2).doubleValue() < newOppModel2
|
---|
191 | // .getWeights().get(ISS2).doubleValue());
|
---|
192 |
|
---|
193 | }
|
---|
194 |
|
---|
195 | @Test
|
---|
196 | public void testPartialBidUpdate() {
|
---|
197 | BayesianOpponentModel oppModel = oppModel1.with(new Offer(other, bid1),
|
---|
198 | progress);
|
---|
199 | Bid partialbid = new Bid(ISS1, I1V1);
|
---|
200 | oppModel.with(new Offer(other, partialbid), progress);
|
---|
201 | }
|
---|
202 |
|
---|
203 | @Test
|
---|
204 | public void testStableName() {
|
---|
205 | String name = oppModel1.getName();
|
---|
206 | assertNotNull(name);
|
---|
207 | assertEquals(name, oppModel1.getName());
|
---|
208 | }
|
---|
209 |
|
---|
210 | @Test
|
---|
211 | public void testWeightsNormalized() {
|
---|
212 | assertEquals(1d, oppModel1.getWeights().values().stream()
|
---|
213 | .mapToDouble(f -> f).sum(), 0.000001);
|
---|
214 | }
|
---|
215 |
|
---|
216 | /**
|
---|
217 | * duration/stress test with random bids
|
---|
218 | */
|
---|
219 | @Test
|
---|
220 | public void testRepeatedRandomBidding() throws IOException {
|
---|
221 | // use japantrip as it has numbervalue as well
|
---|
222 | String profile = new String(
|
---|
223 | Files.readAllBytes(
|
---|
224 | Paths.get("src/test/resources/japantrip.json")),
|
---|
225 | StandardCharsets.UTF_8);
|
---|
226 | Domain dom = jackson.readValue(profile, Domain.class);
|
---|
227 | BayesianOpponentModel model = new BayesianOpponentModel(dom);
|
---|
228 |
|
---|
229 | for (int time = 1; time < 100; time++) {
|
---|
230 | Bid bid = getRandomBid(dom, null, null);
|
---|
231 | double util = model.getUtility(bid).doubleValue();
|
---|
232 | System.out.println(util);
|
---|
233 | assertTrue(util > 0d && util < 1d);
|
---|
234 | model = model.with(new Offer(other, bid), null);
|
---|
235 | }
|
---|
236 | }
|
---|
237 |
|
---|
238 | /**
|
---|
239 | * duration/stress test with similar bids
|
---|
240 | */
|
---|
241 |
|
---|
242 | @Test
|
---|
243 | public void testRepeateSimilarBidding() throws IOException {
|
---|
244 | // use japantrip as it has numbervalue as well
|
---|
245 | String profile = new String(
|
---|
246 | Files.readAllBytes(
|
---|
247 | Paths.get("src/test/resources/japantrip.json")),
|
---|
248 | StandardCharsets.UTF_8);
|
---|
249 | Domain dom = jackson.readValue(profile, Domain.class);
|
---|
250 | BayesianOpponentModel model = new BayesianOpponentModel(dom);
|
---|
251 |
|
---|
252 | for (int time = 1; time < 100; time++) {
|
---|
253 | Bid bid = getRandomBid(dom, "purpose",
|
---|
254 | new DiscreteValue("sightseeing"));
|
---|
255 | double util = model.getUtility(bid).doubleValue();
|
---|
256 | // System.out.println(util);
|
---|
257 | assertTrue(util > 0d && util < 1d);
|
---|
258 | model = model.with(new Offer(other, bid), null);
|
---|
259 | }
|
---|
260 | }
|
---|
261 |
|
---|
262 | /**
|
---|
263 | *
|
---|
264 | * @param dom the {@link Domain}
|
---|
265 | * @param specialissue an issue name. If not null, value is used for this
|
---|
266 | * issue.
|
---|
267 | * @param value the value to use for specialissue.
|
---|
268 | *
|
---|
269 | * @return random bid from domain. Notice that bidspace depends on profile
|
---|
270 | * so we should not be using bidspace. This makes this a bit clumsy
|
---|
271 | */
|
---|
272 | private Bid getRandomBid(Domain dom, String specialissue, Value value) {
|
---|
273 | Map<String, Value> map = new HashMap<>();
|
---|
274 | for (String issue : dom.getIssues()) {
|
---|
275 | Value val = value;
|
---|
276 | if (!issue.equals(specialissue)) {
|
---|
277 | ValueSet values = dom.getValues(issue);
|
---|
278 | val = values.get(rnd.nextInt(values.size().intValue()));
|
---|
279 | }
|
---|
280 | map.put(issue, val);
|
---|
281 | }
|
---|
282 | return new Bid(map);
|
---|
283 | }
|
---|
284 |
|
---|
285 | /**
|
---|
286 | * Generate all bids from a space, sort to order, and pump them in that
|
---|
287 | * order into Bayesian model. In the end the model should "match" with the
|
---|
288 | * original space.
|
---|
289 | *
|
---|
290 | * @return
|
---|
291 | * @throws IOException
|
---|
292 | */
|
---|
293 | @Ignore
|
---|
294 | @Test
|
---|
295 | public void testPumpBidsInOrder() throws IOException {
|
---|
296 | String profile = new String(
|
---|
297 | Files.readAllBytes(
|
---|
298 | Paths.get("src/test/resources/japantrip1.json")),
|
---|
299 | StandardCharsets.UTF_8);
|
---|
300 | UtilitySpace japantrip = jackson.readValue(profile, UtilitySpace.class);
|
---|
301 | BayesianOpponentModel model = new BayesianOpponentModel(
|
---|
302 | japantrip.getDomain());
|
---|
303 |
|
---|
304 | Map<Double, Bid> sorted = getAllBids(japantrip);
|
---|
305 | for (Double val : sorted.keySet()) {
|
---|
306 | model = model.with(new Offer(other, sorted.get(val)), null);
|
---|
307 | }
|
---|
308 | System.out.println(model);
|
---|
309 |
|
---|
310 | Map<String, Double> weights = model.getWeights();
|
---|
311 | System.out.println(weights);
|
---|
312 |
|
---|
313 | // you would expect this result but in practice it doesn't
|
---|
314 | assertEquals(weights.get("place").doubleValue(), 0.57, 0.1);
|
---|
315 | assertEquals(weights.get("budget").doubleValue(), 0.09, 0.1);
|
---|
316 | assertEquals(weights.get("days").doubleValue(), 0.17, 0.1);
|
---|
317 | assertEquals(weights.get("purpose").doubleValue(), 0.17, 0.1);
|
---|
318 |
|
---|
319 | }
|
---|
320 |
|
---|
321 | /**
|
---|
322 | *
|
---|
323 | * @param utllspace the {@link UtilitySpace} to be compiled
|
---|
324 | * @return all bids, sorted from high to low utility. Notice, normally you
|
---|
325 | * would use AllBidsList but we can not import that module here.
|
---|
326 | * Warning, enumerating and sorting all bids in a space this way is
|
---|
327 | * too expensive for use in real time negotiation.
|
---|
328 | */
|
---|
329 | private Map<Double, Bid> getAllBids(UtilitySpace utllspace) {
|
---|
330 | Domain dom = utllspace.getDomain();
|
---|
331 | List<String> issues = new ArrayList<String>(
|
---|
332 | utllspace.getDomain().getIssues());
|
---|
333 | List<ImmutableList<Value>> values = issues.stream()
|
---|
334 | .map(issue -> dom.getValues(issue))
|
---|
335 | .collect(Collectors.toList());
|
---|
336 |
|
---|
337 | Outer<Value> allBidValues = new Outer<Value>(values);
|
---|
338 | Map<Double, Bid> bidmap = new TreeMap<>(Collections.reverseOrder());
|
---|
339 | for (ImmutableList<Value> bidvals : allBidValues) {
|
---|
340 | Map<String, Value> vals = new HashMap<>();
|
---|
341 | for (int n = 0; n < issues.size(); n++) {
|
---|
342 | vals.put(issues.get(n), bidvals.get(n));
|
---|
343 | }
|
---|
344 | Bid bid = new Bid(vals);
|
---|
345 | bidmap.put(utllspace.getUtility(bid).doubleValue(), bid);
|
---|
346 | }
|
---|
347 |
|
---|
348 | return bidmap;
|
---|
349 | }
|
---|
350 | }
|
---|