source: src/main/webapp/plotlog.xhtml@ 46

Last change on this file since 46 was 46, checked in by ruud, 5 days ago

All TimeDependent parties now support the nonlinear SumOfGroupsUtilitySpace. Example Nonlinear space in the computer domain

File size: 15.1 KB
RevLine 
[45]1<?xml version="1.0" encoding="UTF-8"?>
2<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
3<head>
4<title>Plot log results</title>
5<script src="Chart.min_2.8.0.js"></script>
6<script src="utils.js"></script>
7</head>
8<style>
9canvas {
10 -moz-user-select: none;
11 -webkit-user-select: none;
12 -ms-user-select: none;
13}
14</style>
15<body>
16 <div class="noscript">
17 <h2 style="color: #ff0000">Seems your browser doesn't support
18 Javascript! Websockets rely on Javascript being enabled. Please
19 enable Javascript and reload this page!</h2>
20 </div>
21 <h1 id="header">Graph of log results</h1>
22
23 Progress:
24 <div id="progress">Waiting for log file name</div>
25 <br />
26
27 <div>Graph shows utilities of bids in offers and accepts.
28 Points in the graph indicate the party that placed the offer.
29 Plotted utilities do not include possible
30 elicitation costs.</div>
31 <div style="width: 100%;">
32 <canvas id="canvas"></canvas>
33 </div>
34
35 Agreement:
36 <div id="agreement" style="display: inline;">?</div>
37</body>
38
39<!-- Script to get/update the SESSIONS list and put it into the table. -->
40<script type="application/javascript">
41
42
43
44
45
46
47
48
49
50
51
52 <![CDATA[
53 "use strict";
54
55
56
57 var ws = null;
[46]58
59 function arrayequals(array1, array2) {
60 return array1.length === array2.length && array1.every(function(value, index) { return value === array2[index]})
61 }
[45]62
63 <!-- Most functions are reactive programming written out-->
64
65 function connect() {
66 var query = new URLSearchParams(location.search);
67 var id=query.get('id');
68 if (id==undefined) {
69 alert("missing ?id=<logfile> in URL");
70 return;
71 }
72
73 // load the log file with given id
74 document.getElementById('header').innerHTML="Graph of log "+id;
75
76 var url = "log/"+id+".json";
77 setStatus("Downloading file "+url);
78 var request=new XMLHttpRequest();
79 request.responseType = 'text';
80 request.open('Get', url)
81 request.onload=function() {
82 if (request.status!=200) {
83 setStatus("Failed to fetch "+url+":" +request.status+" "+request.statusText)
84 return;
85 }
86 processLogFile(JSON.parse(request.response));
87 }
88 request.send();
89 }
90
91
92 /**
93 Figure out the protocol contents and parse accordingly.
94 */
95 function processLogFile(json) {
96
97 if (json['SAOPState']!=undefined) {
98 processStandard(json['SAOPState'], 'SAOP');
99 } else if (json['SHAOPState']!=undefined) {
100 processSHAOP(json['SHAOPState']);
101 } else if (json['MOPACState']!=undefined) {
102 // just use the SAOP processor, which shows the
103 // utilities of all Offers in sequence.
104 processStandard(json['MOPACState'], 'MOPAC');
105 } else{
106 setStatus("Unknown log file contents "+Object.keys(json));
107 }
108 }
109
110 /**
111 Handle standard protocol result.
112 @param protocol either SAOP or MOPAC or another SAOP-like protocol
113 */
114 function processStandard(json, protocol) {
115 var partyprofiles={}
116 var profs=json['partyprofiles'];
117 for (var party in profs) {
118 partyprofiles[party]=withoutPartial(profs[party]['profile']);
119 }
120
121 getProfiles(json, partyprofiles, protocol);
122 }
123
124
125 /**
126 Handle SHAOP protocol result. Get only SHAOP profiles. Remove all 'partial=XX' from relevant profiles.
127 */
128 function processSHAOP(json) {
129 var partyprofiles={}
130 var teams=json['settings']['SHAOPSettings']['participants'];
131 for (var partyid in json['partyNumbers']) {
132 var nr= json['partyNumbers'][partyid];
133 if ( (nr & 1 != 0)) continue; // skip COB parties
134 var shaop=teams[nr/2]['TeamInfo']['parties'][0];
135 var profile = withoutPartial(shaop['profile']); // hacky, remove complete query.
136 partyprofiles[partyid]=profile;
137 }
138
139
140 getProfiles(json, partyprofiles, "SHAOP");
141 }
142
143
144 /**
145 @param uristr a string with an uri that may have ?partial=XX
146 @return the given uristr without the "partia=XXl" value
147 */
148 function withoutPartial(uristr) {
149 var profuri=new URL(uristr);
150 var searchParams = new URLSearchParams(profuri.search);
151 searchParams.delete("partial");
152 profuri.search = searchParams.toString();
153 return profuri.toString();
154
155 }
156
157 /**
158 Get the partyprofiles.
159 @param json the json protocol result
160 @param partyprofileURIs dict with the websocket addresses for each party'as profile
161 @param partyprofiles dict with each party's profile. Initially should be {}
162 @param protocol protocolname, eg "SAOP"
163 */
164 function getProfiles(json, partyprofileURIs, protocol, partyprofiles={} ) {
165 var keys = Object.keys(partyprofileURIs);
166 if (keys.length==0) {
167 plotResults(computeUtils(json, partyprofiles), isAgreement(json, protocol));
168 return;
169 }
170 var party = keys[0];
171 var uri=partyprofileURIs[party];
172 delete partyprofileURIs[party];
173
174 setStatus("fetching profile "+uri);
175 if ('WebSocket' in window) {
176 ws = new WebSocket(uri);
177 } else if ('MozWebSocket' in window) {
178 ws = new MozWebSocket(uri);
179 } else {
180 setStatus('Fatal: WebSocket is not supported by this browser. Please use a newer browser');
181 return;
182 }
183 ws.onmessage = function (event) {
184 var profile = JSON.parse(event.data);
[46]185 if (profile['LinearAdditiveUtilitySpace']==undefined && profile['SumOfGroupsUtilitySpace']==undefined) {
186 setStatus('Fatal: profile '+uri+" does not contain a LinearAdditive- or SumOfGroups UtilitySpace.");
[45]187 return;
188 }
[46]189 partyprofiles[party]=profile;
[45]190 getProfiles(json, partyprofileURIs, protocol, partyprofiles);
191 };
192 ws.onerror=function(event) {
193 setStatus('Error fetching profile '+uri+':'+event);
194 }
195 }
196
197 /**
198 @param logfile the logfile content, but below the protocol header
199 @param protocol the protocol name, eg SAOP, SHAOP, MOPAC
200 @return 'Yes' if there is agreement, 'No' if there is no agreement at all,
201 or 'Partial' if part of the parties agreed and part did not.
202 or '?' if unsupported protocol
203 */
204 function isAgreement(logfile, protocol) {
205 switch (protocol) {
206 case "SAOP":
207 case "SHAOP":
208 var actions=logfile['actions'];
209 var lastact = actions[actions.length -1];
210 return 'Accept' in lastact ? 'Yes':'No';
211 case "MOPAC":
212 var finalstate=logfile['phase'][Object.keys(logfile['phase'])[0]]['partyStates'];
213 var nagrees=Object.keys(finalstate['agreements']).length;
214 var nparties=Object.keys(finalstate['powers']).length;
215 if (nagrees==nparties) return "Yes";
216 if (nagrees==0) return "No"
217 return "Partial";
218 default:
219 return "?";
220 }
221 }
222 /**
223 @json the log file contents
224 @param partyprofiles a dict with key=party id and value a the (linear additive) profile
225 @return a list [utilities, biddingparties] where utilities is a dict with keys: partyID
226 and value: list of utilities . biddingparties is a list [partyid] which is the
227 bidding party for each round.
228 */
229 function computeUtils(json, partyprofiles) {
230
231 setStatus("Computing utilities.")
232 var utilities={};
233 var biddingparties=[];
234 for (var party in partyprofiles) {
235 utilities[party]=[];
236 }
237 var actions = json['actions'];
238 for (var n in actions) {
239 var action = actions[n];
240 var item;
241 if ('Offer' in action)
242 action= action['Offer'];
243 else if ('Accept' in action)
244 action= action['Accept'];
245 else
246 continue;
247 var issueutilities = action['bid']['issuevalues']
248 for (var party in partyprofiles) {
249 utilities[party].push(utility(partyprofiles[party],issueutilities));
250 }
251 biddingparties.push(action['actor']);
252 }
253 return [utilities, biddingparties];
254
255
256 }
257
258 var chartColors = [
259 'rgb(255, 99, 132)',
260 'rgb(3, 192, 12)',
261 'rgb(54, 162, 235)',
262 'rgb(153, 102, 255)',
263 'rgb(201, 203, 207)',
264 'rgb(255, 159, 64)',
265 'rgb(255, 205, 86)'
266 ];
267
268 function color(i) {
269 return chartColors[i % chartColors.length];
270 }
271
272 /**
273 @param [utilities, parties] utilities=the dict with key=party id of the utilities of all steps for each party )
274 and parties a list of biddingparty for each value in utilities lists.
275 @param isAgreement either Yes, No, Partial or ?
276 */
277
278 function plotResults( [utilities, biddingparties], isAgreement) {
279 setStatus("Plotting" )
280
281 document.getElementById('agreement').innerHTML=isAgreement;
282
283 var parties=Object.keys(utilities);
284 var thelabels=[];
285 for (var i=1; i<=utilities[parties[0]].length; i++) { thelabels.push(i); }
286
287 var thedatasets=[];
288 var partynr=0;
289 for (var party in utilities) {
290 var pointsizes=[];
291 for (var p of biddingparties) {
292 pointsizes.push(p==party ? 4:0);
293 }
294 var dataset={
295 label: party,
296 lineTension:0, // straight lines between the points
297 backgroundColor: color(partynr),
298 borderColor: color(partynr),
299 data: utilities[party],
300 pointRadius: pointsizes,
301 fill: false
302 };
303 thedatasets.push(dataset);
304 partynr++;
305 }
306
307 var config = {
308 type: 'line',
309 data: {
310 labels: thelabels,
311 datasets: thedatasets
312 },
313 options: {
314 responsive: true,
315 title: {
316 display: true,
317 text: 'Per-turn utility plot'
318 },
319 tooltips: {
320 mode: 'index',
321 intersect: false,
322 },
323 hover: {
324 mode: 'nearest',
325 intersect: true
326 },
327 scales: {
328 xAxes: [{
329 display: true,
330 scaleLabel: {
331 display: true,
332 labelString: 'Offer'
333 }
334 }],
335 yAxes: [{
336 display: true,
337 scaleLabel: {
338 display: true,
339 labelString: 'Utility'
340 }
341 }]
342 }
343 }
344 };
345
346 var ctx = document.getElementById('canvas').getContext('2d');
347 window.myLine = new Chart(ctx, config);
348
349 setStatus("Plot ready." )
350
351 }
352
353 /**
354 keys are STRING objects 0,1,2... values are utility values (double)
355 */
356 function makeJxPolyline(pointslist) {
357 var jxpoints = []
358 for (var i in pointslist) {
359 jxpoints.push(new jxPoint(20*Number(i), 100*pointslist[i]));
360 }
361 return jxpoints;
362 }
363
364 /******************* Private support funcs****************/
365
366 /**
367 Set the progress/status text
368 */
369 function setStatus(text) {
370 document.getElementById('progress').innerHTML=text;
371 }
372
373 /*
374 @param sessions a list of IDs (Strings).
375 */
376 function update(sessions) {
377 var table = document.getElementById("sessionsTable");
378 table.innerHTML="";
379 for(var session of sessions) {
380 var row = table.insertRow(-1); //-1 = end
381 row.insertCell(0).innerHTML = session;
382 }
383
384 }
385
386
[46]387 function utility(profile, issueValues) {
388 if (profile['LinearAdditiveUtilitySpace']!=undefined)
389 return linearUtility(profile['LinearAdditiveUtilitySpace'], issueValues);
390
391 if (profile['SumOfGroupsUtilitySpace']!=undefined)
392 return groupUtility(profile['SumOfGroupsUtilitySpace'], issueValues);
393
394 }
395
396 /**
397 Compute utilityes for a linear-additive profile
398 @param linadditive the linear additive utility space
399 @param issueValues the bid containing dict with values for the issues
400 @return utility of issueValues
401 */
402 function groupUtility(profile, issueValues) {
403 var util=0;
404
405 for (var part in profile['partUtilities']) {
406 util = util + grouputil(profile['partUtilities'][part]['PartsUtilities'], issueValues );
407 }
408 return util;
409 }
410
411 function grouputil(partutils, issueValues) {
412
413 // extract the exact value list of this group
414 var value = []
415 for (const iss of partutils['issues']) {
416 value.push( issueValues[iss] )
417 }
418
419 // find the utility in the utilslist
420 for (const item of partutils['utilslist']) {
421 if ( arrayequals( item['values'], value))
422 return item['util']
423 }
424 return 0; // fallback, not found. Maybe error?
425
426 }
427
428
[45]429
430 /**
[46]431 Compute utilityes for a linear-additive profile
[45]432 @param linadditive the linear additive utility space
433 @param issueValues the bid containing dict with values for the issues
434 @return utility of issueValues
435 */
[46]436 function linearUtility(profile, issueValues) {
[45]437 var util=0;
438 var weights = profile['issueWeights'];
439
440 for (var issue in issueValues) {
441 util = util + weights[issue] * evaluate(profile['issueUtilities'][issue], issueValues[issue] );
442 }
443 return util;
444 }
445
[46]446
447 /**
[45]448 Bit hacky, javascript version of evaluator of utility space.
449 Would be much nicer if we could use java here
450 */
451 function evaluate (utilfunc, value) {
452 if (utilfunc == undefined) {
453 return 0;
454 }
455 if (utilfunc['NumberValueSetUtilities']!=undefined) {
456 // it's numeric issue. Compute
457 var minval = utilfunc['NumberValueSetUtilities']['lowValue'];
458 var minutil = utilfunc['NumberValueSetUtilities']['lowUtility'];
459 var maxval = utilfunc['NumberValueSetUtilities']['highValue'];
460 var maxutil = utilfunc['NumberValueSetUtilities']['highUtility'];
461
462 var alfa = (value-minval) / (maxval - minval) ;
463 alfa = Math.max(Math.min(alfa, 1),0);
464 return alfa * maxutil + (1-alfa) * minutil;
465 }
466 if (utilfunc['DiscreteValueSetUtilities']!=undefined) {
467 // it's discrete issue. Compute
468 return utilfunc['DiscreteValueSetUtilities']['valueUtilities'][value];
469 }
470 setStatus("Unknown utility function type "+Object.keys(utilfunc));
471
472 }
473
474
475 ]]>
476
477
478
479
480
481
482
483
484
[46]485
[45]486</script>
487
488
489
490<script type="application/javascript">
491
492
493
494
495
496
497
498
499
500 <![CDATA[
501 "use strict";
502 /*
503 This is called when the window DOM is loaded. window.onload runs BEFORE the window is loaded
504 and therefore is too early to remove the noscript message.
505 */
506 document.addEventListener("DOMContentLoaded", function() {
507 // Remove elements with "noscript" class - <noscript> is not allowed in XHTML
508 var noscripts = document.getElementsByClassName("noscript");
509 for (var i = 0; i < noscripts.length; i++) {
510 noscripts[i].parentNode.removeChild(noscripts[i]);
511 }
512 connect();
513
514 }, false);
515 ]]>
516
517
518
519
520
521
522
523
524
525
526</script>
527
528
529</html>
Note: See TracBrowser for help on using the repository browser.