@tscircuit/autorouting-dataset-01 1.0.24 → 1.0.25

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 (2) hide show
  1. package/dist/cli/index.js +1255 -0
  2. package/package.json +19 -3
@@ -0,0 +1,1255 @@
1
+ #!/usr/bin/env bun
2
+
3
+ // lib/cli/main.ts
4
+ import { Command } from "commander";
5
+
6
+ // lib/cli/run/register.ts
7
+ import { access, mkdir, writeFile } from "fs/promises";
8
+ import path3 from "path";
9
+ import { fileURLToPath } from "url";
10
+ import kleur from "kleur";
11
+
12
+ // lib/cli/loadUserAutorouter.ts
13
+ import { pathToFileURL } from "url";
14
+ var loadUserAutorouter = async (autorouterPath, requestedSolverName) => {
15
+ let exports;
16
+ try {
17
+ const fileUrl = pathToFileURL(autorouterPath).href;
18
+ const module = await import(fileUrl);
19
+ exports = module;
20
+ } catch (error) {
21
+ throw new Error(
22
+ `Failed to import autorouter from "${autorouterPath}": ${error.message}`
23
+ );
24
+ }
25
+ let displayNameMap = null;
26
+ for (const [, exportValue] of Object.entries(exports)) {
27
+ if (exportValue instanceof Map) {
28
+ const firstEntry = exportValue.entries().next().value;
29
+ if (firstEntry && typeof firstEntry[0] === "function" && typeof firstEntry[1] === "string") {
30
+ displayNameMap = exportValue;
31
+ break;
32
+ }
33
+ }
34
+ }
35
+ const getDisplayName = (solver, fallback) => {
36
+ if (displayNameMap) {
37
+ const name = displayNameMap.get(solver);
38
+ if (name) return name;
39
+ }
40
+ return fallback;
41
+ };
42
+ const pipelineExports = [];
43
+ for (const [exportName, exportValue] of Object.entries(exports)) {
44
+ if (typeof exportValue === "function" && !exportName.startsWith("_")) {
45
+ const displayName = getDisplayName(exportValue, exportName);
46
+ pipelineExports.push({ name: displayName, value: exportValue });
47
+ } else if (Array.isArray(exportValue)) {
48
+ for (const item of exportValue) {
49
+ if (typeof item === "function") {
50
+ const displayName = getDisplayName(item, item.name || "unknown");
51
+ pipelineExports.push({ name: displayName, value: item });
52
+ }
53
+ }
54
+ }
55
+ }
56
+ if (exports.default) {
57
+ const defaultExport = exports.default;
58
+ if (typeof defaultExport === "function") {
59
+ const fn = defaultExport;
60
+ const displayName = getDisplayName(defaultExport, fn.name || "default");
61
+ pipelineExports.push({ name: displayName, value: defaultExport });
62
+ } else if (Array.isArray(defaultExport)) {
63
+ for (const item of defaultExport) {
64
+ if (typeof item === "function") {
65
+ const fn = item;
66
+ const displayName = getDisplayName(item, fn.name || "unknown");
67
+ pipelineExports.push({ name: displayName, value: item });
68
+ }
69
+ }
70
+ }
71
+ }
72
+ if (requestedSolverName) {
73
+ const requestedExport = pipelineExports.find(
74
+ (e) => e.name === requestedSolverName
75
+ );
76
+ if (!requestedExport) {
77
+ const availableNames = pipelineExports.map((e) => e.name).filter((n) => n !== "default");
78
+ throw new Error(
79
+ `Solver "${requestedSolverName}" not found in "${autorouterPath}". Available solvers: ${availableNames.join(", ") || "(none)"}`
80
+ );
81
+ }
82
+ return {
83
+ solverConstructor: requestedExport.value,
84
+ solverName: requestedSolverName
85
+ };
86
+ }
87
+ if (pipelineExports.length === 0) {
88
+ throw new Error(
89
+ `No autorouter found in "${autorouterPath}". Expected an export containing "Pipeline" in its name (e.g., AutoroutingPipeline, MyPipeline).`
90
+ );
91
+ }
92
+ if (pipelineExports.length > 1) {
93
+ console.log(
94
+ `Found ${pipelineExports.length} potential solvers: ${pipelineExports.map((e) => e.name).join(", ")}`
95
+ );
96
+ console.log(
97
+ `Using "${pipelineExports[0].name}". To use a different one, specify it as the second argument.`
98
+ );
99
+ }
100
+ const preferredExport = pipelineExports.find((e) => e.name.toLowerCase().includes("pipeline")) ?? pipelineExports[0];
101
+ const solverConstructor = preferredExport.value;
102
+ if (typeof solverConstructor !== "function") {
103
+ throw new Error(
104
+ `Export "${preferredExport.name}" is not a class or function. Expected a solver constructor.`
105
+ );
106
+ }
107
+ return {
108
+ solverConstructor,
109
+ solverName: preferredExport.name
110
+ };
111
+ };
112
+
113
+ // scripts/run-benchmark/buildBenchmarkDetailsJson.ts
114
+ import path from "path";
115
+ var buildBenchmarkDetailsJson = (inputs) => {
116
+ const { scenarioResultList, scenarioList } = inputs;
117
+ const detailsByScenarioPath = {};
118
+ const simpleRouteJsonByScenarioPath = new Map(
119
+ scenarioList.map((scenario) => [
120
+ path.basename(scenario.simpleRouteJsonPath),
121
+ scenario.simpleRouteJson
122
+ ])
123
+ );
124
+ for (const scenarioResult of scenarioResultList) {
125
+ const scenarioFileName = path.basename(scenarioResult.simpleRouteJsonPath);
126
+ const simpleRouteJson = simpleRouteJsonByScenarioPath.get(scenarioFileName);
127
+ if (!simpleRouteJson) {
128
+ continue;
129
+ }
130
+ detailsByScenarioPath[scenarioFileName] = {
131
+ simpleRouteJson,
132
+ solverResults: scenarioResult.solverResultBySolverName,
133
+ circuitPreviewSvg: scenarioResult.circuitPreviewSvg
134
+ };
135
+ }
136
+ return detailsByScenarioPath;
137
+ };
138
+
139
+ // scripts/run-benchmark/formatTimeSeconds.ts
140
+ var formatTimeSeconds = (timeMs) => {
141
+ if (timeMs === null) {
142
+ return "n/a";
143
+ }
144
+ return `${(timeMs / 1e3).toFixed(1)}s`;
145
+ };
146
+
147
+ // scripts/run-benchmark/buildBenchmarkTableRows.ts
148
+ var buildBenchmarkTableRows = (inputs) => {
149
+ const { resultRowList } = inputs;
150
+ const tableHeaderList = [
151
+ "Solver",
152
+ "Completed %",
153
+ "Relaxed DRC Pass %",
154
+ "P50 Time",
155
+ "P95 Time"
156
+ ];
157
+ const tableRowList = resultRowList.map((result) => [
158
+ result.solverName,
159
+ `${result.successRatePercent.toFixed(1)}%`,
160
+ result.relaxedDrcRatePercent === null ? "n/a" : `${result.relaxedDrcRatePercent.toFixed(1)}%`,
161
+ formatTimeSeconds(result.p50TimeMs),
162
+ formatTimeSeconds(result.p95TimeMs)
163
+ ]);
164
+ return { tableHeaderList, tableRowList };
165
+ };
166
+
167
+ // scripts/run-benchmark/buildBenchmarkSummaryJson.ts
168
+ var buildBenchmarkSummaryJson = (inputs) => {
169
+ const { resultRowList, scenarioList } = inputs;
170
+ const { tableHeaderList, tableRowList } = buildBenchmarkTableRows({
171
+ resultRowList
172
+ });
173
+ return {
174
+ tableHeaderList,
175
+ tableRowList,
176
+ scenarioCount: scenarioList.length
177
+ };
178
+ };
179
+
180
+ // scripts/run-benchmark/generateHtmlVisualization/generateChartConfig.ts
181
+ var generateChartConfig = () => {
182
+ return `const chart_config = {
183
+ responsive: true,
184
+ maintainAspectRatio: true,
185
+ plugins: {
186
+ legend: {
187
+ labels: { color: '#1f2937' }
188
+ }
189
+ },
190
+ scales: {
191
+ y: {
192
+ ticks: { color: '#4b5563' },
193
+ grid: { color: '#e5e7eb' }
194
+ },
195
+ x: {
196
+ ticks: { color: '#4b5563' },
197
+ grid: { color: '#e5e7eb' }
198
+ }
199
+ }
200
+ };`;
201
+ };
202
+
203
+ // scripts/run-benchmark/generateHtmlVisualization/generateSuccessRateChart.ts
204
+ var generateSuccessRateChart = () => {
205
+ return `new Chart(document.getElementById('successRateChart'), {
206
+ type: 'bar',
207
+ data: {
208
+ labels: benchmark_data.map(row => row.solverName),
209
+ datasets: [{
210
+ label: 'Success Rate (%)',
211
+ data: benchmark_data.map(row => row.successRatePercent),
212
+ backgroundColor: 'rgba(59, 130, 246, 0.6)',
213
+ borderColor: 'rgba(59, 130, 246, 1)',
214
+ borderWidth: 1
215
+ }]
216
+ },
217
+ options: {
218
+ ...chart_config,
219
+ scales: {
220
+ ...chart_config.scales,
221
+ y: {
222
+ ...chart_config.scales.y,
223
+ beginAtZero: true,
224
+ max: 100
225
+ }
226
+ }
227
+ }
228
+ });`;
229
+ };
230
+
231
+ // scripts/run-benchmark/generateHtmlVisualization/generateTimeChart.ts
232
+ var generateTimeChart = () => {
233
+ return `new Chart(document.getElementById('timeChart'), {
234
+ type: 'bar',
235
+ data: {
236
+ labels: benchmark_data.map(row => row.solverName),
237
+ datasets: [
238
+ {
239
+ label: 'P50 Time (ms)',
240
+ data: benchmark_data.map(row => row.p50TimeMs || 0),
241
+ backgroundColor: 'rgba(34, 197, 94, 0.6)',
242
+ borderColor: 'rgba(34, 197, 94, 1)',
243
+ borderWidth: 1
244
+ },
245
+ {
246
+ label: 'P95 Time (ms)',
247
+ data: benchmark_data.map(row => row.p95TimeMs || 0),
248
+ backgroundColor: 'rgba(234, 179, 8, 0.6)',
249
+ borderColor: 'rgba(234, 179, 8, 1)',
250
+ borderWidth: 1
251
+ }
252
+ ]
253
+ },
254
+ options: chart_config
255
+ });`;
256
+ };
257
+
258
+ // scripts/run-benchmark/generateHtmlVisualization/generateChartScripts.ts
259
+ var generateChartScripts = (result_row_list) => {
260
+ return `<script>
261
+ const benchmark_data = ${JSON.stringify(result_row_list)};
262
+ ${generateChartConfig()}
263
+ ${generateSuccessRateChart()}
264
+ ${generateTimeChart()}
265
+ </script>`;
266
+ };
267
+
268
+ // scripts/run-benchmark/generateHtmlVisualization/generateClientDebuggerScript.ts
269
+ var escapeJsonForHtml = (json) => json.replace(/</g, "\\u003c");
270
+ var generateClientDebuggerScript = (detail_json) => {
271
+ const detail_json_text = escapeJsonForHtml(JSON.stringify(detail_json));
272
+ const svgsonShim = encodeURIComponent(
273
+ [
274
+ "import * as m from 'https://esm.sh/svgson@5.2.1?target=es2022';",
275
+ "const stringify = m.stringify ?? m.stringifySync ?? (m.default && (m.default.stringify || m.default.stringifySync));",
276
+ "export { stringify };",
277
+ "export * from 'https://esm.sh/svgson@5.2.1?target=es2022';"
278
+ ].join("")
279
+ );
280
+ return `<script>
281
+ window.__benchmark_details = ${detail_json_text};
282
+ </script>
283
+ <script type="importmap">
284
+ {
285
+ "imports": {
286
+ "react": "https://esm.sh/react@18",
287
+ "react/jsx-runtime": "https://esm.sh/react@18/jsx-runtime",
288
+ "react/jsx-dev-runtime": "https://esm.sh/react@18/jsx-dev-runtime",
289
+ "react-dom": "https://esm.sh/react-dom@18",
290
+ "react-dom/client": "https://esm.sh/react-dom@18/client",
291
+ "scheduler": "https://esm.sh/scheduler@0.23.0",
292
+ "svgson": "data:text/javascript,${svgsonShim}"
293
+ }
294
+ }
295
+ </script>
296
+ <script type="module">
297
+ import React, { useMemo } from "react";
298
+ import { createRoot } from "react-dom/client";
299
+ import { GenericSolverDebugger } from "https://esm.sh/@tscircuit/solver-utils@latest/react?external=react,react-dom,svgson";
300
+ import {
301
+ AutoroutingPipeline1_OriginalUnravel,
302
+ AutoroutingPipelineSolver as AutoroutingPipelineSolver2_PortPointPathing
303
+ } from "https://esm.sh/@tscircuit/capacity-autorouter@latest";
304
+
305
+ const solverConstructors = {
306
+ AutoroutingPipelineSolver2_PortPointPathing,
307
+ AutoroutingPipeline1_OriginalUnravel
308
+ };
309
+
310
+ const modal = document.getElementById("solver-debugger-modal");
311
+ const modalTitle = document.getElementById("solver-debugger-title");
312
+ const rootEl = document.getElementById("solver-debugger-root");
313
+ const closeBtn = document.getElementById("solver-debugger-close");
314
+ let root = null;
315
+
316
+ const closeModal = () => {
317
+ if (!modal) return;
318
+ modal.classList.add("hidden");
319
+ if (root) {
320
+ root.unmount();
321
+ root = null;
322
+ }
323
+ if (rootEl) {
324
+ rootEl.innerHTML = "";
325
+ }
326
+ };
327
+
328
+ if (closeBtn) {
329
+ closeBtn.addEventListener("click", closeModal);
330
+ }
331
+
332
+ if (modal) {
333
+ modal.addEventListener("click", (event) => {
334
+ if (event.target === modal) {
335
+ closeModal();
336
+ }
337
+ });
338
+ }
339
+
340
+ const DebuggerApp = ({ scenarioKey, solverName }) => {
341
+ const detail = window.__benchmark_details?.[scenarioKey];
342
+ const solverCtor = solverConstructors[solverName];
343
+ const solver = useMemo(() => {
344
+ if (!detail || !solverCtor) return null;
345
+ return new solverCtor(detail.simpleRouteJson);
346
+ }, [scenarioKey, solverName]);
347
+
348
+ if (!detail) {
349
+ return React.createElement(
350
+ "div",
351
+ { className: "p-4 text-sm text-red-700" },
352
+ "Scenario data not found."
353
+ );
354
+ }
355
+
356
+ if (!solverCtor) {
357
+ return React.createElement(
358
+ "div",
359
+ { className: "p-4 text-sm text-red-700" },
360
+ "Solver not available in this build."
361
+ );
362
+ }
363
+
364
+ return React.createElement(GenericSolverDebugger, { solver });
365
+ };
366
+
367
+ const openDebugger = (scenarioKey, solverName) => {
368
+ if (!modal || !rootEl) return;
369
+ if (modalTitle) {
370
+ modalTitle.textContent = \`\${scenarioKey} \u2022 \${solverName}\`;
371
+ }
372
+ modal.classList.remove("hidden");
373
+ if (!root) {
374
+ root = createRoot(rootEl);
375
+ }
376
+ root.render(
377
+ React.createElement(DebuggerApp, {
378
+ scenarioKey,
379
+ solverName,
380
+ key: scenarioKey + "::" + solverName,
381
+ })
382
+ );
383
+ };
384
+
385
+ document.querySelectorAll(".js-open-debugger").forEach((button) => {
386
+ button.addEventListener("click", () => {
387
+ const scenarioKey = button.getAttribute("data-scenario");
388
+ const solverName = button.getAttribute("data-solver");
389
+ if (scenarioKey && solverName) {
390
+ openDebugger(scenarioKey, solverName);
391
+ }
392
+ });
393
+ });
394
+ </script>`;
395
+ };
396
+
397
+ // scripts/run-benchmark/generateHtmlVisualization/generateHeader.ts
398
+ var generateHeader = (summary_json) => {
399
+ return `<header class="mb-8">
400
+ <h1 class="text-4xl font-bold text-blue-600 mb-2">Autorouting Benchmark Results</h1>
401
+ <p class="text-gray-600">Total Scenarios: ${summary_json.scenarioCount}</p>
402
+ </header>`;
403
+ };
404
+
405
+ // scripts/run-benchmark/generateHtmlVisualization/generatePerformanceOverview.ts
406
+ var generatePerformanceOverview = () => {
407
+ return `<section class="mb-8">
408
+ <h2 class="text-2xl font-semibold text-blue-700 mb-4">Performance Overview</h2>
409
+ <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
410
+ <benchmark-card title="Success Rate by Solver">
411
+ <canvas id="successRateChart"></canvas>
412
+ </benchmark-card>
413
+ <benchmark-card title="Average Execution Time">
414
+ <canvas id="timeChart"></canvas>
415
+ </benchmark-card>
416
+ </div>
417
+ </section>`;
418
+ };
419
+
420
+ // scripts/run-benchmark/generateHtmlVisualization/generateSolverResultCard.ts
421
+ var generateSolverResultCard = (inputs) => {
422
+ const { solver_name, solver_result, scenario_path } = inputs;
423
+ const status_badge_class = solver_result.didSolve ? "bg-emerald-100 text-emerald-700 border-emerald-200" : "bg-rose-100 text-rose-700 border-rose-200";
424
+ const status_text = solver_result.didSolve ? "Solved" : "Failed";
425
+ const status_icon = solver_result.didSolve ? `<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>` : `<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"/></svg>`;
426
+ const drc_badge_class = solver_result.relaxedDrcPassed ? "bg-emerald-100 text-emerald-700 border-emerald-200" : "bg-amber-100 text-amber-700 border-amber-200";
427
+ const drc_text = solver_result.relaxedDrcPassed ? "Passed" : "Failed";
428
+ const drc_icon = solver_result.relaxedDrcPassed ? `<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>` : `<svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/></svg>`;
429
+ return `<div class="bg-white rounded-lg p-4 border border-slate-200 shadow-sm hover:shadow-md transition-shadow">
430
+ <h4 class="font-semibold text-slate-900 mb-3 text-base">${solver_name}</h4>
431
+ <div class="space-y-2.5 text-sm">
432
+ <div class="flex justify-between items-center">
433
+ <span class="text-slate-600 font-medium">Status</span>
434
+ <span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md border text-xs font-medium ${status_badge_class}">
435
+ ${status_icon}
436
+ <span>${status_text}</span>
437
+ </span>
438
+ </div>
439
+ <div class="flex justify-between items-center">
440
+ <span class="text-slate-600 font-medium">Time</span>
441
+ <span class="font-mono text-slate-900 font-semibold">${solver_result.elapsedTimeMs.toFixed(2)}<span class="text-slate-500 font-normal text-xs ml-0.5">ms</span></span>
442
+ </div>
443
+ <div class="flex justify-between items-center">
444
+ <span class="text-slate-600 font-medium">DRC</span>
445
+ <span class="inline-flex items-center gap-1.5 px-2 py-0.5 rounded-md border text-xs font-medium ${drc_badge_class}">
446
+ ${drc_icon}
447
+ <span>${drc_text}</span>
448
+ </span>
449
+ </div>
450
+ </div>
451
+ <button
452
+ type="button"
453
+ class="js-open-debugger mt-4 w-full inline-flex items-center justify-center gap-2 rounded-md border border-slate-300 bg-white px-3 py-2 text-xs font-medium text-slate-700 transition hover:bg-slate-50 hover:border-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1"
454
+ data-scenario="${scenario_path}"
455
+ data-solver="${solver_name}"
456
+ >
457
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4"/></svg>
458
+ <span>Open Debugger</span>
459
+ </button>
460
+ </div>`;
461
+ };
462
+
463
+ // scripts/run-benchmark/generateHtmlVisualization/generateScenarioCard.ts
464
+ var generateScenarioCard = (inputs) => {
465
+ const { scenario_path, solver_results, circuit_preview_svg } = inputs;
466
+ const solver_preview_html = Object.entries(solver_results).map(([solver_name, solver_result]) => {
467
+ const badge_class = solver_result.didSolve ? "bg-emerald-100 text-emerald-700 border-emerald-200" : "bg-rose-100 text-rose-700 border-rose-200";
468
+ const status_text = solver_result.didSolve ? "Solved" : "Failed";
469
+ return `<span title="${solver_name}" class="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-md border text-xs font-medium ${badge_class}">
470
+ <span class="font-semibold">${solver_name}</span>
471
+ <span class="opacity-75">${status_text}</span>
472
+ </span>`;
473
+ }).join("");
474
+ const solver_tag_list = Object.entries(solver_results).map(
475
+ ([solver_name, solver_result]) => `solver:${solver_name}:${solver_result.didSolve ? "pass" : "fail"}`
476
+ );
477
+ const solver_cards_html = Object.entries(solver_results).map(
478
+ ([solver_name, solver_result]) => generateSolverResultCard({
479
+ solver_name,
480
+ solver_result,
481
+ scenario_path
482
+ })
483
+ ).join("");
484
+ const circuit_preview_section = circuit_preview_svg ? `<div class="px-6 py-4 border-t border-gray-200 bg-white">
485
+ <h4 class="text-sm font-semibold text-slate-800 mb-3">Circuit Preview</h4>
486
+ <div class="bg-slate-50 rounded-lg border border-slate-200 p-4 flex items-center justify-center overflow-auto">
487
+ ${circuit_preview_svg}
488
+ </div>
489
+ </div>` : "";
490
+ return `<details class="js-scenario-card bg-white rounded-lg border border-gray-200 shadow-sm hover:shadow-md transition-shadow" data-tags="${solver_tag_list.join(" ")}">
491
+ <summary class="px-6 py-4 cursor-pointer hover:bg-slate-50 transition-colors">
492
+ <div class="flex items-start justify-between gap-4">
493
+ <span class="font-mono text-sm text-slate-700 break-all pt-1">${scenario_path}</span>
494
+ <span class="flex flex-wrap items-center gap-2 shrink-0">${solver_preview_html}</span>
495
+ </div>
496
+ </summary>
497
+ ${circuit_preview_section}
498
+ <div class="px-6 py-4 border-t border-gray-200 bg-slate-50">
499
+ <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
500
+ ${solver_cards_html}
501
+ </div>
502
+ </div>
503
+ </details>`;
504
+ };
505
+
506
+ // scripts/run-benchmark/generateHtmlVisualization/generateScenarioDetails.ts
507
+ var generateScenarioDetails = (detail_json) => {
508
+ const solver_name_list = Array.from(
509
+ new Set(
510
+ Object.values(detail_json).flatMap(
511
+ (scenario_detail) => Object.keys(scenario_detail.solverResults)
512
+ )
513
+ )
514
+ );
515
+ const tag_filter_html = solver_name_list.map((solver_name) => {
516
+ const pass_tag = `solver:${solver_name}:pass`;
517
+ const fail_tag = `solver:${solver_name}:fail`;
518
+ return `<div class="inline-flex items-center gap-2 px-3 py-2 bg-white rounded-lg border border-gray-200">
519
+ <span class="text-sm font-medium text-slate-700">${solver_name}:</span>
520
+ <button type="button" class="js-scenario-tag js-pass-tag inline-flex items-center gap-1.5 rounded-md border border-blue-300 bg-blue-50 px-2.5 py-1 text-xs font-medium text-blue-700 transition hover:bg-blue-100 hover:border-blue-400 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-1" data-tag="${pass_tag}" data-label="${solver_name} solved">
521
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M5 13l4 4L19 7"/></svg>
522
+ <span>Solved</span>
523
+ </button>
524
+ <button type="button" class="js-scenario-tag js-fail-tag inline-flex items-center gap-1.5 rounded-md border border-rose-300 bg-rose-50 px-2.5 py-1 text-xs font-medium text-rose-700 transition hover:bg-rose-100 hover:border-rose-400 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-1" data-tag="${fail_tag}" data-label="${solver_name} failed">
525
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M6 18L18 6M6 6l12 12"/></svg>
526
+ <span>Failed</span>
527
+ </button>
528
+ </div>`;
529
+ }).join("");
530
+ const scenarios_html = Object.entries(detail_json).map(
531
+ ([scenario_path, scenario_detail]) => generateScenarioCard({
532
+ scenario_path,
533
+ solver_results: scenario_detail.solverResults,
534
+ circuit_preview_svg: scenario_detail.circuitPreviewSvg
535
+ })
536
+ ).join("");
537
+ return `<section class="mb-8">
538
+ <h2 class="text-2xl font-bold text-slate-800 mb-4">Scenario Details</h2>
539
+ <div class="mb-6 rounded-xl border border-slate-200 bg-gradient-to-br from-slate-50 to-white p-5 shadow-sm">
540
+ <div class="flex flex-wrap items-center gap-3 mb-4">
541
+ <h3 class="text-sm font-semibold text-slate-900">Filter by Status</h3>
542
+ <button type="button" class="js-clear-scenario-tags inline-flex items-center gap-1.5 rounded-md border border-slate-300 bg-white px-3 py-1.5 text-xs font-medium text-slate-700 transition hover:bg-slate-50 hover:border-slate-400 focus:outline-none focus:ring-2 focus:ring-slate-500 focus:ring-offset-1">
543
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/></svg>
544
+ <span>Clear All</span>
545
+ </button>
546
+ <div class="flex-1"></div>
547
+ <div class="text-xs text-slate-600">
548
+ <span class="font-medium">Active filters:</span>
549
+ <span class="js-scenario-tag-summary font-mono text-slate-800">None</span>
550
+ </div>
551
+ </div>
552
+ <div class="flex flex-wrap gap-3">
553
+ ${tag_filter_html}
554
+ </div>
555
+ <div class="mt-4 pt-4 border-t border-slate-200">
556
+ <div class="flex items-start gap-2 text-xs text-slate-600">
557
+ <svg class="w-4 h-4 text-blue-500 mt-0.5 shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
558
+ <p><span class="font-medium">Regression detection:</span> Select "Solved" for one solver and "Failed" for another to identify scenarios that regressed between versions.</p>
559
+ </div>
560
+ </div>
561
+ </div>
562
+ <div class="space-y-3">${scenarios_html}</div>
563
+ <script>
564
+ (() => {
565
+ const tagButtonList = Array.from(document.querySelectorAll(".js-scenario-tag"))
566
+ const scenarioCardList = Array.from(document.querySelectorAll(".js-scenario-card"))
567
+ const summaryEl = document.querySelector(".js-scenario-tag-summary")
568
+ const clearBtn = document.querySelector(".js-clear-scenario-tags")
569
+
570
+ const getActiveTagList = () =>
571
+ tagButtonList
572
+ .filter((button) => button.getAttribute("data-selected") === "true")
573
+ .map((button) => button.getAttribute("data-tag"))
574
+ .filter(Boolean)
575
+
576
+ const updateSummary = (active_tag_list) => {
577
+ if (!summaryEl) return
578
+ if (!active_tag_list.length) {
579
+ summaryEl.textContent = "None"
580
+ return
581
+ }
582
+ const label_list = active_tag_list
583
+ .map((tag) =>
584
+ tagButtonList.find((button) => button.getAttribute("data-tag") === tag),
585
+ )
586
+ .map((button) => button?.getAttribute("data-label"))
587
+ .filter(Boolean)
588
+ summaryEl.textContent = label_list.join(", ")
589
+ }
590
+
591
+ const applyFilter = () => {
592
+ const active_tag_list = getActiveTagList()
593
+ updateSummary(active_tag_list)
594
+ scenarioCardList.forEach((card) => {
595
+ const tag_list = (card.getAttribute("data-tags") ?? "")
596
+ .split(" ")
597
+ .filter(Boolean)
598
+ const is_match = active_tag_list.every((tag) => tag_list.includes(tag))
599
+ card.classList.toggle("hidden", !is_match)
600
+ })
601
+ }
602
+
603
+ tagButtonList.forEach((button) => {
604
+ button.addEventListener("click", () => {
605
+ const is_selected = button.getAttribute("data-selected") === "true"
606
+ const is_pass_tag = button.classList.contains("js-pass-tag")
607
+
608
+ button.setAttribute("data-selected", is_selected ? "false" : "true")
609
+
610
+ if (!is_selected) {
611
+ // When selecting, change to green
612
+ button.classList.add("ring-2", "ring-green-500", "ring-offset-2", "shadow-sm", "scale-105")
613
+ button.classList.remove("border-blue-300", "bg-blue-50", "text-blue-700", "border-rose-300", "bg-rose-50", "text-rose-700")
614
+ button.classList.add("border-green-500", "bg-green-100", "text-green-800")
615
+ } else {
616
+ // When deselecting, restore original color
617
+ button.classList.remove("ring-2", "ring-green-500", "ring-offset-2", "shadow-sm", "scale-105", "border-green-500", "bg-green-100", "text-green-800")
618
+ if (is_pass_tag) {
619
+ button.classList.add("border-blue-300", "bg-blue-50", "text-blue-700")
620
+ } else {
621
+ button.classList.add("border-rose-300", "bg-rose-50", "text-rose-700")
622
+ }
623
+ }
624
+
625
+ applyFilter()
626
+ })
627
+ })
628
+
629
+ if (clearBtn) {
630
+ clearBtn.addEventListener("click", () => {
631
+ tagButtonList.forEach((button) => {
632
+ const is_pass_tag = button.classList.contains("js-pass-tag")
633
+
634
+ button.setAttribute("data-selected", "false")
635
+ button.classList.remove("ring-2", "ring-green-500", "ring-offset-2", "shadow-sm", "scale-105", "border-green-500", "bg-green-100", "text-green-800")
636
+
637
+ if (is_pass_tag) {
638
+ button.classList.add("border-blue-300", "bg-blue-50", "text-blue-700")
639
+ } else {
640
+ button.classList.add("border-rose-300", "bg-rose-50", "text-rose-700")
641
+ }
642
+ })
643
+ applyFilter()
644
+ })
645
+ }
646
+
647
+ applyFilter()
648
+ })()
649
+ </script>
650
+ </section>`;
651
+ };
652
+
653
+ // scripts/run-benchmark/generateHtmlVisualization/generateSolverDebuggerModal.ts
654
+ var generateSolverDebuggerModal = () => {
655
+ return `<div id="solver-debugger-modal" class="fixed inset-0 z-50 flex hidden items-center justify-center bg-black/50 p-4">
656
+ <div class="w-full max-w-6xl overflow-hidden rounded-lg border border-gray-300 bg-white shadow-2xl">
657
+ <div class="flex items-center justify-between border-b border-gray-300 px-4 py-3">
658
+ <h3 id="solver-debugger-title" class="text-sm font-semibold text-blue-800 md:text-base"></h3>
659
+ <button
660
+ id="solver-debugger-close"
661
+ type="button"
662
+ class="rounded border border-gray-400 px-3 py-1 text-sm text-gray-700 transition hover:border-gray-500 hover:text-gray-900"
663
+ >
664
+ Close
665
+ </button>
666
+ </div>
667
+ <div id="solver-debugger-root" class="h-[75vh] overflow-auto"></div>
668
+ </div>
669
+ </div>`;
670
+ };
671
+
672
+ // scripts/run-benchmark/generateHtmlVisualization/generateSummaryTable.ts
673
+ var generateSummaryTable = (summary_json) => {
674
+ const header_html = summary_json.tableHeaderList.map(
675
+ (header) => `<th class="px-4 py-3 text-left text-blue-700 font-semibold">${header}</th>`
676
+ ).join("");
677
+ const rows_html = summary_json.tableRowList.map(
678
+ (row) => `
679
+ <tr class="hover:bg-gray-50 transition-colors">
680
+ ${row.map((cell) => `<td class="px-4 py-3">${cell}</td>`).join("")}
681
+ </tr>
682
+ `
683
+ ).join("");
684
+ return `<section class="mb-8">
685
+ <h2 class="text-2xl font-semibold text-blue-700 mb-4">Summary Table</h2>
686
+ <benchmark-card>
687
+ <div class="overflow-x-auto">
688
+ <table class="w-full text-sm">
689
+ <thead class="bg-gray-100">
690
+ <tr>${header_html}</tr>
691
+ </thead>
692
+ <tbody class="divide-y divide-gray-200">${rows_html}</tbody>
693
+ </table>
694
+ </div>
695
+ </benchmark-card>
696
+ </section>`;
697
+ };
698
+
699
+ // scripts/run-benchmark/generateHtmlVisualization/generateWebComponents.ts
700
+ var generateWebComponents = () => {
701
+ return `<script>
702
+ class BenchmarkCard extends HTMLElement {
703
+ connectedCallback() {
704
+ const title = this.getAttribute('title');
705
+ const content = this.innerHTML;
706
+ this.innerHTML = \`
707
+ <div class="bg-white rounded-lg p-6 border border-gray-300 shadow-lg">
708
+ \${title ? \`<h3 class="text-lg font-semibold text-blue-800 mb-4">\${title}</h3>\` : ''}
709
+ <div>\${content}</div>
710
+ </div>
711
+ \`;
712
+ }
713
+ }
714
+ customElements.define('benchmark-card', BenchmarkCard);
715
+ </script>`;
716
+ };
717
+
718
+ // scripts/run-benchmark/generateHtmlVisualization/index.ts
719
+ var generateHtmlVisualization = (inputs) => {
720
+ const { summary_json, detail_json, result_row_list } = inputs;
721
+ return `<!DOCTYPE html>
722
+ <html lang="en">
723
+ <head>
724
+ <meta charset="UTF-8">
725
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
726
+ <title>Autorouting Benchmark Results</title>
727
+ <script src="https://cdn.tailwindcss.com"></script>
728
+ <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
729
+ </head>
730
+ <body class="bg-white text-gray-900 min-h-screen p-8">
731
+ <div class="max-w-7xl mx-auto">
732
+ ${generateHeader(summary_json)}
733
+ ${generatePerformanceOverview()}
734
+ ${generateSummaryTable(summary_json)}
735
+ ${generateScenarioDetails(detail_json)}
736
+ </div>
737
+ ${generateWebComponents()}
738
+ ${generateSolverDebuggerModal()}
739
+ ${generateChartScripts(result_row_list)}
740
+ ${generateClientDebuggerScript(detail_json)}
741
+ </body>
742
+ </html>`;
743
+ };
744
+
745
+ // scripts/run-benchmark/loadScenarioList.ts
746
+ import { readdir, readFile } from "fs/promises";
747
+ import path2 from "path";
748
+ var loadScenarioList = async (inputs) => {
749
+ const { datasetDirectory, scenarioCountLimit } = inputs;
750
+ const datasetFileList = (await readdir(datasetDirectory)).filter((fileName) => fileName.endsWith(".simple-route.json")).sort();
751
+ const limitedFileList = scenarioCountLimit === null ? datasetFileList : datasetFileList.slice(0, scenarioCountLimit);
752
+ if (limitedFileList.length === 0) {
753
+ return [];
754
+ }
755
+ const scenarioList = [];
756
+ for (const fileName of limitedFileList) {
757
+ const scenarioName = fileName.replace(".simple-route.json", "");
758
+ const simpleRouteJsonPath = path2.join(datasetDirectory, fileName);
759
+ const simpleRouteJsonText = await readFile(simpleRouteJsonPath, "utf8");
760
+ const simpleRouteJson = JSON.parse(simpleRouteJsonText);
761
+ scenarioList.push({ scenarioName, simpleRouteJsonPath, simpleRouteJson });
762
+ }
763
+ return scenarioList;
764
+ };
765
+
766
+ // scripts/run-benchmark/outputTabled.ts
767
+ var outputTabled = (inputs) => {
768
+ const { resultRowList, scenarioList } = inputs;
769
+ const { tableHeaderList, tableRowList } = buildBenchmarkTableRows({
770
+ resultRowList
771
+ });
772
+ const columnWidths = tableHeaderList.map((header, columnIndex) => {
773
+ let maxWidth = header.length;
774
+ for (const rowCells of tableRowList) {
775
+ const cellText = rowCells[columnIndex];
776
+ if (cellText.length > maxWidth) {
777
+ maxWidth = cellText.length;
778
+ }
779
+ }
780
+ return maxWidth;
781
+ });
782
+ const separator = `+${columnWidths.map((width) => "-".repeat(width + 2)).join("+")}+`;
783
+ const headerLine = `| ${tableHeaderList.map((header, columnIndex) => header.padEnd(columnWidths[columnIndex])).join(" | ")} |`;
784
+ const bodyLines = tableRowList.map(
785
+ (rowCells) => `| ${rowCells.map(
786
+ (cellText, columnIndex) => cellText.padEnd(columnWidths[columnIndex])
787
+ ).join(" | ")} |`
788
+ );
789
+ const tableText = [
790
+ separator,
791
+ headerLine,
792
+ separator,
793
+ ...bodyLines,
794
+ separator
795
+ ].join("\n");
796
+ const outputLines = [tableText, "", `Scenarios: ${scenarioList.length}`];
797
+ return outputLines.join("\n");
798
+ };
799
+
800
+ // scripts/run-benchmark/runBenchmark.ts
801
+ import { getSvgFromGraphicsObject } from "graphics-debug";
802
+
803
+ // lib/checks/detectUnfixableRoutingIssues.ts
804
+ import { runAllChecks } from "@tscircuit/checks";
805
+ var detectUnfixableRoutingIssues = async (circuitJson) => {
806
+ const errorList = await runAllChecks(circuitJson);
807
+ const traceErrorList = errorList.filter(
808
+ (error) => "error_type" in error && error.error_type === "pcb_trace_error"
809
+ );
810
+ return traceErrorList.length === 0;
811
+ };
812
+
813
+ // lib/converter/srj-to-circuit-json.ts
814
+ var convertToCircuitJson = (inputs) => {
815
+ const { srjWithPointPairs, routes } = inputs;
816
+ const minTraceWidth = inputs.minTraceWidth ?? 0.1;
817
+ const minViaDiameter = inputs.minViaDiameter ?? 0.6;
818
+ const circuitJson = [];
819
+ const pcb_board_id = "pcb_board_0";
820
+ const boundsWidth = srjWithPointPairs.bounds.maxX - srjWithPointPairs.bounds.minX;
821
+ const boundsHeight = srjWithPointPairs.bounds.maxY - srjWithPointPairs.bounds.minY;
822
+ const boardCenterX = (srjWithPointPairs.bounds.minX + srjWithPointPairs.bounds.maxX) / 2;
823
+ const boardCenterY = (srjWithPointPairs.bounds.minY + srjWithPointPairs.bounds.maxY) / 2;
824
+ const boardShape = srjWithPointPairs.outline ? "polygon" : "rect";
825
+ const pcb_board = {
826
+ type: "pcb_board",
827
+ pcb_board_id,
828
+ center: { x: boardCenterX, y: boardCenterY },
829
+ width: boundsWidth,
830
+ height: boundsHeight,
831
+ thickness: 1.6,
832
+ num_layers: srjWithPointPairs.layerCount,
833
+ shape: boardShape,
834
+ outline: srjWithPointPairs.outline,
835
+ material: "fr4"
836
+ };
837
+ circuitJson.push(pcb_board);
838
+ const connectionNameByConnectionName = /* @__PURE__ */ new Map();
839
+ for (const connection of srjWithPointPairs.connections) {
840
+ const connectionFields = connection;
841
+ const connectionName = connectionFields.netConnectionName ?? connectionFields.rootConnectionName ?? connection.name;
842
+ connectionNameByConnectionName.set(connection.name, connectionName);
843
+ }
844
+ const pcbPortByPcbPortId = /* @__PURE__ */ new Map();
845
+ const sourceTraceBySourceTraceId = /* @__PURE__ */ new Map();
846
+ for (const connection of srjWithPointPairs.connections) {
847
+ const connectionName = connectionNameByConnectionName.get(connection.name) ?? connection.name;
848
+ const connectedSourcePortIdList = [];
849
+ for (let pointIndex = 0; pointIndex < connection.pointsToConnect.length; pointIndex += 1) {
850
+ const point = connection.pointsToConnect[pointIndex];
851
+ const pcb_port_id = point.pcb_port_id ?? `pcb_port_${connection.name}_${pointIndex}`;
852
+ connectedSourcePortIdList.push(pcb_port_id);
853
+ if (!pcbPortByPcbPortId.has(pcb_port_id)) {
854
+ const pointLayers = Array.isArray(
855
+ point.layers
856
+ ) ? point.layers ?? [] : point.layer ? [point.layer] : [];
857
+ const pcb_port = {
858
+ type: "pcb_port",
859
+ pcb_port_id,
860
+ source_port_id: pcb_port_id,
861
+ x: point.x,
862
+ y: point.y,
863
+ layers: pointLayers
864
+ };
865
+ pcbPortByPcbPortId.set(pcb_port_id, pcb_port);
866
+ }
867
+ }
868
+ const existingSourceTrace = sourceTraceBySourceTraceId.get(connectionName);
869
+ if (existingSourceTrace) {
870
+ existingSourceTrace.connected_source_port_ids = Array.from(
871
+ /* @__PURE__ */ new Set([
872
+ ...existingSourceTrace.connected_source_port_ids,
873
+ ...connectedSourcePortIdList
874
+ ])
875
+ );
876
+ continue;
877
+ }
878
+ sourceTraceBySourceTraceId.set(connectionName, {
879
+ type: "source_trace",
880
+ source_trace_id: connectionName,
881
+ connected_source_port_ids: connectedSourcePortIdList,
882
+ connected_source_net_ids: []
883
+ });
884
+ }
885
+ circuitJson.push(
886
+ ...Array.from(sourceTraceBySourceTraceId.values()),
887
+ ...Array.from(pcbPortByPcbPortId.values())
888
+ );
889
+ for (let obstacleIndex = 0; obstacleIndex < srjWithPointPairs.obstacles.length; obstacleIndex += 1) {
890
+ const obstacle = srjWithPointPairs.obstacles[obstacleIndex];
891
+ const obstacleType = obstacle.type;
892
+ const obstacleLayer = obstacle.layers?.[0];
893
+ if (!obstacleLayer) {
894
+ continue;
895
+ }
896
+ const connectedTo = obstacle.connectedTo ?? [];
897
+ if (connectedTo.length === 0) {
898
+ const pcb_keepout_id = `pcb_keepout_${obstacleIndex}`;
899
+ const pcb_keepout = obstacleType === "oval" ? {
900
+ type: "pcb_keepout",
901
+ layers: obstacle.layers,
902
+ shape: "circle",
903
+ center: { x: obstacle.center.x, y: obstacle.center.y },
904
+ pcb_keepout_id,
905
+ radius: Math.min(obstacle.width, obstacle.height) / 2
906
+ } : {
907
+ type: "pcb_keepout",
908
+ layers: obstacle.layers,
909
+ shape: "rect",
910
+ center: { x: obstacle.center.x, y: obstacle.center.y },
911
+ pcb_keepout_id,
912
+ width: obstacle.width,
913
+ height: obstacle.height
914
+ };
915
+ circuitJson.push(pcb_keepout);
916
+ continue;
917
+ }
918
+ const pcb_port_id = connectedTo.find((id) => id.startsWith("pcb_port_"));
919
+ const pcb_smtpad_hint_id = connectedTo.find(
920
+ (id) => id.startsWith("pcb_smtpad_")
921
+ );
922
+ if (!pcb_port_id && !pcb_smtpad_hint_id) {
923
+ continue;
924
+ }
925
+ const pcb_smtpad_id = pcb_smtpad_hint_id ?? `pcb_smtpad_${obstacleIndex}`;
926
+ const centerX = obstacle.center?.x;
927
+ const centerY = obstacle.center?.y;
928
+ if (typeof centerX !== "number" || typeof centerY !== "number") {
929
+ continue;
930
+ }
931
+ const pcb_smtpad = obstacleType === "oval" ? {
932
+ type: "pcb_smtpad",
933
+ shape: "circle",
934
+ pcb_smtpad_id,
935
+ x: centerX,
936
+ y: centerY,
937
+ radius: Math.min(obstacle.width, obstacle.height) / 2,
938
+ layer: obstacleLayer,
939
+ pcb_port_id
940
+ } : {
941
+ type: "pcb_smtpad",
942
+ shape: "rect",
943
+ pcb_smtpad_id,
944
+ x: centerX,
945
+ y: centerY,
946
+ width: obstacle.width,
947
+ height: obstacle.height,
948
+ layer: obstacleLayer,
949
+ pcb_port_id
950
+ };
951
+ circuitJson.push(pcb_smtpad);
952
+ }
953
+ const viaKeySet = /* @__PURE__ */ new Set();
954
+ const pcbViaList = [];
955
+ for (let traceIndex = 0; traceIndex < routes.length; traceIndex += 1) {
956
+ const trace = routes[traceIndex];
957
+ const pcbTraceId = trace.pcb_trace_id ?? `pcb_trace_${traceIndex}`;
958
+ for (const routePoint of trace.route) {
959
+ if (routePoint.route_type !== "via") {
960
+ continue;
961
+ }
962
+ const fromLayer = routePoint.from_layer;
963
+ const toLayer = routePoint.to_layer;
964
+ const viaKey = `${routePoint.x}_${routePoint.y}_${fromLayer}_${toLayer}`;
965
+ if (viaKeySet.has(viaKey)) {
966
+ continue;
967
+ }
968
+ viaKeySet.add(viaKey);
969
+ pcbViaList.push({
970
+ type: "pcb_via",
971
+ pcb_via_id: `pcb_via_${pcbViaList.length}`,
972
+ pcb_trace_id: pcbTraceId,
973
+ x: routePoint.x,
974
+ y: routePoint.y,
975
+ outer_diameter: minViaDiameter,
976
+ hole_diameter: minViaDiameter * 0.5,
977
+ layers: [fromLayer, toLayer]
978
+ });
979
+ }
980
+ }
981
+ circuitJson.push(...pcbViaList);
982
+ for (let traceIndex = 0; traceIndex < routes.length; traceIndex += 1) {
983
+ const trace = routes[traceIndex];
984
+ const pcbTraceId = trace.pcb_trace_id ?? `pcb_trace_${traceIndex}`;
985
+ const traceConnectionName = trace.connection_name ?? trace.connectionName;
986
+ const sourceTraceId = traceConnectionName ? connectionNameByConnectionName.get(traceConnectionName) ?? traceConnectionName : void 0;
987
+ const route = trace.route.map((routePoint) => {
988
+ if (routePoint.route_type === "wire") {
989
+ return {
990
+ route_type: "wire",
991
+ x: routePoint.x,
992
+ y: routePoint.y,
993
+ width: routePoint.width ?? minTraceWidth,
994
+ layer: routePoint.layer,
995
+ start_pcb_port_id: routePoint.start_pcb_port_id,
996
+ end_pcb_port_id: routePoint.end_pcb_port_id
997
+ };
998
+ }
999
+ if (routePoint.route_type === "via") {
1000
+ return {
1001
+ route_type: "via",
1002
+ x: routePoint.x,
1003
+ y: routePoint.y,
1004
+ to_layer: routePoint.to_layer,
1005
+ from_layer: routePoint.from_layer
1006
+ };
1007
+ }
1008
+ return null;
1009
+ }).filter(
1010
+ (routePoint) => routePoint !== null
1011
+ );
1012
+ const pcb_trace = {
1013
+ type: "pcb_trace",
1014
+ pcb_trace_id: pcbTraceId,
1015
+ source_trace_id: sourceTraceId,
1016
+ route
1017
+ };
1018
+ circuitJson.push(pcb_trace);
1019
+ }
1020
+ return circuitJson;
1021
+ };
1022
+
1023
+ // scripts/run-benchmark/getPercentileMs.ts
1024
+ var getPercentileMs = (durationMsList, percentile) => {
1025
+ if (durationMsList.length === 0) {
1026
+ return null;
1027
+ }
1028
+ const sorted = [...durationMsList].sort((a, b) => a - b);
1029
+ const clampedPercentile = Math.min(Math.max(percentile, 0), 1);
1030
+ const position = (sorted.length - 1) * clampedPercentile;
1031
+ const lowerIndex = Math.floor(position);
1032
+ const upperIndex = Math.ceil(position);
1033
+ const lowerValue = sorted[lowerIndex];
1034
+ const upperValue = sorted[upperIndex];
1035
+ if (lowerIndex === upperIndex) {
1036
+ return lowerValue;
1037
+ }
1038
+ const weight = position - lowerIndex;
1039
+ return lowerValue + (upperValue - lowerValue) * weight;
1040
+ };
1041
+
1042
+ // scripts/run-benchmark/solvers.ts
1043
+ import {
1044
+ AutoroutingPipeline1_OriginalUnravel,
1045
+ AutoroutingPipelineSolver as AutoroutingPipelineSolver2_PortPointPathing,
1046
+ AutoroutingPipelineSolver3_HgPortPointPathing
1047
+ } from "@tscircuit/capacity-autorouter";
1048
+ var solverDisplayNameByConstructor = /* @__PURE__ */ new Map([
1049
+ [
1050
+ AutoroutingPipelineSolver2_PortPointPathing,
1051
+ "AutoroutingPipelineSolver2_PortPointPathing"
1052
+ ],
1053
+ [
1054
+ AutoroutingPipelineSolver3_HgPortPointPathing,
1055
+ "AutoroutingPipelineSolver3_HgPortPointPathing"
1056
+ ],
1057
+ [
1058
+ AutoroutingPipeline1_OriginalUnravel,
1059
+ "AutoroutingPipeline1_OriginalUnravel"
1060
+ ]
1061
+ ]);
1062
+
1063
+ // scripts/run-benchmark/runBenchmark.ts
1064
+ var runBenchmark = async (inputs) => {
1065
+ const { scenarioList, solverConstructorList } = inputs;
1066
+ const resultRowList = [];
1067
+ const scenarioResultList = scenarioList.map(
1068
+ (scenario) => ({
1069
+ scenarioName: scenario.scenarioName,
1070
+ simpleRouteJsonPath: scenario.simpleRouteJsonPath,
1071
+ solverResultBySolverName: {},
1072
+ circuitPreviewSvg: ""
1073
+ })
1074
+ );
1075
+ const totalRunCount = scenarioList.length * solverConstructorList.length;
1076
+ let completedRunCount = 0;
1077
+ let averageRunTimeMs = 0;
1078
+ for (const solverClass of solverConstructorList) {
1079
+ const solverDisplayName = solverDisplayNameByConstructor.get(solverClass) ?? solverClass.name;
1080
+ let totalTimeMs = 0;
1081
+ let successCount = 0;
1082
+ let relaxedDrcPassedCount = 0;
1083
+ const elapsedTimeMsList = [];
1084
+ for (const [scenarioIndex, scenario] of scenarioList.entries()) {
1085
+ const solver = new solverClass(scenario.simpleRouteJson);
1086
+ const rawSvg = getSvgFromGraphicsObject(solver.visualize());
1087
+ scenarioResultList[scenarioIndex].circuitPreviewSvg = rawSvg;
1088
+ const startMs = Date.now();
1089
+ try {
1090
+ solver.solve();
1091
+ } catch (_error) {
1092
+ solver.solved = false;
1093
+ }
1094
+ const elapsedMs = Date.now() - startMs;
1095
+ const connectionsCount = scenario.simpleRouteJson.connections?.length ?? 0;
1096
+ totalTimeMs += elapsedMs;
1097
+ const solved = solver.solved;
1098
+ if (solved) {
1099
+ successCount += 1;
1100
+ elapsedTimeMsList.push(elapsedMs);
1101
+ }
1102
+ const scenarioStatus = solved ? "Solved" : "Failed";
1103
+ console.log(
1104
+ `[${solverDisplayName}] ${scenarioStatus} ${scenario.scenarioName} in ${formatTimeSeconds(elapsedMs)} (connections: ${connectionsCount})`
1105
+ );
1106
+ completedRunCount += 1;
1107
+ averageRunTimeMs += (elapsedMs - averageRunTimeMs) / Math.max(completedRunCount, 1);
1108
+ const remainingRunCount = Math.max(totalRunCount - completedRunCount, 0);
1109
+ const etaMs = Math.max(averageRunTimeMs * remainingRunCount, 0);
1110
+ const percentComplete = totalRunCount === 0 ? 100 : completedRunCount / totalRunCount * 100;
1111
+ console.log(
1112
+ `[Progress] ${completedRunCount}/${totalRunCount} (${percentComplete.toFixed(1)}%) ETA ${formatTimeSeconds(etaMs)}`
1113
+ );
1114
+ const circuitJson = convertToCircuitJson({
1115
+ srjWithPointPairs: solver.srjWithPointPairs,
1116
+ minTraceWidth: scenario.simpleRouteJson.minTraceWidth,
1117
+ minViaDiameter: scenario.simpleRouteJson.minViaDiameter,
1118
+ routes: !solver.failed ? solver.getOutputSimplifiedPcbTraces() : []
1119
+ });
1120
+ const relaxedDrcPassed = await detectUnfixableRoutingIssues(circuitJson);
1121
+ if (relaxedDrcPassed && solver.solved) {
1122
+ relaxedDrcPassedCount += 1;
1123
+ }
1124
+ scenarioResultList[scenarioIndex].solverResultBySolverName[solverDisplayName] = {
1125
+ didSolve: solved,
1126
+ elapsedTimeMs: elapsedMs,
1127
+ relaxedDrcPassed
1128
+ };
1129
+ }
1130
+ const scenarioCount = scenarioList.length;
1131
+ const successRatePercent = scenarioCount === 0 ? 0 : successCount / scenarioCount * 100;
1132
+ const relaxedDrcRatePercent = relaxedDrcPassedCount === 0 ? null : relaxedDrcPassedCount / scenarioCount * 100;
1133
+ resultRowList.push({
1134
+ solverName: solverDisplayName,
1135
+ totalTimeMs,
1136
+ p50TimeMs: getPercentileMs(elapsedTimeMsList, 0.5),
1137
+ p95TimeMs: getPercentileMs(elapsedTimeMsList, 0.95),
1138
+ successRatePercent,
1139
+ relaxedDrcRatePercent
1140
+ });
1141
+ }
1142
+ return { resultRowList, scenarioResultList };
1143
+ };
1144
+
1145
+ // lib/cli/run/register.ts
1146
+ var findDatasetDirectory = async () => {
1147
+ const possiblePaths = [
1148
+ path3.resolve(process.cwd(), "lib/dataset"),
1149
+ path3.resolve(
1150
+ fileURLToPath(import.meta.url),
1151
+ "..",
1152
+ "..",
1153
+ "..",
1154
+ "..",
1155
+ "lib",
1156
+ "dataset"
1157
+ ),
1158
+ path3.resolve(
1159
+ fileURLToPath(import.meta.url),
1160
+ "..",
1161
+ "..",
1162
+ "..",
1163
+ "lib",
1164
+ "dataset"
1165
+ )
1166
+ ];
1167
+ for (const datasetPath of possiblePaths) {
1168
+ try {
1169
+ await access(datasetPath);
1170
+ return datasetPath;
1171
+ } catch {
1172
+ }
1173
+ }
1174
+ throw new Error(
1175
+ `Could not find dataset directory. Checked: ${possiblePaths.join(", ")}`
1176
+ );
1177
+ };
1178
+ var registerRun = (program2) => {
1179
+ program2.command("run", { isDefault: true }).description("Run benchmark on an autorouter").argument("<autorouter-path>", "Path to autorouter TypeScript file").argument("[solver-name]", "Specific export name to use as solver").option("-l, --scenario-limit <count>", "Limit number of scenarios to run").option("-o, --output <path>", "Output HTML file path").action(
1180
+ async (autorouterPath, solverName, options) => {
1181
+ try {
1182
+ const absolutePath = path3.resolve(autorouterPath);
1183
+ console.log(
1184
+ `${kleur.cyan("Loading autorouter from:")} ${absolutePath}`
1185
+ );
1186
+ const { solverConstructor, solverName: detectedSolverName } = await loadUserAutorouter(absolutePath, solverName);
1187
+ const finalSolverName = solverName || detectedSolverName;
1188
+ console.log(
1189
+ `${kleur.green("\u2713")} Using autorouter: ${kleur.bold(finalSolverName)}`
1190
+ );
1191
+ solverDisplayNameByConstructor.set(solverConstructor, finalSolverName);
1192
+ const datasetDirectory = await findDatasetDirectory();
1193
+ const scenarioCountLimit = options.scenarioLimit ? Number.parseInt(options.scenarioLimit, 10) : null;
1194
+ const scenarioList = await loadScenarioList({
1195
+ datasetDirectory,
1196
+ scenarioCountLimit
1197
+ });
1198
+ if (scenarioList.length === 0) {
1199
+ console.error(
1200
+ kleur.red(
1201
+ "No dataset files found. Ensure the package is installed correctly."
1202
+ )
1203
+ );
1204
+ process.exit(1);
1205
+ }
1206
+ console.log(
1207
+ `
1208
+ ${kleur.cyan("Running benchmark with")} ${kleur.bold(String(scenarioList.length))} ${kleur.cyan("scenarios...")}
1209
+ `
1210
+ );
1211
+ const { resultRowList, scenarioResultList } = await runBenchmark({
1212
+ scenarioList,
1213
+ solverConstructorList: [solverConstructor]
1214
+ });
1215
+ const outputText = outputTabled({ resultRowList, scenarioList });
1216
+ console.log("");
1217
+ console.log(outputText);
1218
+ const summaryJson = buildBenchmarkSummaryJson({
1219
+ resultRowList,
1220
+ scenarioList
1221
+ });
1222
+ const detailJson = buildBenchmarkDetailsJson({
1223
+ scenarioResultList,
1224
+ scenarioList
1225
+ });
1226
+ const htmlText = generateHtmlVisualization({
1227
+ summary_json: summaryJson,
1228
+ detail_json: detailJson,
1229
+ result_row_list: resultRowList
1230
+ });
1231
+ const outputDir = path3.resolve("results");
1232
+ await mkdir(outputDir, { recursive: true });
1233
+ const outputPath = options.output ? path3.resolve(options.output) : path3.join(outputDir, `${finalSolverName}.html`);
1234
+ await writeFile(outputPath, htmlText);
1235
+ console.log(
1236
+ `
1237
+ ${kleur.green("\u2713")} HTML results written to: ${kleur.cyan(outputPath)}`
1238
+ );
1239
+ process.exit(0);
1240
+ } catch (error) {
1241
+ const message = error instanceof Error ? error.message : String(error);
1242
+ console.error(`${kleur.red("Error:")} ${message}`);
1243
+ process.exit(1);
1244
+ }
1245
+ }
1246
+ );
1247
+ };
1248
+
1249
+ // lib/cli/main.ts
1250
+ var program = new Command();
1251
+ program.name("autorouting-dataset-runner").description(
1252
+ "Benchmark autorouters against the tscircuit autorouting dataset"
1253
+ ).version("1.0.0");
1254
+ registerRun(program);
1255
+ program.parse();
package/package.json CHANGED
@@ -1,9 +1,17 @@
1
1
  {
2
2
  "name": "@tscircuit/autorouting-dataset-01",
3
- "version": "1.0.24",
3
+ "version": "1.0.25",
4
+ "type": "module",
4
5
  "main": "lib/dataset/index.ts",
6
+ "bin": {
7
+ "autorouting-dataset-runner": "./dist/cli/index.js"
8
+ },
5
9
  "files": [
6
- "lib/dataset"
10
+ "lib/dataset",
11
+ "dist/cli",
12
+ "dist/lib",
13
+ "dist/scripts",
14
+ "dist/types"
7
15
  ],
8
16
  "keywords": [
9
17
  "tscircuit"
@@ -18,12 +26,20 @@
18
26
  "lint:fix": "biome lint --write",
19
27
  "check": "biome check",
20
28
  "check:write": "biome check --write --unsafe",
21
- "build": "tsci build --disable-parts-engine --ignore-errors"
29
+ "build": "tsci build --disable-parts-engine --ignore-errors",
30
+ "build:cli": "tsup",
31
+ "setup": "bun run build:cli && npm install -g .",
32
+ "prepublishOnly": "bun run build:cli"
22
33
  },
23
34
  "publishConfig": {
24
35
  "access": "public"
25
36
  },
37
+ "dependencies": {
38
+ "commander": "^12.1.0",
39
+ "kleur": "^4.1.5"
40
+ },
26
41
  "devDependencies": {
42
+ "tsup": "^8.2.4",
27
43
  "@biomejs/biome": "^2.3.13",
28
44
  "@tsci/imrishabh18.TB6612FNG": "^0.1.0",
29
45
  "@tsci/seveibar.PICO": "^0.1.0",