@tscircuit/schematic-trace-solver 0.0.1
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/.claude/settings.local.json +6 -0
- package/.github/workflows/bun-formatcheck.yml +26 -0
- package/.github/workflows/bun-pver-release.yml +28 -0
- package/.github/workflows/bun-test.yml +28 -0
- package/.github/workflows/bun-typecheck.yml +26 -0
- package/LICENSE +21 -0
- package/README.md +75 -0
- package/biome.json +93 -0
- package/bunfig.toml +5 -0
- package/cosmos.config.json +6 -0
- package/cosmos.decorator.tsx +21 -0
- package/dist/index.d.ts +420 -0
- package/dist/index.js +7863 -0
- package/index.html +17 -0
- package/lib/data-structures/ChipObstacleSpatialIndex.ts +70 -0
- package/lib/index.ts +2 -0
- package/lib/solvers/BaseSolver/BaseSolver.ts +83 -0
- package/lib/solvers/GuidelinesSolver/GuidelinesSolver.ts +138 -0
- package/lib/solvers/GuidelinesSolver/getGeneratorForAllChipPairs.ts +39 -0
- package/lib/solvers/GuidelinesSolver/getHorizontalGuidelineY.ts +18 -0
- package/lib/solvers/GuidelinesSolver/getInputChipBounds.ts +20 -0
- package/lib/solvers/GuidelinesSolver/getVerticalGuidelineX.ts +18 -0
- package/lib/solvers/GuidelinesSolver/visualizeGuidelines.ts +45 -0
- package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +156 -0
- package/lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts +20 -0
- package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +96 -0
- package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +192 -0
- package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +479 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +127 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts +191 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts +132 -0
- package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts +42 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +244 -0
- package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +89 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +180 -0
- package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +264 -0
- package/lib/types/InputProblem.ts +40 -0
- package/lib/utils/dir.ts +16 -0
- package/lib/utils/getAllPossibleOrderingsGenerator.ts +47 -0
- package/lib/utils/getColorFromString.ts +7 -0
- package/package.json +29 -0
- package/site/components/GenericSolverDebugger.tsx +0 -0
- package/site/components/PipelineDebugger.tsx +29 -0
- package/site/components/PipelineStageTable.tsx +149 -0
- package/site/components/SolverBreadcrumbInputDownloader.tsx +62 -0
- package/site/components/SolverToolbar.tsx +128 -0
- package/site/examples/example01-basic.page.tsx +104 -0
- package/tests/functions/generateElbowVariants.test.ts +98 -0
- package/tests/functions/getOrthogonalMinimumSpanningTree.test.ts +23 -0
- package/tsconfig.json +37 -0
- package/vite.config.ts +12 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { InputPin, PinId } from "lib/types/InputProblem"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Compute the Orthogonal (Manhattan/L1) Minimum Spanning Tree (MST)
|
|
5
|
+
* for a set of points and return it as pairs of connected PinIds.
|
|
6
|
+
*
|
|
7
|
+
* The MST minimizes total |Δx| + |Δy| distance and fully connects all points.
|
|
8
|
+
* Uses Prim's algorithm with O(n^2) time and O(n) extra space.
|
|
9
|
+
*
|
|
10
|
+
* Edge cases:
|
|
11
|
+
* - [] -> []
|
|
12
|
+
* - [one point] -> []
|
|
13
|
+
* - duplicate coordinates are allowed (0-length edges possible)
|
|
14
|
+
*
|
|
15
|
+
* Deterministic tie-breaking:
|
|
16
|
+
* - When multiple candidates have the same distance, we pick the one
|
|
17
|
+
* whose pinId is lexicographically smallest (for stable output).
|
|
18
|
+
*/
|
|
19
|
+
export function getOrthogonalMinimumSpanningTree(
|
|
20
|
+
pins: InputPin[],
|
|
21
|
+
): Array<[PinId, PinId]> {
|
|
22
|
+
const n = pins.length
|
|
23
|
+
if (n <= 1) return []
|
|
24
|
+
|
|
25
|
+
// Quick validation (optional; remove if hot path)
|
|
26
|
+
// Ensure pinIds are unique to avoid ambiguous output edges.
|
|
27
|
+
{
|
|
28
|
+
const seen = new Set<string>()
|
|
29
|
+
for (const p of pins) {
|
|
30
|
+
if (seen.has(p.pinId)) {
|
|
31
|
+
throw new Error(`Duplicate pinId detected: "${p.pinId}"`)
|
|
32
|
+
}
|
|
33
|
+
seen.add(p.pinId)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Helper: Manhattan distance
|
|
38
|
+
const manhattan = (a: InputPin, b: InputPin) =>
|
|
39
|
+
Math.abs(a.x - b.x) + Math.abs(a.y - b.y)
|
|
40
|
+
|
|
41
|
+
// Prim's data structures
|
|
42
|
+
const inTree = new Array<boolean>(n).fill(false)
|
|
43
|
+
const bestDist = new Array<number>(n).fill(Number.POSITIVE_INFINITY)
|
|
44
|
+
const parent = new Array<number>(n).fill(-1)
|
|
45
|
+
|
|
46
|
+
// Start from the point with lexicographically smallest pinId (stable)
|
|
47
|
+
let startIndex = 0
|
|
48
|
+
for (let i = 1; i < n; i++) {
|
|
49
|
+
if (pins[i].pinId < pins[startIndex].pinId) startIndex = i
|
|
50
|
+
}
|
|
51
|
+
bestDist[startIndex] = 0
|
|
52
|
+
|
|
53
|
+
const edges: Array<[PinId, PinId]> = []
|
|
54
|
+
for (let iter = 0; iter < n; iter++) {
|
|
55
|
+
// Pick the next vertex u with minimal bestDist[u] not yet in the tree
|
|
56
|
+
let u = -1
|
|
57
|
+
let best = Number.POSITIVE_INFINITY
|
|
58
|
+
let bestId = "" // for tie-breaking
|
|
59
|
+
for (let i = 0; i < n; i++) {
|
|
60
|
+
if (!inTree[i]) {
|
|
61
|
+
const d = bestDist[i]
|
|
62
|
+
if (
|
|
63
|
+
d < best ||
|
|
64
|
+
(d === best && (bestId === "" || pins[i].pinId < bestId))
|
|
65
|
+
) {
|
|
66
|
+
best = d
|
|
67
|
+
bestId = pins[i].pinId
|
|
68
|
+
u = i
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Add u to the tree
|
|
74
|
+
inTree[u] = true
|
|
75
|
+
if (parent[u] !== -1) {
|
|
76
|
+
edges.push([pins[u].pinId, pins[parent[u]].pinId])
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Relax edges from u to all v not yet in the tree
|
|
80
|
+
for (let v = 0; v < n; v++) {
|
|
81
|
+
if (!inTree[v]) {
|
|
82
|
+
const d = manhattan(pins[u], pins[v])
|
|
83
|
+
if (
|
|
84
|
+
d < bestDist[v] ||
|
|
85
|
+
(d === bestDist[v] && pins[u].pinId < pins[parent[v]]?.pinId)
|
|
86
|
+
) {
|
|
87
|
+
bestDist[v] = d
|
|
88
|
+
parent[v] = u
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// edges.length === n-1 when n>0
|
|
95
|
+
return edges
|
|
96
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
2
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
3
|
+
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
4
|
+
import type { MspConnectionPairId } from "../MspConnectionPairSolver/MspConnectionPairSolver"
|
|
5
|
+
import { SingleNetLabelPlacementSolver } from "./SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver"
|
|
6
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
7
|
+
import type { Point } from "@tscircuit/math-utils"
|
|
8
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
9
|
+
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
10
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A group of traces that have at least one overlapping segment and
|
|
14
|
+
* are part of the same global connectivity net
|
|
15
|
+
*/
|
|
16
|
+
export type OverlappingSameNetTraceGroup = {
|
|
17
|
+
globalConnNetId: string
|
|
18
|
+
netId?: string
|
|
19
|
+
overlappingTraces: SolvedTracePath
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface NetLabelPlacement {
|
|
23
|
+
globalConnNetId: string
|
|
24
|
+
dcConnNetId?: string
|
|
25
|
+
orientation: FacingDirection
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The anchor point is the point on the trace where the net label connects
|
|
29
|
+
*/
|
|
30
|
+
anchorPoint: Point
|
|
31
|
+
|
|
32
|
+
width: number
|
|
33
|
+
height: number
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The center point is computed from the anchor point, the width and height
|
|
37
|
+
* and the orientation.
|
|
38
|
+
*/
|
|
39
|
+
center: Point
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Places net labels in an available orientation along a trace in each group.
|
|
44
|
+
*
|
|
45
|
+
* Trace groups each receive either one net label or no net label if there
|
|
46
|
+
* isn't a netId.
|
|
47
|
+
*
|
|
48
|
+
* The specific placement of the net label is solved for using the
|
|
49
|
+
*/
|
|
50
|
+
export class NetLabelPlacementSolver extends BaseSolver {
|
|
51
|
+
inputProblem: InputProblem
|
|
52
|
+
inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>
|
|
53
|
+
|
|
54
|
+
overlappingSameNetTraceGroups: Array<OverlappingSameNetTraceGroup>
|
|
55
|
+
|
|
56
|
+
queuedOverlappingSameNetTraceGroups: Array<OverlappingSameNetTraceGroup>
|
|
57
|
+
|
|
58
|
+
declare activeSubSolver: SingleNetLabelPlacementSolver | null
|
|
59
|
+
|
|
60
|
+
netLabelPlacements: Array<NetLabelPlacement> = []
|
|
61
|
+
|
|
62
|
+
constructor(params: {
|
|
63
|
+
inputProblem: InputProblem
|
|
64
|
+
inputTraceMap: Record<MspConnectionPairId, SolvedTracePath>
|
|
65
|
+
}) {
|
|
66
|
+
super()
|
|
67
|
+
this.inputProblem = params.inputProblem
|
|
68
|
+
this.inputTraceMap = params.inputTraceMap
|
|
69
|
+
|
|
70
|
+
this.overlappingSameNetTraceGroups =
|
|
71
|
+
this.computeOverlappingSameNetTraceGroups()
|
|
72
|
+
|
|
73
|
+
this.queuedOverlappingSameNetTraceGroups = [
|
|
74
|
+
...this.overlappingSameNetTraceGroups,
|
|
75
|
+
]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
computeOverlappingSameNetTraceGroups(): Array<OverlappingSameNetTraceGroup> {
|
|
79
|
+
// Group traces by their global connectivity net id.
|
|
80
|
+
const byGlobal: Record<string, Array<SolvedTracePath>> = {}
|
|
81
|
+
for (const trace of Object.values(this.inputTraceMap)) {
|
|
82
|
+
const key = trace.globalConnNetId
|
|
83
|
+
if (!byGlobal[key]) byGlobal[key] = []
|
|
84
|
+
byGlobal[key].push(trace)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// For each group, pick a representative path (longest by L1 length).
|
|
88
|
+
const groups: Array<OverlappingSameNetTraceGroup> = []
|
|
89
|
+
for (const [globalConnNetId, traces] of Object.entries(byGlobal)) {
|
|
90
|
+
if (traces.length === 0) continue
|
|
91
|
+
const lengthOf = (path: SolvedTracePath) => {
|
|
92
|
+
let sum = 0
|
|
93
|
+
const pts = path.tracePath
|
|
94
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
95
|
+
sum +=
|
|
96
|
+
Math.abs(pts[i + 1]!.x - pts[i]!.x) +
|
|
97
|
+
Math.abs(pts[i + 1]!.y - pts[i]!.y)
|
|
98
|
+
}
|
|
99
|
+
return sum
|
|
100
|
+
}
|
|
101
|
+
let rep = traces[0]!
|
|
102
|
+
let repLen = lengthOf(rep)
|
|
103
|
+
for (let i = 1; i < traces.length; i++) {
|
|
104
|
+
const len = lengthOf(traces[i]!)
|
|
105
|
+
if (len > repLen) {
|
|
106
|
+
rep = traces[i]!
|
|
107
|
+
repLen = len
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const userNetId = traces.find((t) => t.userNetId != null)?.userNetId
|
|
111
|
+
groups.push({
|
|
112
|
+
globalConnNetId,
|
|
113
|
+
netId: userNetId,
|
|
114
|
+
overlappingTraces: rep,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return groups
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
override _step() {
|
|
122
|
+
if (this.activeSubSolver?.solved) {
|
|
123
|
+
this.netLabelPlacements.push(this.activeSubSolver.netLabelPlacement!)
|
|
124
|
+
this.activeSubSolver = null
|
|
125
|
+
return
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (this.activeSubSolver?.failed) {
|
|
129
|
+
this.failed = true
|
|
130
|
+
this.error = this.activeSubSolver.error
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (this.activeSubSolver) {
|
|
135
|
+
this.activeSubSolver.step()
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const nextOverlappingSameNetTraceGroup =
|
|
140
|
+
this.queuedOverlappingSameNetTraceGroups.shift()
|
|
141
|
+
|
|
142
|
+
if (!nextOverlappingSameNetTraceGroup) {
|
|
143
|
+
this.solved = true
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const netId =
|
|
148
|
+
nextOverlappingSameNetTraceGroup.netId ??
|
|
149
|
+
nextOverlappingSameNetTraceGroup.globalConnNetId
|
|
150
|
+
|
|
151
|
+
this.activeSubSolver = new SingleNetLabelPlacementSolver({
|
|
152
|
+
inputProblem: this.inputProblem,
|
|
153
|
+
inputTraceMap: this.inputTraceMap,
|
|
154
|
+
overlappingSameNetTraceGroup: nextOverlappingSameNetTraceGroup,
|
|
155
|
+
availableOrientations: this.inputProblem.availableNetLabelOrientations[
|
|
156
|
+
netId
|
|
157
|
+
] ?? ["x+", "x-", "y+", "y-"],
|
|
158
|
+
})
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
override visualize(): GraphicsObject {
|
|
162
|
+
if (this.activeSubSolver) {
|
|
163
|
+
return this.activeSubSolver.visualize()
|
|
164
|
+
}
|
|
165
|
+
const graphics = visualizeInputProblem(this.inputProblem)
|
|
166
|
+
|
|
167
|
+
for (const trace of Object.values(this.inputTraceMap)) {
|
|
168
|
+
graphics.lines!.push({
|
|
169
|
+
points: trace.tracePath,
|
|
170
|
+
strokeColor: "purple",
|
|
171
|
+
strokeWidth: 0.005,
|
|
172
|
+
})
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const p of this.netLabelPlacements) {
|
|
176
|
+
graphics.rects!.push({
|
|
177
|
+
center: p.center,
|
|
178
|
+
width: p.width,
|
|
179
|
+
height: p.height,
|
|
180
|
+
fill: getColorFromString(p.globalConnNetId, 0.35),
|
|
181
|
+
strokeColor: getColorFromString(p.globalConnNetId, 0.9),
|
|
182
|
+
} as any)
|
|
183
|
+
graphics.points!.push({
|
|
184
|
+
x: p.anchorPoint.x,
|
|
185
|
+
y: p.anchorPoint.y,
|
|
186
|
+
color: getColorFromString(p.globalConnNetId, 0.9),
|
|
187
|
+
} as any)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return graphics
|
|
191
|
+
}
|
|
192
|
+
}
|