@tscircuit/rectdiff 0.0.39 → 0.0.41
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/.github/workflows/bun-test.yml +1 -1
- package/benchmark.sh +61 -0
- package/global.d.ts +7 -0
- package/lib/RectDiffPipeline.ts +0 -27
- package/lib/solvers/OuterLayerContainmentMergeSolver/OuterLayerContainmentMergeSolver.ts +4 -108
- package/package.json +3 -1
- package/scripts/benchmark/benchmark-child/constants.ts +6 -0
- package/scripts/benchmark/benchmark-child/createSolver.ts +34 -0
- package/scripts/benchmark/benchmark-child/getAutorouterPipeline4ModulePath.ts +7 -0
- package/scripts/benchmark/benchmark-child/handleWorkerTaskLine.ts +64 -0
- package/scripts/benchmark/benchmark-child/importRuntimeModule.ts +8 -0
- package/scripts/benchmark/benchmark-child/types.ts +20 -0
- package/scripts/benchmark/benchmark-child/writeWorkerResultMessage.ts +10 -0
- package/scripts/benchmark/benchmark-types.ts +34 -0
- package/scripts/benchmark/benchmark.child.ts +11 -0
- package/scripts/benchmark/index.ts +413 -0
- package/tests/benchmark-rectdiff-override.test.ts +41 -0
- package/tests/benchmark-results.test.ts +30 -0
- package/tests/solver/__snapshots__/rectDiffGridSolverPipeline.snap.svg +3 -3
- package/tests/solver/arduino-uno-inner2-ground-bottom-power/__snapshots__/arduino-uno-inner2-ground-bottom-power.snap.svg +3 -3
- package/tests/solver/arduino-uno-inner2-ground-inner1-power/__snapshots__/arduino-uno-inner2-ground-inner1-power.snap.svg +1 -1
- package/tests/solver/both-points-equivalent/__snapshots__/both-points-equivalent.snap.svg +1 -1
- package/tests/solver/bugreport02-bc4361/__snapshots__/bugreport02-bc4361.snap.svg +3 -3
- package/tests/solver/bugreport03-fe4a17/__snapshots__/bugreport03-fe4a17.snap.svg +1 -1
- package/tests/solver/bugreport08-e3ec95/__snapshots__/bugreport08-e3ec95.snap.svg +1 -1
- package/tests/solver/bugreport09-618e09/__snapshots__/bugreport09-618e09.snap.svg +1 -1
- package/tests/solver/bugreport10-71239a/__snapshots__/bugreport10-71239a.snap.svg +3 -3
- package/tests/solver/bugreport11-b2de3c/__snapshots__/bugreport11-b2de3c.snap.svg +3 -3
- package/tests/solver/bugreport13-b9a758/__snapshots__/bugreport13-b9a758.snap.svg +2 -2
- package/tests/solver/bugreport16-d95f38/__snapshots__/bugreport16-d95f38.snap.svg +1 -1
- package/tests/solver/bugreport18-1b2d06/__snapshots__/bugreport18-1b2d06.snap.svg +3 -3
- package/tests/solver/bugreport19/__snapshots__/bugreport19.snap.svg +1 -1
- package/tests/solver/bugreport22-2a75ce/__snapshots__/bugreport22-2a75ce.snap.svg +1 -1
- package/tests/solver/bugreport23-LGA15x4/__snapshots__/bugreport23-LGA15x4.snap.svg +2 -2
- package/tests/solver/bugreport24-05597c/__snapshots__/bugreport24-05597c.snap.svg +1 -1
- package/tests/solver/bugreport25-4b1d55/__snapshots__/bugreport25-4b1d55.snap.svg +3 -3
- package/tests/solver/bugreport36-bf8303/__snapshots__/bugreport36-bf8303.snap.svg +2 -2
- package/tests/solver/interaction/__snapshots__/interaction.snap.svg +3 -3
- package/tests/solver/multi-point/__snapshots__/multi-point.snap.svg +1 -1
- package/tests/solver/no-better-path/__snapshots__/no-better-path.snap.svg +1 -1
- package/tests/solver/offboardconnects01/__snapshots__/offboardconnects01.snap.svg +1 -1
- package/tests/solver/pcb_trace_id-should-return-root-connection-name/__snapshots__/pcb_trace_id-should-return-root-connection-name.snap.svg +3 -3
- package/tests/solver/repros/merge-single-layer-node/__snapshots__/merge-single-layer-node.snap.svg +3 -3
- package/tests/solver/transitivity/__snapshots__/transitivity.snap.svg +2 -2
- package/lib/math/layers/getUnionZ.ts +0 -6
- package/lib/math/layers/getZLayerName.ts +0 -6
- package/lib/math/layers/getZSpanMask.ts +0 -6
- package/lib/math/layers/hasContiguousZSpan.ts +0 -11
- package/lib/math/rects/intersectRects.ts +0 -28
- package/lib/math/rects/mergeRects.ts +0 -12
- package/lib/math/rects/rectArea.ts +0 -7
- package/lib/math/rects/rectContainsRect.ts +0 -18
- package/lib/math/rects/rectsTouchOrOverlap.ts +0 -12
- package/lib/math/rects/subtractRects.ts +0 -23
- package/lib/solvers/SparseMultilayerPromotionSolver/SparseMultilayerPromotionSolver.ts +0 -134
- package/lib/solvers/SparseMultilayerPromotionSolver/cloneNode.ts +0 -15
- package/lib/solvers/SparseMultilayerPromotionSolver/cloneNodeWithRect.ts +0 -34
- package/lib/solvers/SparseMultilayerPromotionSolver/createResidualNodes.ts +0 -42
- package/lib/solvers/SparseMultilayerPromotionSolver/findBestCoalesceCandidate.ts +0 -98
- package/lib/solvers/SparseMultilayerPromotionSolver/findBestPromotionCandidate.ts +0 -72
- package/lib/solvers/SparseMultilayerPromotionSolver/getUsableMultilayerVolumeShare.ts +0 -34
- package/lib/solvers/SparseMultilayerPromotionSolver/isFreeNode.ts +0 -8
- package/lib/solvers/SparseMultilayerPromotionSolver/nodeToRect.ts +0 -13
- package/lib/solvers/SparseMultilayerPromotionSolver/solvers/CoalesceMultilayerTilesSolver.ts +0 -104
- package/lib/solvers/SparseMultilayerPromotionSolver/solvers/PromoteSparseMultilayerCoverageSolver.ts +0 -148
- package/lib/solvers/SparseMultilayerPromotionSolver/solvers/TrimContainedSingleLayerCoverageSolver.ts +0 -137
- package/lib/solvers/SparseMultilayerPromotionSolver/types.ts +0 -23
package/benchmark.sh
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
LIMIT=""
|
|
5
|
+
|
|
6
|
+
default_concurrency() {
|
|
7
|
+
getconf _NPROCESSORS_ONLN 2>/dev/null || nproc 2>/dev/null || echo 4
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
CONCURRENT="${BENCHMARK_CONCURRENCY:-$(default_concurrency)}"
|
|
11
|
+
|
|
12
|
+
print_help() {
|
|
13
|
+
cat <<'EOF'
|
|
14
|
+
Usage:
|
|
15
|
+
./benchmark.sh [--concurrent N] [--limit N]
|
|
16
|
+
|
|
17
|
+
Options:
|
|
18
|
+
--concurrent N Number of Bun worker processes to run, or "auto"
|
|
19
|
+
--limit N Run only the first N dataset scenarios
|
|
20
|
+
-h, --help Show this help
|
|
21
|
+
|
|
22
|
+
Examples:
|
|
23
|
+
./benchmark.sh
|
|
24
|
+
./benchmark.sh --concurrent 8
|
|
25
|
+
./benchmark.sh --limit 25
|
|
26
|
+
./benchmark.sh --concurrent auto --limit 50
|
|
27
|
+
EOF
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
while [ "$#" -gt 0 ]; do
|
|
31
|
+
case "$1" in
|
|
32
|
+
-h|--help)
|
|
33
|
+
print_help
|
|
34
|
+
exit 0
|
|
35
|
+
;;
|
|
36
|
+
--concurrent)
|
|
37
|
+
CONCURRENT="${2:-}"
|
|
38
|
+
if [ "$CONCURRENT" = "auto" ]; then
|
|
39
|
+
CONCURRENT="$(default_concurrency)"
|
|
40
|
+
fi
|
|
41
|
+
shift 2
|
|
42
|
+
;;
|
|
43
|
+
--limit)
|
|
44
|
+
LIMIT="${2:-}"
|
|
45
|
+
shift 2
|
|
46
|
+
;;
|
|
47
|
+
*)
|
|
48
|
+
echo "Unknown argument: $1"
|
|
49
|
+
echo "Run ./benchmark.sh --help for usage"
|
|
50
|
+
exit 1
|
|
51
|
+
;;
|
|
52
|
+
esac
|
|
53
|
+
done
|
|
54
|
+
|
|
55
|
+
CMD=(bun "scripts/benchmark/index.ts" "--concurrent" "$CONCURRENT")
|
|
56
|
+
|
|
57
|
+
if [ -n "$LIMIT" ]; then
|
|
58
|
+
CMD+=("--limit" "$LIMIT")
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
"${CMD[@]}"
|
package/global.d.ts
CHANGED
|
@@ -2,3 +2,10 @@ declare module "*.json" {
|
|
|
2
2
|
const value: any
|
|
3
3
|
export default value
|
|
4
4
|
}
|
|
5
|
+
|
|
6
|
+
declare module "@tscircuit/autorouting-dataset-01" {
|
|
7
|
+
import type { SimpleRouteJson } from "./lib/types/srj-types"
|
|
8
|
+
|
|
9
|
+
const dataset: Record<string, SimpleRouteJson>
|
|
10
|
+
export = dataset
|
|
11
|
+
}
|
package/lib/RectDiffPipeline.ts
CHANGED
|
@@ -16,20 +16,17 @@ import { computeInverseRects } from "./solvers/RectDiffSeedingSolver/computeInve
|
|
|
16
16
|
import { buildZIndexMap } from "./solvers/RectDiffSeedingSolver/layers"
|
|
17
17
|
import { buildObstacleClearanceGraphics } from "./utils/renderObstacleClearance"
|
|
18
18
|
import { mergeGraphics } from "graphics-debug"
|
|
19
|
-
import { SparseMultilayerPromotionSolver } from "./solvers/SparseMultilayerPromotionSolver/SparseMultilayerPromotionSolver"
|
|
20
19
|
|
|
21
20
|
export interface RectDiffPipelineInput {
|
|
22
21
|
simpleRouteJson: SimpleRouteJson
|
|
23
22
|
gridOptions?: Partial<GridFill3DOptions>
|
|
24
23
|
obstacleClearance?: number
|
|
25
|
-
sparseMultilayerPromotionTargetShare?: number
|
|
26
24
|
}
|
|
27
25
|
|
|
28
26
|
export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput> {
|
|
29
27
|
rectDiffGridSolverPipeline?: RectDiffGridSolverPipeline
|
|
30
28
|
gapFillSolver?: GapFillSolverPipeline
|
|
31
29
|
outerLayerContainmentMergeSolver?: OuterLayerContainmentMergeSolver
|
|
32
|
-
sparseMultilayerPromotionSolver?: SparseMultilayerPromotionSolver
|
|
33
30
|
boardVoidRects: XYRect[] | undefined
|
|
34
31
|
zIndexByName?: Map<string, number>
|
|
35
32
|
layerNames?: string[]
|
|
@@ -90,25 +87,6 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
|
|
|
90
87
|
},
|
|
91
88
|
],
|
|
92
89
|
),
|
|
93
|
-
definePipelineStep(
|
|
94
|
-
"sparseMultilayerPromotionSolver",
|
|
95
|
-
SparseMultilayerPromotionSolver,
|
|
96
|
-
(rectDiffPipeline: RectDiffPipeline) => [
|
|
97
|
-
{
|
|
98
|
-
meshNodes:
|
|
99
|
-
rectDiffPipeline.outerLayerContainmentMergeSolver?.getOutput()
|
|
100
|
-
.outputNodes ??
|
|
101
|
-
rectDiffPipeline.gapFillSolver?.getOutput().outputNodes ??
|
|
102
|
-
rectDiffPipeline.rectDiffGridSolverPipeline?.getOutput()
|
|
103
|
-
.meshNodes ??
|
|
104
|
-
[],
|
|
105
|
-
promotionTargetShare:
|
|
106
|
-
rectDiffPipeline.inputProblem
|
|
107
|
-
.sparseMultilayerPromotionTargetShare ?? 0.86,
|
|
108
|
-
simpleRouteJson: rectDiffPipeline.inputProblem.simpleRouteJson,
|
|
109
|
-
},
|
|
110
|
-
],
|
|
111
|
-
),
|
|
112
90
|
]
|
|
113
91
|
|
|
114
92
|
override _setup(): void {
|
|
@@ -142,11 +120,6 @@ export class RectDiffPipeline extends BasePipelineSolver<RectDiffPipelineInput>
|
|
|
142
120
|
override getOutput(): { meshNodes: CapacityMeshNode[] } {
|
|
143
121
|
const outerLayerMergeOutput =
|
|
144
122
|
this.outerLayerContainmentMergeSolver?.getOutput()
|
|
145
|
-
const sparseMultilayerOutput =
|
|
146
|
-
this.sparseMultilayerPromotionSolver?.getOutput()
|
|
147
|
-
if (sparseMultilayerOutput) {
|
|
148
|
-
return { meshNodes: sparseMultilayerOutput.outputNodes }
|
|
149
|
-
}
|
|
150
123
|
if (outerLayerMergeOutput) {
|
|
151
124
|
return { meshNodes: outerLayerMergeOutput.outputNodes }
|
|
152
125
|
}
|
|
@@ -29,22 +29,6 @@ const nodeToRect = (node: CapacityMeshNode): XYRect => ({
|
|
|
29
29
|
|
|
30
30
|
const rectArea = (rect: XYRect) => rect.width * rect.height
|
|
31
31
|
|
|
32
|
-
const intersectRects = (a: XYRect, b: XYRect): XYRect | null => {
|
|
33
|
-
const x0 = Math.max(a.x, b.x)
|
|
34
|
-
const y0 = Math.max(a.y, b.y)
|
|
35
|
-
const x1 = Math.min(a.x + a.width, b.x + b.width)
|
|
36
|
-
const y1 = Math.min(a.y + a.height, b.y + b.height)
|
|
37
|
-
|
|
38
|
-
if (x1 <= x0 + EPS || y1 <= y0 + EPS) return null
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
x: x0,
|
|
42
|
-
y: y0,
|
|
43
|
-
width: x1 - x0,
|
|
44
|
-
height: y1 - y0,
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
|
|
48
32
|
const cloneNode = (node: CapacityMeshNode): CapacityMeshNode => ({
|
|
49
33
|
...node,
|
|
50
34
|
center: { ...node.center },
|
|
@@ -103,7 +87,6 @@ const isFullyCoveredByRects = (target: XYRect, coveringRects: XYRect[]) => {
|
|
|
103
87
|
export class OuterLayerContainmentMergeSolver extends BaseSolver {
|
|
104
88
|
private outputNodes: CapacityMeshNode[] = []
|
|
105
89
|
private promotedNodeIds = new Set<string>()
|
|
106
|
-
private fullyPromotedNodeIds = new Set<string>()
|
|
107
90
|
private residualNodeIds = new Set<string>()
|
|
108
91
|
|
|
109
92
|
constructor(private input: OuterLayerContainmentMergeSolverInput) {
|
|
@@ -113,7 +96,6 @@ export class OuterLayerContainmentMergeSolver extends BaseSolver {
|
|
|
113
96
|
override _setup() {
|
|
114
97
|
this.outputNodes = this.input.meshNodes.map(cloneNode)
|
|
115
98
|
this.promotedNodeIds.clear()
|
|
116
|
-
this.fullyPromotedNodeIds.clear()
|
|
117
99
|
this.residualNodeIds.clear()
|
|
118
100
|
}
|
|
119
101
|
|
|
@@ -134,8 +116,6 @@ export class OuterLayerContainmentMergeSolver extends BaseSolver {
|
|
|
134
116
|
const viaMinSize = Math.max(srj.minViaDiameter ?? 0, srj.minTraceWidth || 0)
|
|
135
117
|
const originalNodes = this.input.meshNodes.map(cloneNode)
|
|
136
118
|
const obstaclesByLayer = this.buildObstaclesByLayer(layerCount)
|
|
137
|
-
const shouldAllowPartialPromotion =
|
|
138
|
-
this.getUsableMultilayerVolumeShare(originalNodes) < 0.5
|
|
139
119
|
const mutableOuterNodes = originalNodes.filter(
|
|
140
120
|
(node) =>
|
|
141
121
|
isFreeNode(node) &&
|
|
@@ -186,51 +166,23 @@ export class OuterLayerContainmentMergeSolver extends BaseSolver {
|
|
|
186
166
|
continue
|
|
187
167
|
}
|
|
188
168
|
if (!isFullyCoveredByRects(candidateRect, oppositeSupportRects)) {
|
|
189
|
-
if (!shouldAllowPartialPromotion) {
|
|
190
|
-
continue
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
const partialPromotionRects = this.getSupportedPromotionRects({
|
|
194
|
-
candidateRect,
|
|
195
|
-
supportRects: oppositeSupportRects,
|
|
196
|
-
minRectSize: viaMinSize,
|
|
197
|
-
})
|
|
198
|
-
|
|
199
|
-
if (partialPromotionRects.length === 0) {
|
|
200
|
-
continue
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
for (const partialRect of partialPromotionRects) {
|
|
204
|
-
const promotedNode = cloneNodeWithRect(
|
|
205
|
-
candidate,
|
|
206
|
-
partialRect,
|
|
207
|
-
`${candidate.capacityMeshNodeId}-outer-partial-${promotedNodes.length}`,
|
|
208
|
-
)
|
|
209
|
-
promotedNode.availableZ = [topZ, bottomZ]
|
|
210
|
-
promotedNode.layer = `z${topZ},${bottomZ}`
|
|
211
|
-
promotedNodes.push(promotedNode)
|
|
212
|
-
promotedRects.push(partialRect)
|
|
213
|
-
this.promotedNodeIds.add(promotedNode.capacityMeshNodeId)
|
|
214
|
-
}
|
|
215
169
|
continue
|
|
216
170
|
}
|
|
217
171
|
|
|
218
|
-
|
|
172
|
+
promotedNodes.push({
|
|
219
173
|
...candidate,
|
|
220
174
|
availableZ: [topZ, bottomZ],
|
|
221
175
|
layer: `z${topZ},${bottomZ}`,
|
|
222
|
-
}
|
|
223
|
-
promotedNodes.push(promotedNode)
|
|
176
|
+
})
|
|
224
177
|
promotedRects.push(candidateRect)
|
|
225
|
-
this.promotedNodeIds.add(
|
|
226
|
-
this.fullyPromotedNodeIds.add(candidate.capacityMeshNodeId)
|
|
178
|
+
this.promotedNodeIds.add(candidate.capacityMeshNodeId)
|
|
227
179
|
}
|
|
228
180
|
|
|
229
181
|
let nextResidualId = 0
|
|
230
182
|
const residualNodes: CapacityMeshNode[] = []
|
|
231
183
|
|
|
232
184
|
for (const node of mutableOuterNodes) {
|
|
233
|
-
if (this.
|
|
185
|
+
if (this.promotedNodeIds.has(node.capacityMeshNodeId)) {
|
|
234
186
|
continue
|
|
235
187
|
}
|
|
236
188
|
|
|
@@ -259,62 +211,6 @@ export class OuterLayerContainmentMergeSolver extends BaseSolver {
|
|
|
259
211
|
return [...immutableNodes, ...promotedNodes, ...residualNodes]
|
|
260
212
|
}
|
|
261
213
|
|
|
262
|
-
private getUsableMultilayerVolumeShare(nodes: CapacityMeshNode[]) {
|
|
263
|
-
let totalVolume = 0
|
|
264
|
-
let obstacleVolume = 0
|
|
265
|
-
let multilayerVolume = 0
|
|
266
|
-
|
|
267
|
-
for (const node of nodes) {
|
|
268
|
-
const volume = node.width * node.height * node.availableZ.length
|
|
269
|
-
totalVolume += volume
|
|
270
|
-
if (node._containsObstacle) {
|
|
271
|
-
obstacleVolume += volume
|
|
272
|
-
continue
|
|
273
|
-
}
|
|
274
|
-
if (node.availableZ.length > 1) {
|
|
275
|
-
multilayerVolume += volume
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const usableVolume = totalVolume - obstacleVolume
|
|
280
|
-
if (usableVolume <= EPS) return 0
|
|
281
|
-
return multilayerVolume / usableVolume
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
private getSupportedPromotionRects(params: {
|
|
285
|
-
candidateRect: XYRect
|
|
286
|
-
supportRects: XYRect[]
|
|
287
|
-
minRectSize: number
|
|
288
|
-
}) {
|
|
289
|
-
const { candidateRect, supportRects, minRectSize } = params
|
|
290
|
-
const promotedPieces: XYRect[] = []
|
|
291
|
-
|
|
292
|
-
for (const supportRect of supportRects) {
|
|
293
|
-
const overlapRect = intersectRects(candidateRect, supportRect)
|
|
294
|
-
if (!overlapRect) continue
|
|
295
|
-
|
|
296
|
-
let remainingPieces = [overlapRect]
|
|
297
|
-
for (const existingPiece of promotedPieces) {
|
|
298
|
-
remainingPieces = remainingPieces.flatMap((piece) =>
|
|
299
|
-
subtractRect2D(piece, existingPiece),
|
|
300
|
-
)
|
|
301
|
-
if (remainingPieces.length === 0) break
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
for (const piece of remainingPieces) {
|
|
305
|
-
if (
|
|
306
|
-
piece.width + EPS < minRectSize ||
|
|
307
|
-
piece.height + EPS < minRectSize
|
|
308
|
-
) {
|
|
309
|
-
continue
|
|
310
|
-
}
|
|
311
|
-
promotedPieces.push(piece)
|
|
312
|
-
}
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
return promotedPieces
|
|
316
|
-
}
|
|
317
|
-
|
|
318
214
|
private buildObstaclesByLayer(layerCount: number): ObstacleWithRect[][] {
|
|
319
215
|
const out = Array.from(
|
|
320
216
|
{ length: layerCount },
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tscircuit/rectdiff",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "lib/index.ts",
|
|
6
6
|
"types": "lib/index.ts",
|
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@biomejs/biome": "^2.3.5",
|
|
20
20
|
"@react-hook/resize-observer": "^2.0.2",
|
|
21
|
+
"@tscircuit/autorouting-dataset-01": "^1.0.32",
|
|
22
|
+
"@tscircuit/capacity-autorouter": "https://npm-releases.tscircuit.com/npm-tarballs/25314012755-1/tscircuit-capacity-autorouter-0.0.489.tgz",
|
|
21
23
|
"@tscircuit/math-utils": "^0.0.29",
|
|
22
24
|
"@tscircuit/solver-utils": "^0.0.16",
|
|
23
25
|
"@types/bun": "latest",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { RectDiffPipeline } from "../../../lib/RectDiffPipeline"
|
|
2
|
+
import type { WorkerTaskMessage } from "../benchmark-types"
|
|
3
|
+
import { getAutorouterPipeline4ModulePath } from "./getAutorouterPipeline4ModulePath"
|
|
4
|
+
import { importRuntimeModule } from "./importRuntimeModule"
|
|
5
|
+
import type { Pipeline4Constructor } from "./types"
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates the benchmark solver with the repo-local RectDiff pipeline override.
|
|
9
|
+
* The benchmark imports Pipeline 4 from the published autorouter bundle while
|
|
10
|
+
* forcing it to instantiate this repo's RectDiff implementation by default.
|
|
11
|
+
*/
|
|
12
|
+
export const createSolver = async (
|
|
13
|
+
scenario: WorkerTaskMessage["task"]["scenario"],
|
|
14
|
+
RectDiffPipelineClass: typeof RectDiffPipeline = RectDiffPipeline,
|
|
15
|
+
) => {
|
|
16
|
+
// Load Pipeline 4 from the installed capacity-autorouter dist bundle, then
|
|
17
|
+
// inject the RectDiff class explicitly through its runtime override hook.
|
|
18
|
+
const solverModule = await importRuntimeModule(
|
|
19
|
+
getAutorouterPipeline4ModulePath(),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
// TODO: Replace this cast once capacity-autorouter exposes a typed
|
|
23
|
+
// Pipeline 4 override interface. As of this repo state, the installed
|
|
24
|
+
// Pipeline 4 constructor only exposes CapacityMeshSolverOptions and does
|
|
25
|
+
// not type the benchmark's overrides.RectDiffPipelineClass injection.
|
|
26
|
+
const AutoroutingPipelineSolver4 = (solverModule as any)
|
|
27
|
+
.AutoroutingPipelineSolver4 as Pipeline4Constructor
|
|
28
|
+
|
|
29
|
+
return new AutoroutingPipelineSolver4(scenario, {
|
|
30
|
+
overrides: {
|
|
31
|
+
RectDiffPipelineClass,
|
|
32
|
+
},
|
|
33
|
+
})
|
|
34
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the published capacity-autorouter dist entry for benchmarking.
|
|
3
|
+
* The benchmark relies on the bundled runtime export instead of package-
|
|
4
|
+
* internal source paths.
|
|
5
|
+
*/
|
|
6
|
+
export const getAutorouterPipeline4ModulePath = () =>
|
|
7
|
+
import.meta.resolve("@tscircuit/capacity-autorouter")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { WorkerResult, WorkerTaskMessage } from "../benchmark-types"
|
|
2
|
+
import { NEXT_PHASE_AFTER_PORT_POINT_PATHING } from "./constants"
|
|
3
|
+
import { createSolver } from "./createSolver"
|
|
4
|
+
import { writeWorkerResultMessage } from "./writeWorkerResultMessage"
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Handles one benchmark request line from the parent process.
|
|
8
|
+
* Each line must contain a single JSON-encoded worker task message.
|
|
9
|
+
*/
|
|
10
|
+
export const handleWorkerTaskLine = async (line: string) => {
|
|
11
|
+
if (!line.trim()) {
|
|
12
|
+
return
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
let message: WorkerTaskMessage
|
|
16
|
+
try {
|
|
17
|
+
message = JSON.parse(line) as WorkerTaskMessage
|
|
18
|
+
} catch (error) {
|
|
19
|
+
const result: WorkerResult = {
|
|
20
|
+
scenarioName: "unknown",
|
|
21
|
+
elapsedTimeMs: 0,
|
|
22
|
+
didSolve: false,
|
|
23
|
+
didTimeout: false,
|
|
24
|
+
error: `Invalid worker message: ${String(error)}`,
|
|
25
|
+
}
|
|
26
|
+
writeWorkerResultMessage(result)
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const startedAt = performance.now()
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const solver = await createSolver(message.task.scenario)
|
|
34
|
+
|
|
35
|
+
while (
|
|
36
|
+
!solver.failed &&
|
|
37
|
+
solver.getCurrentPhase() !== NEXT_PHASE_AFTER_PORT_POINT_PATHING
|
|
38
|
+
) {
|
|
39
|
+
solver.step()
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const result: WorkerResult = {
|
|
43
|
+
scenarioName: message.task.scenarioName,
|
|
44
|
+
elapsedTimeMs: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
45
|
+
didSolve:
|
|
46
|
+
!solver.failed &&
|
|
47
|
+
solver.getCurrentPhase() === NEXT_PHASE_AFTER_PORT_POINT_PATHING &&
|
|
48
|
+
solver.portPointPathingSolver !== undefined,
|
|
49
|
+
didTimeout: false,
|
|
50
|
+
error: solver.failed ? solver.error : null,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
writeWorkerResultMessage(result)
|
|
54
|
+
} catch (error) {
|
|
55
|
+
const result: WorkerResult = {
|
|
56
|
+
scenarioName: message.task.scenarioName,
|
|
57
|
+
elapsedTimeMs: Math.max(0, Math.round(performance.now() - startedAt)),
|
|
58
|
+
didSolve: false,
|
|
59
|
+
didTimeout: false,
|
|
60
|
+
error: String(error),
|
|
61
|
+
}
|
|
62
|
+
writeWorkerResultMessage(result)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Imports a runtime-resolved module path.
|
|
3
|
+
* This keeps the benchmark loader on a runtime-only import path.
|
|
4
|
+
*/
|
|
5
|
+
export const importRuntimeModule = (modulePath: string) =>
|
|
6
|
+
new Function("modulePath", "return import(modulePath)")(
|
|
7
|
+
modulePath,
|
|
8
|
+
) as Promise<unknown>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Describes the subset of solver state the benchmark worker needs.
|
|
3
|
+
* This keeps the child process decoupled from internal package typings.
|
|
4
|
+
*/
|
|
5
|
+
export type BenchmarkWorkerSolver = {
|
|
6
|
+
failed: boolean
|
|
7
|
+
error: string | null
|
|
8
|
+
portPointPathingSolver?: unknown
|
|
9
|
+
getCurrentPhase(): string
|
|
10
|
+
step(): void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Represents the Pipeline 4 constructor shape used by the benchmark.
|
|
15
|
+
* It allows the worker to inject the local RectDiff override.
|
|
16
|
+
*/
|
|
17
|
+
export type Pipeline4Constructor = new (
|
|
18
|
+
srj: unknown,
|
|
19
|
+
opts?: Record<string, unknown>,
|
|
20
|
+
) => BenchmarkWorkerSolver
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WorkerResult, WorkerResultMessage } from "../benchmark-types"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Writes a worker result message as a single JSON line.
|
|
5
|
+
* The parent benchmark process reads these messages over stdout.
|
|
6
|
+
*/
|
|
7
|
+
export const writeWorkerResultMessage = (result: WorkerResult) => {
|
|
8
|
+
const response: WorkerResultMessage = { result }
|
|
9
|
+
process.stdout.write(`${JSON.stringify(response)}\n`)
|
|
10
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SimpleRouteJson } from "../../lib/types/srj-types"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Defines one benchmark scenario loaded from the dataset package.
|
|
5
|
+
*/
|
|
6
|
+
export type BenchmarkTask = {
|
|
7
|
+
scenarioName: string
|
|
8
|
+
scenario: SimpleRouteJson
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Wraps a benchmark task for line-delimited worker communication.
|
|
13
|
+
*/
|
|
14
|
+
export type WorkerTaskMessage = {
|
|
15
|
+
task: BenchmarkTask
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Captures the outcome of a single benchmark worker run.
|
|
20
|
+
*/
|
|
21
|
+
export type WorkerResult = {
|
|
22
|
+
scenarioName: string
|
|
23
|
+
elapsedTimeMs: number
|
|
24
|
+
didSolve: boolean
|
|
25
|
+
didTimeout: boolean
|
|
26
|
+
error: string | null
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Wraps a worker result for line-delimited stdout transport.
|
|
31
|
+
*/
|
|
32
|
+
export type WorkerResultMessage = {
|
|
33
|
+
result: WorkerResult
|
|
34
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
|
|
3
|
+
import * as readline from "node:readline"
|
|
4
|
+
import { handleWorkerTaskLine } from "./benchmark-child/handleWorkerTaskLine"
|
|
5
|
+
|
|
6
|
+
const reader = readline.createInterface({
|
|
7
|
+
input: process.stdin,
|
|
8
|
+
crlfDelay: Infinity,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
reader.on("line", handleWorkerTaskLine)
|