chiark / gitweb /
Tents: mark squares as non-tents with {Shift,Control}-cursor keys.
[sgt-puzzles.git] / benchmark.pl
1 #!/usr/bin/perl
2
3 # Process the raw output from benchmark.sh into Javascript-ified HTML.
4
5 use strict;
6 use warnings;
7
8 my @presets = ();
9 my %presets = ();
10 my $maxval = 0;
11
12 while (<>) {
13     chomp;
14     if (/^(.*)(#.*): ([\d\.]+)$/) {
15         push @presets, $1 unless defined $presets{$1};
16         push @{$presets{$1}}, $3;
17         $maxval = $3 if $maxval < $3;
18     }
19 }
20
21 print <<EOF;
22 <!DOCTYPE html>
23 <html>
24 <head>
25 <meta http-equiv="Content-Type" content="text/html; charset=ASCII" />
26 <title>Puzzle generation-time benchmarks</title>
27 <script type="text/javascript">
28 //<![CDATA[
29 function choose_scale_ticks(scale) {
30     var nscale = 1, j = 0, factors = [2,2.5,2];
31     while (scale / nscale > 20) {
32         nscale *= factors[j];
33         j = (j+1) % factors.length;
34     } 
35     return nscale;
36 }
37 function initPlots() {
38     var canvases = document.getElementsByTagName('canvas');
39     for (var i = 0; i < canvases.length; i++) {
40         var canvas = canvases[i];
41         var scale = eval(canvas.getAttribute("data-scale"));
42         var add = 20.5, mult = (canvas.width - 2*add) / scale;
43         var data = eval(canvas.getAttribute("data-points"));
44         var ctx = canvas.getContext('2d');
45         ctx.lineWidth = '1px';
46         ctx.lineCap = 'round';
47         ctx.lineJoin = 'round';
48         ctx.strokeStyle = ctx.fillStyle = '#000000';
49         if (data === "scale") {
50             // Draw scale.
51             ctx.font = "16px sans-serif";
52             ctx.textAlign = "center";
53             ctx.textBaseline = "alphabetic";
54             var nscale = choose_scale_ticks(scale);
55             for (var x = 0; x <= scale; x += nscale) {
56                 ctx.beginPath();
57                 ctx.moveTo(add+mult*x, canvas.height);
58                 ctx.lineTo(add+mult*x, canvas.height - 3);
59                 ctx.stroke();
60                 ctx.fillText(x + "s", add+mult*x, canvas.height - 6);
61             }
62         } else {
63             // Draw a box plot.
64             function quantile(x) {
65                 var n = (data.length * x) | 0;
66                 return (data[n-1] + data[n]) / 2;
67             }
68
69             var q1 = quantile(0.25), q2 = quantile(0.5), q3 = quantile(0.75);
70             var iqr = q3 - q1;
71             var top = 0.5, bot = canvas.height - 1.5, mid = (top+bot)/2;
72             var wlo = null, whi = null; // whisker ends
73
74             ctx.strokeStyle = '#bbbbbb';
75             var nscale = choose_scale_ticks(scale);
76             for (var x = 0; x <= scale; x += nscale) {
77                 ctx.beginPath();
78                 ctx.moveTo(add+mult*x, 0);
79                 ctx.lineTo(add+mult*x, canvas.height);
80                 ctx.stroke();
81             }
82             ctx.strokeStyle = '#000000';
83
84             for (var j in data) {
85                 var x = data[j];
86                 if (x >= q1 - 1.5 * iqr && x <= q3 + 1.5 * iqr) {
87                     if (wlo === null || wlo > x)
88                         wlo = x;
89                     if (whi === null || whi < x)
90                         whi = x;
91                 } else {
92                     ctx.beginPath();
93                     ctx.arc(add+mult*x, mid, 2, 0, 2*Math.PI);
94                     ctx.stroke();
95                     if (x >= q1 - 3 * iqr && x <= q3 + 3 * iqr)
96                         ctx.fill();
97                 }
98             }
99
100             ctx.beginPath();
101
102             // Box
103             ctx.moveTo(add+mult*q1, top);
104             ctx.lineTo(add+mult*q3, top);
105             ctx.lineTo(add+mult*q3, bot);
106             ctx.lineTo(add+mult*q1, bot);
107             ctx.closePath();
108
109             // Line at median
110             ctx.moveTo(add+mult*q2, top);
111             ctx.lineTo(add+mult*q2, bot);
112
113             // Lower whisker
114             ctx.moveTo(add+mult*q1, mid);
115             ctx.lineTo(add+mult*wlo, mid);
116             ctx.moveTo(add+mult*wlo, top);
117             ctx.lineTo(add+mult*wlo, bot);
118
119             // Upper whisker
120             ctx.moveTo(add+mult*q3, mid);
121             ctx.lineTo(add+mult*whi, mid);
122             ctx.moveTo(add+mult*whi, top);
123             ctx.lineTo(add+mult*whi, bot);
124
125             ctx.stroke();
126         }
127     }
128     document.getElementById('sort_orig').onclick = function() {
129         sort(function(e) {
130             return parseFloat(e.getAttribute("data-index"));
131         });
132     };
133     document.getElementById('sort_median').onclick = function() {
134         sort(function(e) {
135             return -parseFloat(e.getAttribute("data-median"));
136         });
137     };
138     document.getElementById('sort_mean').onclick = function() {
139         sort(function(e) {
140             return -parseFloat(e.getAttribute("data-mean"));
141         });
142     };
143 }
144 function sort(keyfn) {
145     var rows = document.getElementsByTagName("tr");
146     var trs = [];
147     for (var i = 0; i < rows.length; i++)
148         trs.push(rows[i]);
149     trs.sort(function(a,b) {
150         var akey = keyfn(a);
151         var bkey = keyfn(b);
152         return akey < bkey ? -1 : akey > bkey ? +1 : 0;
153     });
154     var parent = trs[0].parentElement;
155     for (var i = 0; i < trs.length; i++)
156         parent.removeChild(trs[i]);
157     for (var i = 0; i < trs.length; i++)
158         parent.appendChild(trs[i]);
159 }
160 //]]>
161 </script>
162 </head>
163 <body onLoad="initPlots();">
164 <h1 align=center>Puzzle generation-time benchmarks</h1>
165 <p>Sort order:
166 <button id="sort_orig">Original</button>
167 <button id="sort_median">Median</button>
168 <button id="sort_mean">Mean</button>
169 <table>
170 <tr><th>Preset</th><td><canvas width=700 height=30 data-points='"scale"' data-scale="$maxval"></td></tr>
171 EOF
172
173 my $index = 0;
174 for my $preset (@presets) {
175     my @data = sort { $a <=> $b } @{$presets{$preset}};
176     my $median = ($#data % 2 ?
177                   ($data[($#data-1)/2]+$data[($#data+1)/2])/2 :
178                   $data[$#data/2]);
179     my $mean = 0; map { $mean += $_ } @data; $mean /= @data;
180     print "<tr data-index=\"$index\" data-mean=\"$mean\" data-median=\"$median\"><td>", &escape($preset), "</td><td><canvas width=700 height=15 data-points=\"[";
181     print join ",", @data;
182     print "]\" data-scale=\"$maxval\"></td></tr>\n";
183     $index++;
184 }
185
186 print <<EOF;
187 </body>
188 </html>
189 EOF
190
191 sub escape {
192     my ($text) = @_;
193     $text =~ s/&/&amp;/g;
194     $text =~ s/</&lt;/g;
195     $text =~ s/>/&gt;/g;
196     return $text;
197 }