eyeling 1.16.3 → 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.
- package/README.md +0 -1
- package/package.json +2 -3
- package/arctifacts/README.md +0 -59
- package/arctifacts/ackermann.html +0 -678
- package/arctifacts/auroracare.html +0 -1297
- package/arctifacts/bike-trip.html +0 -752
- package/arctifacts/binomial-theorem.html +0 -631
- package/arctifacts/bmi.html +0 -511
- package/arctifacts/building-performance.html +0 -750
- package/arctifacts/clinical-care.html +0 -726
- package/arctifacts/collatz.html +0 -403
- package/arctifacts/complex.html +0 -321
- package/arctifacts/control-system.html +0 -482
- package/arctifacts/delfour.html +0 -849
- package/arctifacts/earthquake-epicenter.html +0 -982
- package/arctifacts/eco-route.html +0 -662
- package/arctifacts/euclid-infinitude.html +0 -564
- package/arctifacts/euler-identity.html +0 -667
- package/arctifacts/exoplanet-transit.html +0 -1000
- package/arctifacts/faltings-theorem.html +0 -1046
- package/arctifacts/fibonacci.html +0 -299
- package/arctifacts/fundamental-theorem-arithmetic.html +0 -398
- package/arctifacts/godel-numbering.html +0 -743
- package/arctifacts/gps-bike.html +0 -759
- package/arctifacts/gps-clinical-bench.html +0 -792
- package/arctifacts/graph-french.html +0 -449
- package/arctifacts/grass-molecular.html +0 -592
- package/arctifacts/group-theory.html +0 -740
- package/arctifacts/health-info.html +0 -833
- package/arctifacts/kaprekar-constant.html +0 -576
- package/arctifacts/lee.html +0 -805
- package/arctifacts/linked-lists.html +0 -502
- package/arctifacts/lldm.html +0 -612
- package/arctifacts/matrix-multiplication.html +0 -502
- package/arctifacts/matrix.html +0 -651
- package/arctifacts/newton-raphson.html +0 -944
- package/arctifacts/peano-factorial.html +0 -456
- package/arctifacts/pi.html +0 -363
- package/arctifacts/polynomial.html +0 -646
- package/arctifacts/prime.html +0 -366
- package/arctifacts/pythagorean-theorem.html +0 -468
- package/arctifacts/rest-path.html +0 -469
- package/arctifacts/roots-of-unity.html +0 -363
- package/arctifacts/turing.html +0 -409
- package/arctifacts/wind-turbines.html +0 -726
package/arctifacts/gps-bike.html
DELETED
|
@@ -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>
|