eyeling 1.16.2 → 1.16.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/HANDBOOK.md +4 -0
  2. package/README.md +0 -1
  3. package/examples/ershov-mixed-computation.n3 +106 -0
  4. package/examples/output/ershov-mixed-computation.n3 +15 -0
  5. package/eyeling.js +510 -263
  6. package/lib/cli.js +22 -12
  7. package/lib/engine.js +488 -251
  8. package/package.json +2 -3
  9. package/arctifacts/README.md +0 -59
  10. package/arctifacts/ackermann.html +0 -678
  11. package/arctifacts/auroracare.html +0 -1297
  12. package/arctifacts/bike-trip.html +0 -752
  13. package/arctifacts/binomial-theorem.html +0 -631
  14. package/arctifacts/bmi.html +0 -511
  15. package/arctifacts/building-performance.html +0 -750
  16. package/arctifacts/clinical-care.html +0 -726
  17. package/arctifacts/collatz.html +0 -403
  18. package/arctifacts/complex.html +0 -321
  19. package/arctifacts/control-system.html +0 -482
  20. package/arctifacts/delfour.html +0 -849
  21. package/arctifacts/earthquake-epicenter.html +0 -982
  22. package/arctifacts/eco-route.html +0 -662
  23. package/arctifacts/euclid-infinitude.html +0 -564
  24. package/arctifacts/euler-identity.html +0 -667
  25. package/arctifacts/exoplanet-transit.html +0 -1000
  26. package/arctifacts/faltings-theorem.html +0 -1046
  27. package/arctifacts/fibonacci.html +0 -299
  28. package/arctifacts/fundamental-theorem-arithmetic.html +0 -398
  29. package/arctifacts/godel-numbering.html +0 -743
  30. package/arctifacts/gps-bike.html +0 -759
  31. package/arctifacts/gps-clinical-bench.html +0 -792
  32. package/arctifacts/graph-french.html +0 -449
  33. package/arctifacts/grass-molecular.html +0 -592
  34. package/arctifacts/group-theory.html +0 -740
  35. package/arctifacts/health-info.html +0 -833
  36. package/arctifacts/kaprekar-constant.html +0 -576
  37. package/arctifacts/lee.html +0 -805
  38. package/arctifacts/linked-lists.html +0 -502
  39. package/arctifacts/lldm.html +0 -612
  40. package/arctifacts/matrix-multiplication.html +0 -502
  41. package/arctifacts/matrix.html +0 -651
  42. package/arctifacts/newton-raphson.html +0 -944
  43. package/arctifacts/peano-factorial.html +0 -456
  44. package/arctifacts/pi.html +0 -363
  45. package/arctifacts/polynomial.html +0 -646
  46. package/arctifacts/prime.html +0 -366
  47. package/arctifacts/pythagorean-theorem.html +0 -468
  48. package/arctifacts/rest-path.html +0 -469
  49. package/arctifacts/roots-of-unity.html +0 -363
  50. package/arctifacts/turing.html +0 -409
  51. package/arctifacts/wind-turbines.html +0 -726
@@ -1,759 +0,0 @@
1
- <!doctype html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="utf-8" />
5
- <title>GPS Bike — Gent → Maasmechelen</title>
6
- <meta name="viewport" content="width=device-width, initial-scale=1" />
7
- <style>
8
- :root {
9
- --bg: #f7fafc;
10
- --card: #ffffff;
11
- --ink: #0f172a;
12
- --muted: #475569;
13
- --ok: #16a34a;
14
- --bad: #dc2626;
15
- --accent: #2563eb;
16
- --edge: #93c5fd;
17
- --mway: #cbd5e1;
18
- --path: #1d4ed8;
19
- --node: #020617;
20
- --shadow: 0 8px 20px rgba(2, 6, 23, 0.08);
21
- --radius: 16px;
22
- }
23
- html,
24
- body {
25
- margin: 0;
26
- background: var(--bg);
27
- color: var(--ink);
28
- font-family:
29
- system-ui,
30
- -apple-system,
31
- Segoe UI,
32
- Roboto,
33
- Ubuntu,
34
- Cantarell,
35
- Noto Sans,
36
- sans-serif;
37
- line-height: 1.45;
38
- }
39
- .wrap {
40
- max-width: 1000px;
41
- margin-inline: auto;
42
- padding: 24px;
43
- display: flex;
44
- flex-direction: column;
45
- gap: 20px;
46
- }
47
- header {
48
- display: flex;
49
- flex-direction: column;
50
- gap: 8px;
51
- }
52
- h1 {
53
- font-size: clamp(1.4rem, 2.4vw, 2rem);
54
- margin: 0;
55
- }
56
- .sub {
57
- color: var(--muted);
58
- }
59
- .card {
60
- background: var(--card);
61
- border-radius: var(--radius);
62
- box-shadow: var(--shadow);
63
- padding: 18px;
64
- }
65
- .stack {
66
- display: flex;
67
- flex-direction: column;
68
- gap: 14px;
69
- }
70
- .row {
71
- display: flex;
72
- gap: 10px;
73
- flex-wrap: wrap;
74
- align-items: center;
75
- }
76
- .btn {
77
- border: 0;
78
- border-radius: 12px;
79
- padding: 10px 14px;
80
- font-weight: 600;
81
- cursor: pointer;
82
- background: var(--accent);
83
- color: white;
84
- box-shadow: var(--shadow);
85
- }
86
- .kpi {
87
- display: grid;
88
- grid-template-columns: 1fr 1fr;
89
- gap: 14px;
90
- }
91
- .k {
92
- background: var(--bg);
93
- border-radius: 12px;
94
- padding: 10px 12px;
95
- }
96
- .k .label {
97
- font-size: 0.8rem;
98
- color: var(--muted);
99
- }
100
- .k .val {
101
- font-size: 1.05rem;
102
- font-weight: 700;
103
- }
104
- .checks {
105
- display: grid;
106
- grid-template-columns: 1fr;
107
- gap: 10px;
108
- }
109
- .check {
110
- display: flex;
111
- justify-content: space-between;
112
- align-items: center;
113
- background: var(--bg);
114
- border-radius: 12px;
115
- padding: 10px 12px;
116
- }
117
- .badge {
118
- font-size: 0.8rem;
119
- font-weight: 700;
120
- padding: 4px 8px;
121
- border-radius: 999px;
122
- }
123
- .pass {
124
- background: rgba(22, 163, 74, 0.12);
125
- color: var(--ok);
126
- }
127
- .fail {
128
- background: rgba(220, 38, 38, 0.12);
129
- color: var(--bad);
130
- }
131
- .hint {
132
- color: var(--muted);
133
- font-size: 0.9rem;
134
- }
135
- svg {
136
- width: 100%;
137
- height: 520px;
138
- background: white;
139
- border-radius: var(--radius);
140
- box-shadow: var(--shadow);
141
- }
142
- .legend {
143
- display: flex;
144
- gap: 18px;
145
- align-items: center;
146
- color: var(--muted);
147
- font-size: 0.9rem;
148
- }
149
- .dot {
150
- width: 10px;
151
- height: 10px;
152
- border-radius: 50%;
153
- }
154
- .dot.path {
155
- background: var(--path);
156
- }
157
- .dot.edge {
158
- background: var(--edge);
159
- }
160
- .dot.mway {
161
- background: var(--mway);
162
- }
163
- .dot.node {
164
- background: var(--node);
165
- }
166
- .pill {
167
- display: inline-flex;
168
- align-items: center;
169
- gap: 8px;
170
- background: var(--bg);
171
- border-radius: 999px;
172
- padding: 6px 10px;
173
- }
174
- .pill .seq {
175
- font-weight: 700;
176
- color: var(--accent);
177
- }
178
- code {
179
- background: var(--bg);
180
- padding: 2px 6px;
181
- border-radius: 8px;
182
- }
183
- footer {
184
- color: var(--muted);
185
- font-size: 0.85rem;
186
- }
187
- pre {
188
- white-space: pre-wrap;
189
- background: var(--bg);
190
- padding: 10px;
191
- border-radius: 12px;
192
- margin: 0;
193
- }
194
- </style>
195
- </head>
196
- <body>
197
- <div class="wrap">
198
- <header>
199
- <h1>GPS Bike — Gent → Maasmechelen</h1>
200
- </header>
201
-
202
- <section class="card stack" id="answer">
203
- <div class="row">
204
- <button class="btn" id="run">Compute Optimal Bike Route</button>
205
- <div class="hint">
206
- Shortest path computed on a bike graph (motorways excluded). Weights = great-circle (Haversine) distances.
207
- </div>
208
- </div>
209
- <div class="kpi">
210
- <div class="k">
211
- <div class="label">Start</div>
212
- <div class="val" id="startVal">—</div>
213
- </div>
214
- <div class="k">
215
- <div class="label">Goal</div>
216
- <div class="val" id="goalVal">—</div>
217
- </div>
218
- <div class="k">
219
- <div class="label">Shortest distance (km)</div>
220
- <div class="val" id="distVal">—</div>
221
- </div>
222
- <div class="k">
223
- <div class="label">Stops (nodes)</div>
224
- <div class="val" id="stopsVal">—</div>
225
- </div>
226
- </div>
227
- <div class="row" id="pathList"></div>
228
- </section>
229
-
230
- <section class="card stack" id="reason">
231
- <h3 style="margin: 0">Reason Why</h3>
232
- <div class="hint">
233
- We model cities as nodes and intercity connections as weighted, undirected edges. The
234
- <b>bike graph</b> excludes edges flagged as motorways. We use <b>Dijkstra</b> to find the shortest path, then
235
- verify with <b>A*</b> (admissible straight-line heuristic), <b>Bellman–Ford</b>, and <b>Floyd–Warshall</b>.
236
- </div>
237
- <pre id="reasonText"></pre>
238
- </section>
239
-
240
- <section class="card stack" id="checks">
241
- <h3 style="margin: 0">Independent Checks</h3>
242
- <div class="checks" id="checksList"></div>
243
- </section>
244
-
245
- <section class="card stack">
246
- <h3 style="margin: 0">Route Map (SVG)</h3>
247
- <div class="legend">
248
- <span class="dot edge"></span> bike-allowed edges <span class="dot mway"></span> motorways (not allowed)
249
- <span class="dot path"></span> chosen shortest path <span class="dot node"></span> city
250
- </div>
251
- <svg id="map" viewBox="0 0 1000 520" role="img" aria-label="Belgium bike route map"></svg>
252
- </section>
253
-
254
- <footer>Self-contained; no external libraries. No fixed sequence — the path is discovered automatically.</footer>
255
- </div>
256
-
257
- <script>
258
- // ---------- Cities (approximate centers) ----------
259
- const cities = {
260
- Gent: { lat: 51.0543, lon: 3.7174 },
261
- Lokeren: { lat: 51.1036, lon: 3.9936 },
262
- Dendermonde: { lat: 51.023, lon: 4.101 },
263
- Aalst: { lat: 50.936, lon: 4.0355 },
264
- Mechelen: { lat: 51.0259, lon: 4.4777 },
265
- Vilvoorde: { lat: 50.9281, lon: 4.4294 },
266
- Leuven: { lat: 50.8798, lon: 4.7005 },
267
- Aarschot: { lat: 50.987, lon: 4.8369 },
268
- Tienen: { lat: 50.8074, lon: 4.9367 },
269
- Diest: { lat: 50.989, lon: 5.0508 },
270
- 'Sint-Truiden': { lat: 50.816, lon: 5.1869 },
271
- 'Herk-de-Stad': { lat: 50.9484, lon: 5.1719 },
272
- Hasselt: { lat: 50.9307, lon: 5.3326 },
273
- Genk: { lat: 50.9669, lon: 5.5 },
274
- Zutendaal: { lat: 50.9333, lon: 5.5667 },
275
- Maasmechelen: { lat: 50.9651, lon: 5.6947 },
276
- Antwerpen: { lat: 51.2194, lon: 4.4025 },
277
- Brussel: { lat: 50.8503, lon: 4.3517 },
278
- };
279
-
280
- // ---------- Links: [u, v, {motorway:boolean}] ----------
281
- // Motorways are excluded from the bike graph (still drawn dashed for context).
282
- const links = [
283
- // Bike-allowed backbone & options (canal & regional links)
284
- ['Gent', 'Lokeren', { motorway: false }],
285
- ['Lokeren', 'Dendermonde', { motorway: false }],
286
- ['Gent', 'Aalst', { motorway: false }],
287
- ['Aalst', 'Dendermonde', { motorway: false }],
288
- ['Dendermonde', 'Mechelen', { motorway: false }],
289
- ['Mechelen', 'Leuven', { motorway: false }],
290
- ['Mechelen', 'Aarschot', { motorway: false }],
291
- ['Leuven', 'Aarschot', { motorway: false }],
292
- ['Leuven', 'Tienen', { motorway: false }],
293
- ['Tienen', 'Diest', { motorway: false }],
294
- ['Aarschot', 'Diest', { motorway: false }],
295
- ['Diest', 'Herk-de-Stad', { motorway: false }],
296
- ['Herk-de-Stad', 'Hasselt', { motorway: false }],
297
- ['Diest', 'Sint-Truiden', { motorway: false }],
298
- ['Sint-Truiden', 'Hasselt', { motorway: false }],
299
- ['Hasselt', 'Genk', { motorway: false }],
300
- ['Genk', 'Zutendaal', { motorway: false }],
301
- ['Zutendaal', 'Maasmechelen', { motorway: false }],
302
- ['Genk', 'Maasmechelen', { motorway: false }],
303
- // A few cross options
304
- ['Vilvoorde', 'Mechelen', { motorway: false }],
305
- ['Vilvoorde', 'Leuven', { motorway: false }],
306
- ['Gent', 'Dendermonde', { motorway: false }],
307
- // Motorways (not allowed for bikes)
308
- ['Gent', 'Brussel', { motorway: true }],
309
- ['Brussel', 'Leuven', { motorway: true }],
310
- ['Mechelen', 'Brussel', { motorway: true }],
311
- ['Gent', 'Antwerpen', { motorway: true }],
312
- ['Antwerpen', 'Hasselt', { motorway: true }],
313
- ['Leuven', 'Hasselt', { motorway: true }],
314
- ];
315
-
316
- // ---------- Build graphs ----------
317
- const rad = (d) => (d * Math.PI) / 180;
318
- function haversine(a, b) {
319
- const R = 6371;
320
- const dLat = rad(b.lat - a.lat),
321
- dLon = rad(b.lon - a.lon);
322
- const s = Math.sin(dLat / 2) ** 2 + Math.cos(rad(a.lat)) * Math.cos(rad(b.lat)) * Math.sin(dLon / 2) ** 2;
323
- return 2 * R * Math.asin(Math.sqrt(s));
324
- }
325
-
326
- function buildAdj(includeMotorways) {
327
- const g = {};
328
- for (const [u, v, meta] of links) {
329
- const allow = includeMotorways || !meta.motorway;
330
- if (!allow) continue;
331
- const w = haversine(cities[u], cities[v]);
332
- g[u] = g[u] || {};
333
- g[v] = g[v] || {};
334
- g[u][v] = Math.min(g[u][v] ?? Infinity, w);
335
- g[v][u] = Math.min(g[v][u] ?? Infinity, w);
336
- }
337
- return g;
338
- }
339
-
340
- const bikeGraph = buildAdj(false);
341
- const fullGraph = buildAdj(true);
342
-
343
- // Active graph pointer for algos (set to bike by default)
344
- let graph = bikeGraph;
345
-
346
- // ---------- Shortest path algorithms ----------
347
- function dijkstra(start, goal) {
348
- const dist = {},
349
- prev = {},
350
- Q = new Set(Object.keys(cities));
351
- for (const n of Q) dist[n] = Infinity;
352
- dist[start] = 0;
353
- const steps = [];
354
- while (Q.size) {
355
- let u = null,
356
- best = Infinity;
357
- for (const n of Q) {
358
- if (dist[n] < best) {
359
- best = dist[n];
360
- u = n;
361
- }
362
- }
363
- if (u === null) break;
364
- Q.delete(u);
365
- steps.push({ pick: u, d: dist[u] });
366
- if (u === goal) break;
367
- for (const [v, w] of Object.entries(graph[u] || {})) {
368
- if (!Q.has(v)) continue;
369
- const alt = dist[u] + w;
370
- if (alt < dist[v]) {
371
- dist[v] = alt;
372
- prev[v] = u;
373
- }
374
- }
375
- }
376
- const path = [];
377
- let u = goal;
378
- if (prev[u] || u === start) {
379
- while (u) {
380
- path.unshift(u);
381
- if (u === start) break;
382
- u = prev[u];
383
- }
384
- }
385
- return { distance: dist[goal], path, steps };
386
- }
387
-
388
- function astar(start, goal) {
389
- const h = (n) => haversine(cities[n], cities[goal]);
390
- const open = new Set([start]);
391
- const came = {},
392
- g = {},
393
- f = {};
394
- for (const n in cities) {
395
- g[n] = Infinity;
396
- f[n] = Infinity;
397
- }
398
- g[start] = 0;
399
- f[start] = h(start);
400
- while (open.size) {
401
- let cur = null,
402
- best = Infinity;
403
- open.forEach((n) => {
404
- if (f[n] < best) {
405
- best = f[n];
406
- cur = n;
407
- }
408
- });
409
- if (cur === goal) {
410
- const path = [cur];
411
- let x = cur;
412
- while (came[x]) {
413
- x = came[x];
414
- path.unshift(x);
415
- }
416
- return { distance: g[cur], path };
417
- }
418
- open.delete(cur);
419
- for (const [nbr, w] of Object.entries(graph[cur] || {})) {
420
- const tentative = g[cur] + w;
421
- if (tentative < g[nbr]) {
422
- came[nbr] = cur;
423
- g[nbr] = tentative;
424
- f[nbr] = tentative + h(nbr);
425
- open.add(nbr);
426
- }
427
- }
428
- }
429
- return { distance: Infinity, path: [] };
430
- }
431
-
432
- function bellmanFord(start, goal) {
433
- const nodes = Object.keys(cities);
434
- const dist = {},
435
- prev = {};
436
- nodes.forEach((n) => (dist[n] = Infinity));
437
- dist[start] = 0;
438
- const edges = [];
439
- for (const u in graph) for (const v in graph[u]) edges.push([u, v, graph[u][v]]);
440
- for (let i = 0; i < nodes.length - 1; i++) {
441
- let changed = false;
442
- for (const [u, v, w] of edges) {
443
- if (dist[u] + w < dist[v]) {
444
- dist[v] = dist[u] + w;
445
- prev[v] = u;
446
- changed = true;
447
- }
448
- }
449
- if (!changed) break;
450
- }
451
- const path = [];
452
- let u = goal;
453
- if (prev[u] || u === start) {
454
- while (u) {
455
- path.unshift(u);
456
- if (u === start) break;
457
- u = prev[u];
458
- }
459
- }
460
- return { distance: dist[goal], path };
461
- }
462
-
463
- function floydWarshall() {
464
- const nodes = Object.keys(cities);
465
- const idx = {},
466
- N = nodes.length;
467
- nodes.forEach((n, i) => (idx[n] = i));
468
- const D = Array.from({ length: N }, (_, i) => Array.from({ length: N }, (_, j) => (i === j ? 0 : Infinity)));
469
- for (const u in graph) for (const v in graph[u]) D[idx[u]][idx[v]] = Math.min(D[idx[u]][idx[v]], graph[u][v]);
470
- for (let k = 0; k < N; k++)
471
- for (let i = 0; i < N; i++)
472
- for (let j = 0; j < N; j++) if (D[i][k] + D[k][j] < D[i][j]) D[i][j] = D[i][k] + D[k][j];
473
- return { nodes, idx, D };
474
- }
475
-
476
- // Helper: motorway edge?
477
- function isMotorwayEdge(u, v) {
478
- for (const [a, b, meta] of links) {
479
- if ((a === u && b === v) || (a === v && b === u)) return !!meta.motorway;
480
- }
481
- return false;
482
- }
483
-
484
- // ---------- Checks (independent harness) ----------
485
- function runChecks(result, dA, dB, fw, start, goal, carD) {
486
- const checks = [];
487
- const add = (name, ok, detail) => checks.push({ name, ok, detail: ok ? detail : detail || 'failed' });
488
-
489
- const path = result.path;
490
- const dist = result.distance;
491
-
492
- // 1) Start/End
493
- add(
494
- 'Starts at Gent, ends at Maasmechelen',
495
- path[0] === start && path[path.length - 1] === goal,
496
- `${path[0]} → ${path[path.length - 1]}`,
497
- );
498
-
499
- // 2) Path exists (every hop present)
500
- let edgesOk = true;
501
- for (let i = 0; i < path.length - 1; i++) {
502
- const u = path[i],
503
- v = path[i + 1];
504
- if (!(graph[u] && graph[u][v] > 0)) {
505
- edgesOk = false;
506
- break;
507
- }
508
- }
509
- add('Each hop exists in the bike graph', edgesOk);
510
-
511
- // 3) No motorways used
512
- let noHighways = true,
513
- offender = '';
514
- for (let i = 0; i < path.length - 1; i++) {
515
- const u = path[i],
516
- v = path[i + 1];
517
- if (isMotorwayEdge(u, v)) {
518
- noHighways = false;
519
- offender = `${u}–${v}`;
520
- break;
521
- }
522
- }
523
- add('No motorways (highways) on chosen path', noHighways, offender || 'ok');
524
-
525
- // 4) Distance equals sum of edges
526
- let sum = 0;
527
- for (let i = 0; i < path.length - 1; i++) sum += graph[path[i]][path[i + 1]];
528
- add(
529
- 'Distance equals sum of edge weights',
530
- Math.abs(sum - dist) < 1e-6,
531
- `sum=${sum.toFixed(3)} vs dijkstra=${dist.toFixed(3)}`,
532
- );
533
-
534
- // 5) A* agrees
535
- add('A* distance equals Dijkstra', Math.abs(dA.distance - dist) < 1e-6, `A*=${dA.distance.toFixed(3)} km`);
536
-
537
- // 6) Bellman–Ford agrees
538
- add(
539
- 'Bellman–Ford distance equals Dijkstra',
540
- Math.abs(dB.distance - dist) < 1e-6,
541
- `BF=${dB.distance.toFixed(3)} km`,
542
- );
543
-
544
- // 7) Floyd–Warshall global optimum matches
545
- const i = fw.idx[start],
546
- j = fw.idx[goal];
547
- add('Floyd–Warshall (all-pairs) matches', Math.abs(fw.D[i][j] - dist) < 1e-6, `FW=${fw.D[i][j].toFixed(3)} km`);
548
-
549
- // 8) Path is simple (no repeated nodes)
550
- const uniq = new Set(path);
551
- add('Path is simple (no repeats)', uniq.size === path.length, `uniq=${uniq.size} nodes`);
552
-
553
- // 9) Direct lower bound (sinuosity sanity)
554
- const direct = haversine(cities[start], cities[goal]);
555
- add('Route ≥ direct great-circle distance', dist + 1e-9 >= direct, `direct=${direct.toFixed(2)} km`);
556
-
557
- // 10) Compare to unrestricted (car) graph
558
- add(
559
- 'Bike distance ≥ car (unrestricted) distance',
560
- dist + 1e-9 >= carD,
561
- `Bike=${dist.toFixed(1)} vs Car=${carD.toFixed(1)} km`,
562
- );
563
-
564
- // 11) Edge criticality (no single used edge removable without longer/∞ result)
565
- let criticalOK = true,
566
- note = '';
567
- for (let k = 0; k < path.length - 1; k++) {
568
- const u = path[k],
569
- v = path[k + 1],
570
- w = graph[u][v];
571
- // temporarily remove
572
- delete graph[u][v];
573
- delete graph[v][u];
574
- const alt = dijkstra(start, goal).distance;
575
- // restore
576
- graph[u][v] = w;
577
- graph[v][u] = w;
578
- if (alt + 1e-9 < dist) {
579
- criticalOK = false;
580
- note = `Removing ${u}–${v} gave shorter ${alt.toFixed(3)} km`;
581
- break;
582
- }
583
- }
584
- add('No single edge on the path is replaceable by a shorter alternative', criticalOK, note || 'ok');
585
-
586
- return checks;
587
- }
588
-
589
- // ---------- SVG ----------
590
- function project(lat, lon, box) {
591
- const lats = Object.values(cities).map((c) => c.lat);
592
- const lons = Object.values(cities).map((c) => c.lon);
593
- const minLat = Math.min(...lats),
594
- maxLat = Math.max(...lats);
595
- const minLon = Math.min(...lons),
596
- maxLon = Math.max(...lons);
597
- const x = ((lon - minLon) / (maxLon - minLon)) * box.w + box.x;
598
- const y = box.y + box.h - ((lat - minLat) / (maxLat - minLat)) * box.h;
599
- return [x, y];
600
- }
601
-
602
- function drawMap(path) {
603
- const svg = document.getElementById('map');
604
- svg.innerHTML = '';
605
- const box = { x: 30, y: 20, w: 940, h: 480 };
606
-
607
- // motorways (dashed)
608
- for (const [u, v, meta] of links) {
609
- if (!meta.motorway) continue;
610
- const [x1, y1] = project(cities[u].lat, cities[u].lon, box);
611
- const [x2, y2] = project(cities[v].lat, cities[v].lon, box);
612
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
613
- line.setAttribute('x1', x1);
614
- line.setAttribute('y1', y1);
615
- line.setAttribute('x2', x2);
616
- line.setAttribute('y2', y2);
617
- line.setAttribute('stroke', 'var(--mway)');
618
- line.setAttribute('stroke-width', '3');
619
- line.setAttribute('stroke-linecap', 'round');
620
- line.setAttribute('stroke-dasharray', '6 6');
621
- line.setAttribute('opacity', '0.9');
622
- svg.appendChild(line);
623
- }
624
-
625
- // bike edges
626
- for (const [u, v, meta] of links) {
627
- if (meta.motorway) continue;
628
- const [x1, y1] = project(cities[u].lat, cities[u].lon, box);
629
- const [x2, y2] = project(cities[v].lat, cities[v].lon, box);
630
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
631
- line.setAttribute('x1', x1);
632
- line.setAttribute('y1', y1);
633
- line.setAttribute('x2', x2);
634
- line.setAttribute('y2', y2);
635
- line.setAttribute('stroke', 'var(--edge)');
636
- line.setAttribute('stroke-width', '3');
637
- line.setAttribute('stroke-linecap', 'round');
638
- line.setAttribute('opacity', '0.8');
639
- svg.appendChild(line);
640
- }
641
-
642
- // chosen path
643
- for (let i = 0; i < path.length - 1; i++) {
644
- const u = path[i],
645
- v = path[i + 1];
646
- const [x1, y1] = project(cities[u].lat, cities[u].lon, box);
647
- const [x2, y2] = project(cities[v].lat, cities[v].lon, box);
648
- const line = document.createElementNS('http://www.w3.org/2000/svg', 'line');
649
- line.setAttribute('x1', x1);
650
- line.setAttribute('y1', y1);
651
- line.setAttribute('x2', x2);
652
- line.setAttribute('y2', y2);
653
- line.setAttribute('stroke', 'var(--path)');
654
- line.setAttribute('stroke-width', '6');
655
- line.setAttribute('stroke-linecap', 'round');
656
- svg.appendChild(line);
657
- }
658
-
659
- // nodes + labels
660
- for (const [name, coord] of Object.entries(cities)) {
661
- const [x, y] = project(coord.lat, coord.lon, box);
662
- const g = document.createElementNS('http://www.w3.org/2000/svg', 'g');
663
- const c = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
664
- c.setAttribute('cx', x);
665
- c.setAttribute('cy', y);
666
- c.setAttribute('r', 6);
667
- c.setAttribute('fill', 'var(--node)');
668
- c.setAttribute('opacity', path.includes(name) ? '1' : '0.85');
669
- const t = document.createElementNS('http://www.w3.org/2000/svg', 'text');
670
- t.setAttribute('x', x + 10);
671
- t.setAttribute('y', y - 10);
672
- t.setAttribute('font-size', '12');
673
- t.setAttribute('fill', 'var(--ink)');
674
- t.textContent = name;
675
- g.appendChild(c);
676
- g.appendChild(t);
677
- svg.appendChild(g);
678
- }
679
- }
680
-
681
- // ---------- Driver (P3) ----------
682
- function run() {
683
- const start = 'Gent',
684
- goal = 'Maasmechelen';
685
-
686
- // Bike (active)
687
- graph = bikeGraph;
688
- const d = dijkstra(start, goal);
689
- const a = astar(start, goal);
690
- const b = bellmanFord(start, goal);
691
- const fw = floydWarshall();
692
-
693
- // Car (unrestricted) distance for comparison
694
- const saved = graph;
695
- graph = fullGraph;
696
- const car = dijkstra(start, goal);
697
- graph = saved;
698
-
699
- // Answer
700
- document.getElementById('startVal').textContent = start;
701
- document.getElementById('goalVal').textContent = goal;
702
- document.getElementById('distVal').textContent = isFinite(d.distance) ? d.distance.toFixed(2) : '∞';
703
- document.getElementById('stopsVal').textContent = d.path.length;
704
-
705
- // Path chips
706
- const pathList = document.getElementById('pathList');
707
- pathList.innerHTML = '';
708
- d.path.forEach((name, idx) => {
709
- const el = document.createElement('div');
710
- el.className = 'pill';
711
- el.innerHTML = `<span class="seq">${idx + 1}</span> ${name}`;
712
- pathList.appendChild(el);
713
- });
714
-
715
- // Reason
716
- const reason = [
717
- `Graph: weighted, undirected; weight = Haversine distance on bike-allowed links.`,
718
- `Algorithms: Dijkstra (primary), A* (admissible straight-line), Bellman–Ford, Floyd–Warshall.`,
719
- `Chosen shortest path (bike): ${d.path.join(' → ')}`,
720
- `Distance (bike): ${isFinite(d.distance) ? d.distance.toFixed(2) : '∞'} km`,
721
- `Reference car (unrestricted) distance: ${isFinite(car.distance) ? car.distance.toFixed(2) : '∞'} km`,
722
- `Dijkstra selection order: ${d.steps.map((s) => `${s.pick}(${isFinite(s.d) ? s.d.toFixed(1) : '∞'})`).join(' → ')}`,
723
- ].join('\n');
724
- document.getElementById('reasonText').textContent = reason;
725
-
726
- // Checks
727
- const checks = runChecks(d, a, b, fw, start, goal, car.distance);
728
- const list = document.getElementById('checksList');
729
- list.innerHTML = '';
730
- checks.forEach((c) => {
731
- const row = document.createElement('div');
732
- row.className = 'check';
733
- const name = document.createElement('div');
734
- name.textContent = c.name;
735
- const right = document.createElement('div');
736
- right.className = 'row';
737
- const detail = document.createElement('div');
738
- detail.className = 'hint';
739
- detail.textContent = c.detail || '';
740
- const badge = document.createElement('span');
741
- badge.className = 'badge ' + (c.ok ? 'pass' : 'fail');
742
- badge.textContent = c.ok ? 'PASS' : 'FAIL';
743
- right.appendChild(detail);
744
- right.appendChild(badge);
745
- row.appendChild(name);
746
- row.appendChild(right);
747
- list.appendChild(row);
748
- });
749
-
750
- // Map
751
- drawMap(d.path);
752
- }
753
-
754
- document.getElementById('run').addEventListener('click', run);
755
- // Run immediately
756
- run();
757
- </script>
758
- </body>
759
- </html>