@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.
Files changed (51) hide show
  1. package/.claude/settings.local.json +6 -0
  2. package/.github/workflows/bun-formatcheck.yml +26 -0
  3. package/.github/workflows/bun-pver-release.yml +28 -0
  4. package/.github/workflows/bun-test.yml +28 -0
  5. package/.github/workflows/bun-typecheck.yml +26 -0
  6. package/LICENSE +21 -0
  7. package/README.md +75 -0
  8. package/biome.json +93 -0
  9. package/bunfig.toml +5 -0
  10. package/cosmos.config.json +6 -0
  11. package/cosmos.decorator.tsx +21 -0
  12. package/dist/index.d.ts +420 -0
  13. package/dist/index.js +7863 -0
  14. package/index.html +17 -0
  15. package/lib/data-structures/ChipObstacleSpatialIndex.ts +70 -0
  16. package/lib/index.ts +2 -0
  17. package/lib/solvers/BaseSolver/BaseSolver.ts +83 -0
  18. package/lib/solvers/GuidelinesSolver/GuidelinesSolver.ts +138 -0
  19. package/lib/solvers/GuidelinesSolver/getGeneratorForAllChipPairs.ts +39 -0
  20. package/lib/solvers/GuidelinesSolver/getHorizontalGuidelineY.ts +18 -0
  21. package/lib/solvers/GuidelinesSolver/getInputChipBounds.ts +20 -0
  22. package/lib/solvers/GuidelinesSolver/getVerticalGuidelineX.ts +18 -0
  23. package/lib/solvers/GuidelinesSolver/visualizeGuidelines.ts +45 -0
  24. package/lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts +156 -0
  25. package/lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem.ts +20 -0
  26. package/lib/solvers/MspConnectionPairSolver/getMspConnectionPairsFromPins.ts +96 -0
  27. package/lib/solvers/NetLabelPlacementSolver/NetLabelPlacementSolver.ts +192 -0
  28. package/lib/solvers/NetLabelPlacementSolver/SingleNetLabelPlacementSolver/SingleNetLabelPlacementSolver.ts +479 -0
  29. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver.ts +127 -0
  30. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/SchematicTraceSingleLineSolver.ts +191 -0
  31. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/generateElbowVariants.ts +132 -0
  32. package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts +42 -0
  33. package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +244 -0
  34. package/lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem.ts +89 -0
  35. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts +180 -0
  36. package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapShiftSolver.ts +264 -0
  37. package/lib/types/InputProblem.ts +40 -0
  38. package/lib/utils/dir.ts +16 -0
  39. package/lib/utils/getAllPossibleOrderingsGenerator.ts +47 -0
  40. package/lib/utils/getColorFromString.ts +7 -0
  41. package/package.json +29 -0
  42. package/site/components/GenericSolverDebugger.tsx +0 -0
  43. package/site/components/PipelineDebugger.tsx +29 -0
  44. package/site/components/PipelineStageTable.tsx +149 -0
  45. package/site/components/SolverBreadcrumbInputDownloader.tsx +62 -0
  46. package/site/components/SolverToolbar.tsx +128 -0
  47. package/site/examples/example01-basic.page.tsx +104 -0
  48. package/tests/functions/generateElbowVariants.test.ts +98 -0
  49. package/tests/functions/getOrthogonalMinimumSpanningTree.test.ts +23 -0
  50. package/tsconfig.json +37 -0
  51. 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
+ }