source: src/main/webapp/utilstable.xhtml@ 26

Last change on this file since 26 was 26, checked in by bart, 4 years ago

Fixed memory leak. MOPAC2. removed jcenter build dependencies

File size: 15.0 KB
Line 
1<?xml version="1.0" encoding="UTF-8"?>
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3<head>
4<title>Show tournament results table</title>
5<link rel="stylesheet" type="text/css" href="style.css" />
6</head>
7<style>
8canvas {
9 -moz-user-select: none;
10 -webkit-user-select: none;
11 -ms-user-select: none;
12}
13</style>
14<body>
15 <div class="noscript">
16 <h2 style="color: #ff0000">Seems your browser doesn't support
17 Javascript! Websockets rely on Javascript being enabled. Please
18 enable Javascript and reload this page!</h2>
19 </div>
20 <h1 id="header">Tournament results</h1>
21
22 Progress:
23 <div id="progress">Waiting for log file name</div>
24 <br />
25
26 <table id="outcomes">
27 <thead>
28 <tr>
29 <th align="center">sess / agree</th>
30 <th align="center">accepted bid in session</th>
31 <th align="center">party utility - penalty</th>
32 </tr>
33 </thead>
34 <tbody id="outcomeslist">
35 <!-- one row for each session run added by script -->
36 </tbody>
37
38 </table>
39</body>
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56<!-- Script to get/update the table using the query given in the URL. -->
57<script type="application/javascript">
58
59
60
61
62 <![CDATA[
63 "use strict";
64
65
66
67 var ws = null;
68 var SHAOP=false;
69 /* dict with key=profile URL and value = the downloaded (json) profile */
70 var profiles = {};
71
72 <!-- Most functions are reactive programming written out-->
73
74 /**
75 Called when the page opens.
76 */
77 function init() {
78
79 var id=document.location.search;
80 id = location.search.split('?')[1]
81 if (id==undefined) return;
82
83 // load the log file with given id
84 document.getElementById('header').innerHTML="Tournament results table of "+id;
85
86 var url = "log/"+id+".json";
87 setStatus("Downloading file "+url);
88 var request=new XMLHttpRequest();
89 request.responseType = 'text';
90 request.open('Get', url)
91 request.onload=function() {
92 if (request.status!=200) {
93 setStatus("Failed to fetch "+url+":" +request.status+" "+request.statusText)
94 return;
95 }
96 processLogFile(JSON.parse(request.response));
97 }
98 request.send();
99 }
100
101
102 /**
103 Figure out the protocol contents and parse accordingly.
104 */
105 function processLogFile(json) {
106 if (json['AllPermutationsState']!=undefined) {
107 processAPP(json['AllPermutationsState']);
108 } else {
109 setStatus("Unknown log file contents "+Object.keys(json));
110 }
111 }
112
113 /**
114 Handle AllPermutationsState protocol result.
115 */
116 function processAPP(json) {
117 setStatus("processing the log file");
118
119 if (json['toursettings']['AllPermutationsSettings']['sessionsettings']['SHAOPSettings']!=undefined) {
120 SHAOP=true;
121 }
122
123 // collect all profiles from all profileslists
124 var profiles= [];
125 for (const profilelist of json['toursettings']['AllPermutationsSettings']['profileslists']) {
126 profiles = profiles.concat(profilelist['ProfileList']);
127 }
128 getAllProfiles(json, profiles);
129 }
130
131 /**
132 Step 1, get all profiles. Repeats recursiuvely till profileurls is empty.
133 Note just calling the function again without recurse would be better.
134 Problem is that we call this after we got the profile to ensure we
135 have all profiles at the end.
136 @param profileurls the list of profile urls that need to be fetched.
137 @param profiles a dictionary of profiles . Key is profileurl, value the profile. Should
138 be initially {}
139 returns only after all profiles have been fetched.
140 */
141 function getAllProfiles(json, profileurls) {
142 for (var n in profileurls) {
143 getProfile(profileurls[n], function() {fillRows(json)} );
144 }
145 }
146
147 /**
148 This function returns immediately and will add the profile to the profiles dictionary some time later.
149 @param profurl the profile URL may have ?partial filter.
150 @param callwhendone function is called (without arguments) when profiles contains no pending anymore
151 */
152 function getProfile(profurl, callwhendone) {
153 profiles[profurl]='pending'; // store ORIGINAL URL
154 var profileurl = profurl.split('?',1)[0]; // hacky, remove complete query.
155
156 setStatus("fetching profile "+profileurl);
157 var ws;
158 if ('WebSocket' in window) {
159 ws = new WebSocket(profileurl);
160 } else if ('MozWebSocket' in window) {
161 ws = new MozWebSocket(profileurl);
162 } else {
163 setStatus('Fatal: WebSocket is not supported by this browser. Please use a newer browser');
164 return;
165 }
166 ws.onmessage = function (event) {
167 ws.close();
168 var profile = JSON.parse(event.data);
169 if (profile['LinearAdditiveUtilitySpace']==undefined) {
170 setStatus('Fatal: profile '+profileurl+" does not contain a LinearAdditiveUtilitySpace.");
171 return;
172 }
173
174 profiles[profurl]=profile['LinearAdditiveUtilitySpace'];
175
176 checkAllProfiles(callwhendone);
177 };
178 ws.onerror=function(event) {
179 ws.close();
180 setStatus('Error fetching profile '+uri+':'+event);
181 }
182 }
183
184 /**
185 Check if there are still pending profiles. If not, callwhendone().
186 */
187 function checkAllProfiles(callwhendone) {
188 for (var key in profiles) {
189 if (profiles[key] == 'pending') return;
190 }
191 callwhendone();
192 }
193
194 /**
195 Step 2, fill the table with util values
196 @param profiles a dictionary of profiles . Key is profileurl, value the profile. Should
197 be initially {}
198 */
199 function fillRows(json)
200 {
201 setStatus("Computing utilities");
202 var table = document.getElementById("outcomeslist");
203 table.innerHTML="";
204 var results = json['results'];
205
206 for (var nr in results) {
207 var result = results[nr];
208 fillBids(table, nr, result);
209 }
210 setStatus("done");
211 }
212
213 /**
214 Add rows for all the bids in the result. Notice that some protocols allow multiple bids to be accepted per session.
215 @param table the table to add the bid results to
216 @param nr the session number
217 @param results the results for this sess. This is a dict with key: partyid (eg "party83+127_0_1_1)
218 and the value the accepted issuevalues (the bid contents). Parties that did not reach an agreement
219 are not in the list.
220 */
221 function fillBids(table, nr, result) {
222 // collect unique bids in agreements.
223 var agreements = result['agreements'];
224
225 //if (Object.keys(agreements).length == 0) {
226 // fillRow(table.insertRow(-1), nr, result, {});
227 // return;
228 //}
229
230 // collect all bids, plus "null" if there are parties without agreements.
231 var bids = [];
232 const participants = result['participants'];
233 for (var pid in participants) {
234 if (pid in agreements) {
235 bids=addSet(bids, agreements[pid]['issuevalues']);
236 } else {
237 bids=addSet(bids, null)
238 }
239 }
240
241 var dealnr='A'.charCodeAt(0);
242 for (const bid of bids) {
243 fillRow(table.insertRow(-1), nr+String.fromCharCode(dealnr++), result, bid);
244 }
245 }
246
247
248
249
250 /**
251 @param row the table row object in the document
252 @param nr the row number
253 @param result a single json result from the APP results section.
254 Contains participants, agreements and error
255 @param agreedbid the bid to show in this row. dict, with hte issuevalues from the bid.
256 If null, this should show parties that did not reach an agreement.
257 */
258 function fillRow(row, nr, result, agreedbid) {
259 row.insertCell(0).innerHTML = nr;
260
261 if (result['error']!=null) {
262 var errstr="session failed:";
263 var err=result['error'];
264 if ("geniusweb.protocol.ProtocolException" in err) {
265 errstr=errstr+err["geniusweb.protocol.ProtocolException"]['cause']['message'];
266 }
267 row.insertCell(-1).innerHTML=errstr;
268 } else {
269 if (agreedbid==null) {
270 row.insertCell(-1).innerHTML = "No deal";
271 } else {
272 row.insertCell(-1).innerHTML = JSON.stringify(agreedbid);
273 }
274 }
275 // fill in the columns. If SHAOP, only the even parties
276 // FIXME this can't be with a MAP
277 const participants = result['participants'];
278 for (var pid in participants) {
279 var partyandprofile = result['participants'][pid];
280 var profile =partyandprofile['profile'];
281 var penalty=result['penalties'][pid];
282 if (penalty==undefined) penalty=0;
283 // make short name for readability
284 var partyname = partyandprofile['party']['partyref'];
285 partyname = partyname.split('/');
286 partyname = partyname[partyname.length-1];
287
288 // we set bid to agreedbid only if party accepted agreedbid.
289 var bid = {};
290 if (pid in result['agreements'] && agreedbid!=null &&
291 deepEqual(agreedbid, result['agreements'][pid]['issuevalues'] ) )
292 bid = agreedbid;
293 addUtilityCell(row.insertCell(-1), bid, partyname, profile, penalty);
294 }
295 }
296
297 /**
298 @param cell the table cell to put the result in
299 @param agreedbid the bid that was agreed on
300 @param partyname the short name of the party
301 @param profileurl the profile url to use for the evaluation of the bid
302 @param bid the accepted bid, not null.
303 @param penalty costs made by the party, to be subtracted from total util.
304 typically these are elicitation costs.
305 */
306 function addUtilityCell(cell, agreedbid, partyname, profileurl, penalty) {
307 var util = utility(profiles[profileurl],agreedbid);
308 var rUtil = Math.round(util*1000000)/1000000;
309 var rPenalty = Math.round( (penalty )*1000000)/1000000 ;
310 cell.innerHTML = rUtil + "-" + rPenalty + " :"+partyname;
311
312 }
313
314
315
316
317
318 /******************* Private support funcs****************/
319
320 /**
321 Set the progress/status text
322 */
323 function setStatus(text) {
324 document.getElementById('progress').innerHTML=text;
325 }
326
327 /*
328 @param sessions a list of IDs (Strings).
329 */
330 function update(sessions) {
331 var table = document.getElementById("sessionsTable");
332 table.innerHTML="";
333 for(var session of sessions) {
334 var row = table.insertRow(-1); //-1 = end
335 row.insertCell(0).innerHTML = session;
336 }
337
338 }
339
340
341
342
343 /**
344 Compute utilityes
345 @param profile the linear additive utility space
346 @param issueValues the bid containing dict with values for the issues
347 @param isResBidAlternative true if the reservation bid is usable as alternative bid
348 . If true, and the issueValues list is empty/null, this returns the utility
349 of the reservation bid instead.
350 @return utility of issueValues. Returns 0 if profile =null or no bid available
351
352 */
353 function utility(profile, issueValues, isResBidAlternative) {
354 if (profile==null) return 0;
355
356 // check if we need the reservationbid.
357 if (issueValues==null || Object.keys(issueValues).length==0) {
358 var resBid = profile['reservationBid'];
359 if (resBid==null) return 0;
360 issueValues = resBid['issuevalues'];
361 }
362
363 if (issueValues==null) return 0;
364
365 var util=0;
366 var weights = profile['issueWeights'];
367
368 for (var issue in issueValues) {
369 util = util + weights[issue] * evaluate(profile['issueUtilities'][issue], issueValues[issue] );
370 }
371 return util;
372 }
373
374 /**
375 Bit hacky, javascript version of evaluator of utility space.
376 Would be much nicer if we could use java here
377 */
378 function evaluate (utilfunc, value) {
379 if (utilfunc == undefined) {
380 return 0;
381 }
382 if (utilfunc['numberutils']!=undefined) {
383 // it's numeric issue. Compute
384 var minval = utilfunc['numberutils']['lowValue'];
385 var minutil = utilfunc['numberutils']['lowUtility'];
386 var maxval = utilfunc['numberutils']['highValue'];
387 var maxutil = utilfunc['numberutils']['highUtility'];
388
389 var alfa = (value-minval) / (maxval - minval) ;
390 alfa = Math.max(Math.min(alfa, 1),0);
391 return alfa * maxutil + (1-alfa) * minutil;
392 }
393 if (utilfunc['discreteutils']!=undefined) {
394 // it's discrete issue. Compute
395 if (value in utilfunc['discreteutils']['valueUtilities'])
396 return utilfunc['discreteutils']['valueUtilities'][value];
397 return 0;
398 }
399 setStatus("Unknown utility function type "+Object.keys(utilfunc));
400
401 }
402
403 /************ util functions to compare dict objects **************/
404 /**
405 @return true if object1, object2 are 'deep equal'. That means,
406 if they are dicts then we check that the keys and their values are equal,
407 recursively.
408 */
409 function deepEqual(object1, object2) {
410 if (object1==null) {
411 return object2==null;
412 }
413 if (object2==null) return false;
414 const keys1 = Object.keys(object1);
415 const keys2 = Object.keys(object2);
416
417 if (keys1.length !== keys2.length) {
418 return false;
419 }
420
421 for (const key of keys1) {
422 const val1 = object1[key];
423 const val2 = object2[key];
424 const areObjects = isObject(val1) && isObject(val2);
425 if (
426 areObjects && !deepEqual(val1, val2) || !areObjects && val1 !== val2
427 ) {
428 return false;
429 }
430 }
431
432 return true;
433 }
434
435 function isObject(object) {
436 return object != null && typeof object === 'object';
437 }
438
439 /**
440 @param list the list to add value to
441 @param value the value to add
442 @return list with the value added, but only if not already in list.
443 Uses deepEqual to check equality of list members
444 */
445 function addSet(list, value) {
446 for (const v of list) {
447 if (deepEqual(v, value))
448 return list;
449 }
450 return list.concat(value);
451 }
452
453 ]]>
454
455
456
457
458 <![CDATA[
459 "use strict";
460 /*
461 This is called when the window DOM is loaded. window.onload runs BEFORE the window is loaded
462 and therefore is too early to remove the noscript message.
463 */
464 document.addEventListener("DOMContentLoaded", function() {
465 // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
466 var noscripts = document.getElementsByClassName("noscript");
467 for (var i = 0; i < noscripts.length; i++) {
468 noscripts[i].parentNode.removeChild(noscripts[i]);
469 }
470 init();
471
472 }, false);
473 ]]>
474
475
476</script>
477
478
479</html>
Note: See TracBrowser for help on using the repository browser.