@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
package/lib/solvers/TraceOverlapShiftSolver/TraceOverlapIssueSolver/TraceOverlapIssueSolver.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
3
|
+
import type { MspConnectionPairId } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
4
|
+
import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
|
|
6
|
+
type ConnNetId = string
|
|
7
|
+
|
|
8
|
+
export interface OverlappingTraceSegmentLocator {
|
|
9
|
+
connNetId: string
|
|
10
|
+
pathsWithOverlap: Array<{
|
|
11
|
+
solvedTracePathIndex: number
|
|
12
|
+
traceSegmentIndex: number
|
|
13
|
+
}>
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class TraceOverlapIssueSolver extends BaseSolver {
|
|
17
|
+
overlappingTraceSegments: OverlappingTraceSegmentLocator[]
|
|
18
|
+
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
|
|
19
|
+
|
|
20
|
+
SHIFT_DISTANCE = 0.1
|
|
21
|
+
|
|
22
|
+
correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath> = {}
|
|
23
|
+
|
|
24
|
+
constructor(params: {
|
|
25
|
+
overlappingTraceSegments: OverlappingTraceSegmentLocator[]
|
|
26
|
+
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>>
|
|
27
|
+
}) {
|
|
28
|
+
super()
|
|
29
|
+
this.overlappingTraceSegments = params.overlappingTraceSegments
|
|
30
|
+
this.traceNetIslands = params.traceNetIslands
|
|
31
|
+
|
|
32
|
+
// Only add the relevant traces to the correctedTraceMap
|
|
33
|
+
for (const { connNetId, pathsWithOverlap } of this
|
|
34
|
+
.overlappingTraceSegments) {
|
|
35
|
+
for (const {
|
|
36
|
+
solvedTracePathIndex,
|
|
37
|
+
traceSegmentIndex,
|
|
38
|
+
} of pathsWithOverlap) {
|
|
39
|
+
const mspPairId =
|
|
40
|
+
this.traceNetIslands[connNetId][solvedTracePathIndex].mspPairId
|
|
41
|
+
this.correctedTraceMap[mspPairId] =
|
|
42
|
+
this.traceNetIslands[connNetId][solvedTracePathIndex]
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
override _step() {
|
|
48
|
+
// Shift only the overlapping segments, and move the shared endpoints
|
|
49
|
+
// (the last point of the previous segment and the first point of the next
|
|
50
|
+
// segment) so the polyline remains orthogonal without self-overlap.
|
|
51
|
+
const EPS = 1e-6
|
|
52
|
+
|
|
53
|
+
// Compute offsets for each island involved: alternate directions
|
|
54
|
+
const offsets = this.overlappingTraceSegments.map((_, idx) => {
|
|
55
|
+
const n = Math.floor(idx / 2) + 1
|
|
56
|
+
const signed = idx % 2 === 0 ? -n : n
|
|
57
|
+
return signed * this.SHIFT_DISTANCE
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const eq = (a: number, b: number) => Math.abs(a - b) < EPS
|
|
61
|
+
const samePoint = (
|
|
62
|
+
p: { x: number; y: number } | undefined,
|
|
63
|
+
q: { x: number; y: number } | undefined,
|
|
64
|
+
) => !!p && !!q && eq(p.x, q.x) && eq(p.y, q.y)
|
|
65
|
+
|
|
66
|
+
// For each net island group, shift only its overlapping segments and adjust adjacent joints
|
|
67
|
+
this.overlappingTraceSegments.forEach((group, gidx) => {
|
|
68
|
+
const offset = offsets[gidx]!
|
|
69
|
+
|
|
70
|
+
// Gather unique segment indices per path
|
|
71
|
+
const byPath: Map<number, Set<number>> = new Map()
|
|
72
|
+
for (const loc of group.pathsWithOverlap) {
|
|
73
|
+
if (!byPath.has(loc.solvedTracePathIndex)) {
|
|
74
|
+
byPath.set(loc.solvedTracePathIndex, new Set())
|
|
75
|
+
}
|
|
76
|
+
byPath.get(loc.solvedTracePathIndex)!.add(loc.traceSegmentIndex)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const [pathIdx, segIdxSet] of byPath) {
|
|
80
|
+
const original = this.traceNetIslands[group.connNetId][pathIdx]!
|
|
81
|
+
const current = this.correctedTraceMap[original.mspPairId] ?? original
|
|
82
|
+
const pts = current.tracePath.map((p) => ({ ...p }))
|
|
83
|
+
|
|
84
|
+
const segIdxs = Array.from(segIdxSet).sort((a, b) => a - b)
|
|
85
|
+
|
|
86
|
+
// Track per-point adjustments to avoid double-shifting shared joints
|
|
87
|
+
const appliedX = new Set<number>()
|
|
88
|
+
const appliedY = new Set<number>()
|
|
89
|
+
|
|
90
|
+
for (const si of segIdxs) {
|
|
91
|
+
if (si < 0 || si >= pts.length - 1) continue
|
|
92
|
+
const start = pts[si]!
|
|
93
|
+
const end = pts[si + 1]!
|
|
94
|
+
const isVertical = Math.abs(start.x - end.x) < EPS
|
|
95
|
+
const isHorizontal = Math.abs(start.y - end.y) < EPS
|
|
96
|
+
|
|
97
|
+
if (!isVertical && !isHorizontal) continue
|
|
98
|
+
|
|
99
|
+
if (isVertical) {
|
|
100
|
+
if (!appliedX.has(si)) {
|
|
101
|
+
start.x += offset
|
|
102
|
+
appliedX.add(si)
|
|
103
|
+
}
|
|
104
|
+
if (!appliedX.has(si + 1)) {
|
|
105
|
+
end.x += offset
|
|
106
|
+
appliedX.add(si + 1)
|
|
107
|
+
}
|
|
108
|
+
} else if (isHorizontal) {
|
|
109
|
+
if (!appliedY.has(si)) {
|
|
110
|
+
start.y += offset
|
|
111
|
+
appliedY.add(si)
|
|
112
|
+
}
|
|
113
|
+
if (!appliedY.has(si + 1)) {
|
|
114
|
+
end.y += offset
|
|
115
|
+
appliedY.add(si + 1)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Remove consecutive duplicate points that might appear after shifts
|
|
121
|
+
const cleaned: typeof pts = []
|
|
122
|
+
for (const p of pts) {
|
|
123
|
+
if (
|
|
124
|
+
cleaned.length === 0 ||
|
|
125
|
+
!samePoint(cleaned[cleaned.length - 1], p)
|
|
126
|
+
) {
|
|
127
|
+
cleaned.push(p)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.correctedTraceMap[original.mspPairId] = {
|
|
132
|
+
...current,
|
|
133
|
+
tracePath: cleaned,
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
this.solved = true
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
override visualize(): GraphicsObject {
|
|
142
|
+
// Visualize overlapped segments and proposed corrections
|
|
143
|
+
const graphics: GraphicsObject = {
|
|
144
|
+
lines: [],
|
|
145
|
+
points: [],
|
|
146
|
+
rects: [],
|
|
147
|
+
circles: [],
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Draw overlapped segments in red
|
|
151
|
+
for (const group of this.overlappingTraceSegments) {
|
|
152
|
+
for (const {
|
|
153
|
+
solvedTracePathIndex,
|
|
154
|
+
traceSegmentIndex,
|
|
155
|
+
} of group.pathsWithOverlap) {
|
|
156
|
+
const path =
|
|
157
|
+
this.traceNetIslands[group.connNetId][solvedTracePathIndex]!
|
|
158
|
+
const segStart = path.tracePath[traceSegmentIndex]!
|
|
159
|
+
const segEnd = path.tracePath[traceSegmentIndex + 1]!
|
|
160
|
+
graphics.lines!.push({
|
|
161
|
+
points: [segStart, segEnd],
|
|
162
|
+
strokeColor: "red",
|
|
163
|
+
strokeWidth: 0.006,
|
|
164
|
+
})
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Draw corrected traces (post-shift) in blue dashed
|
|
169
|
+
for (const trace of Object.values(this.correctedTraceMap)) {
|
|
170
|
+
graphics.lines!.push({
|
|
171
|
+
points: trace.tracePath,
|
|
172
|
+
strokeColor: "blue",
|
|
173
|
+
strokeDash: "4 2",
|
|
174
|
+
strokeWidth: 0.004,
|
|
175
|
+
})
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return graphics
|
|
179
|
+
}
|
|
180
|
+
}
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
2
|
+
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
3
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
4
|
+
import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
|
|
5
|
+
import type { ConnectivityMap } from "connectivity-map"
|
|
6
|
+
import {
|
|
7
|
+
TraceOverlapIssueSolver,
|
|
8
|
+
type OverlappingTraceSegmentLocator,
|
|
9
|
+
} from "./TraceOverlapIssueSolver/TraceOverlapIssueSolver"
|
|
10
|
+
import type { MspConnectionPairId } from "../MspConnectionPairSolver/MspConnectionPairSolver"
|
|
11
|
+
|
|
12
|
+
type ConnNetId = string
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* This solver finds traces that overlap (coincident and parallel) and aren't
|
|
16
|
+
* connected via the globalConnMap, then shifts them to avoid the overlap in
|
|
17
|
+
* such a way that minimizes the resulting intersections
|
|
18
|
+
*
|
|
19
|
+
* All traces are orthogonal, so for traces to be considered overlapping, they
|
|
20
|
+
* need to each have a segment where both are horizontal or both are vertical
|
|
21
|
+
* AND the segments must be within 1e-6 of each other in X (if vertical) or
|
|
22
|
+
* Y (if horizontal)
|
|
23
|
+
*
|
|
24
|
+
* Each iteration, we find overlapping traces that aren't part of the same net.
|
|
25
|
+
* This is the same as finding two "trace net islands" that have an overlap.
|
|
26
|
+
*
|
|
27
|
+
* We then consider all the possible ways to shift the overlapping traces to
|
|
28
|
+
* minimize the intersections. If there are multiple trace segments within the
|
|
29
|
+
* same net island they shift together.
|
|
30
|
+
*/
|
|
31
|
+
export class TraceOverlapShiftSolver extends BaseSolver {
|
|
32
|
+
inputProblem: InputProblem
|
|
33
|
+
inputTracePaths: Array<SolvedTracePath>
|
|
34
|
+
globalConnMap: ConnectivityMap
|
|
35
|
+
|
|
36
|
+
declare activeSubSolver: TraceOverlapIssueSolver | null
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A traceNetIsland is a set of traces that are connected via the globalConnMap
|
|
40
|
+
*/
|
|
41
|
+
traceNetIslands: Record<ConnNetId, Array<SolvedTracePath>> = {}
|
|
42
|
+
|
|
43
|
+
correctedTraceMap: Record<MspConnectionPairId, SolvedTracePath> = {}
|
|
44
|
+
|
|
45
|
+
constructor(params: {
|
|
46
|
+
inputProblem: InputProblem
|
|
47
|
+
inputTracePaths: Array<SolvedTracePath>
|
|
48
|
+
globalConnMap: ConnectivityMap
|
|
49
|
+
}) {
|
|
50
|
+
super()
|
|
51
|
+
this.inputProblem = params.inputProblem
|
|
52
|
+
this.inputTracePaths = params.inputTracePaths
|
|
53
|
+
this.globalConnMap = params.globalConnMap
|
|
54
|
+
|
|
55
|
+
for (const tracePath of this.inputTracePaths) {
|
|
56
|
+
const { mspPairId } = tracePath
|
|
57
|
+
this.correctedTraceMap[mspPairId] = tracePath
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
this.traceNetIslands = this.computeTraceNetIslands()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
override getConstructorParams(): ConstructorParameters<
|
|
64
|
+
typeof TraceOverlapShiftSolver
|
|
65
|
+
>[0] {
|
|
66
|
+
return {
|
|
67
|
+
inputProblem: this.inputProblem,
|
|
68
|
+
inputTracePaths: this.inputTracePaths,
|
|
69
|
+
globalConnMap: this.globalConnMap,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
computeTraceNetIslands(): Record<ConnNetId, Array<SolvedTracePath>> {
|
|
74
|
+
// Build islands keyed by global connection net id.
|
|
75
|
+
// Preserve stable order by iterating original inputTracePaths array.
|
|
76
|
+
const islands: Record<ConnNetId, Array<SolvedTracePath>> = {}
|
|
77
|
+
|
|
78
|
+
for (const original of this.inputTracePaths) {
|
|
79
|
+
const path = this.correctedTraceMap[original.mspPairId] ?? original
|
|
80
|
+
const key: ConnNetId = path.globalConnNetId
|
|
81
|
+
if (!islands[key]) islands[key] = []
|
|
82
|
+
islands[key].push(path)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return islands
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
findNextOverlapIssue(): {
|
|
89
|
+
overlappingTraceSegments: Array<OverlappingTraceSegmentLocator>
|
|
90
|
+
} | null {
|
|
91
|
+
// Detect the next set of overlapping segments between two different net islands.
|
|
92
|
+
const EPS = 1e-6
|
|
93
|
+
|
|
94
|
+
const netIds = Object.keys(this.traceNetIslands)
|
|
95
|
+
// Compare each pair of different nets
|
|
96
|
+
for (let i = 0; i < netIds.length; i++) {
|
|
97
|
+
for (let j = i + 1; j < netIds.length; j++) {
|
|
98
|
+
const netA = netIds[i]!
|
|
99
|
+
const netB = netIds[j]!
|
|
100
|
+
const pathsA = this.traceNetIslands[netA] || []
|
|
101
|
+
const pathsB = this.traceNetIslands[netB] || []
|
|
102
|
+
|
|
103
|
+
// Collect overlaps for this pair
|
|
104
|
+
const overlapsA: Array<{
|
|
105
|
+
solvedTracePathIndex: number
|
|
106
|
+
traceSegmentIndex: number
|
|
107
|
+
}> = []
|
|
108
|
+
const overlapsB: Array<{
|
|
109
|
+
solvedTracePathIndex: number
|
|
110
|
+
traceSegmentIndex: number
|
|
111
|
+
}> = []
|
|
112
|
+
|
|
113
|
+
// Track to avoid duplicates
|
|
114
|
+
const seenA = new Set<string>()
|
|
115
|
+
const seenB = new Set<string>()
|
|
116
|
+
|
|
117
|
+
const overlaps1D = (
|
|
118
|
+
a1: number,
|
|
119
|
+
a2: number,
|
|
120
|
+
b1: number,
|
|
121
|
+
b2: number,
|
|
122
|
+
): boolean => {
|
|
123
|
+
const minA = Math.min(a1, a2)
|
|
124
|
+
const maxA = Math.max(a1, a2)
|
|
125
|
+
const minB = Math.min(b1, b2)
|
|
126
|
+
const maxB = Math.max(b1, b2)
|
|
127
|
+
const overlap = Math.min(maxA, maxB) - Math.max(minA, minB)
|
|
128
|
+
return overlap > EPS
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
for (let pa = 0; pa < pathsA.length; pa++) {
|
|
132
|
+
const pathA = pathsA[pa]!
|
|
133
|
+
const ptsA = pathA.tracePath
|
|
134
|
+
for (let sa = 0; sa < ptsA.length - 1; sa++) {
|
|
135
|
+
const a1 = ptsA[sa]!
|
|
136
|
+
const a2 = ptsA[sa + 1]!
|
|
137
|
+
const aVert = Math.abs(a1.x - a2.x) < EPS
|
|
138
|
+
const aHorz = Math.abs(a1.y - a2.y) < EPS
|
|
139
|
+
if (!aVert && !aHorz) continue
|
|
140
|
+
|
|
141
|
+
for (let pb = 0; pb < pathsB.length; pb++) {
|
|
142
|
+
const pathB = pathsB[pb]!
|
|
143
|
+
const ptsB = pathB.tracePath
|
|
144
|
+
for (let sb = 0; sb < ptsB.length - 1; sb++) {
|
|
145
|
+
const b1 = ptsB[sb]!
|
|
146
|
+
const b2 = ptsB[sb + 1]!
|
|
147
|
+
const bVert = Math.abs(b1.x - b2.x) < EPS
|
|
148
|
+
const bHorz = Math.abs(b1.y - b2.y) < EPS
|
|
149
|
+
if (!bVert && !bHorz) continue
|
|
150
|
+
|
|
151
|
+
// Only consider colinear, parallel orientation overlaps
|
|
152
|
+
if (aVert && bVert) {
|
|
153
|
+
if (Math.abs(a1.x - b1.x) < EPS) {
|
|
154
|
+
if (overlaps1D(a1.y, a2.y, b1.y, b2.y)) {
|
|
155
|
+
const keyA = `${pa}:${sa}`
|
|
156
|
+
const keyB = `${pb}:${sb}`
|
|
157
|
+
if (!seenA.has(keyA)) {
|
|
158
|
+
overlapsA.push({
|
|
159
|
+
solvedTracePathIndex: pa,
|
|
160
|
+
traceSegmentIndex: sa,
|
|
161
|
+
})
|
|
162
|
+
seenA.add(keyA)
|
|
163
|
+
}
|
|
164
|
+
if (!seenB.has(keyB)) {
|
|
165
|
+
overlapsB.push({
|
|
166
|
+
solvedTracePathIndex: pb,
|
|
167
|
+
traceSegmentIndex: sb,
|
|
168
|
+
})
|
|
169
|
+
seenB.add(keyB)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
} else if (aHorz && bHorz) {
|
|
174
|
+
if (Math.abs(a1.y - b1.y) < EPS) {
|
|
175
|
+
if (overlaps1D(a1.x, a2.x, b1.x, b2.x)) {
|
|
176
|
+
const keyA = `${pa}:${sa}`
|
|
177
|
+
const keyB = `${pb}:${sb}`
|
|
178
|
+
if (!seenA.has(keyA)) {
|
|
179
|
+
overlapsA.push({
|
|
180
|
+
solvedTracePathIndex: pa,
|
|
181
|
+
traceSegmentIndex: sa,
|
|
182
|
+
})
|
|
183
|
+
seenA.add(keyA)
|
|
184
|
+
}
|
|
185
|
+
if (!seenB.has(keyB)) {
|
|
186
|
+
overlapsB.push({
|
|
187
|
+
solvedTracePathIndex: pb,
|
|
188
|
+
traceSegmentIndex: sb,
|
|
189
|
+
})
|
|
190
|
+
seenB.add(keyB)
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
if (overlapsA.length > 0 && overlapsB.length > 0) {
|
|
201
|
+
return {
|
|
202
|
+
overlappingTraceSegments: [
|
|
203
|
+
{ connNetId: netA, pathsWithOverlap: overlapsA },
|
|
204
|
+
{ connNetId: netB, pathsWithOverlap: overlapsB },
|
|
205
|
+
],
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return null
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
override _step() {
|
|
215
|
+
if (this.activeSubSolver?.solved) {
|
|
216
|
+
for (const [mspPairId, newTrace] of Object.entries(
|
|
217
|
+
this.activeSubSolver.correctedTraceMap,
|
|
218
|
+
)) {
|
|
219
|
+
this.correctedTraceMap[mspPairId] = newTrace
|
|
220
|
+
}
|
|
221
|
+
this.activeSubSolver = null
|
|
222
|
+
this.traceNetIslands = this.computeTraceNetIslands()
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (this.activeSubSolver) {
|
|
226
|
+
this.activeSubSolver.step()
|
|
227
|
+
return
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Find the next overlapping trace segment
|
|
231
|
+
const overlapIssue = this.findNextOverlapIssue()
|
|
232
|
+
|
|
233
|
+
if (overlapIssue === null) {
|
|
234
|
+
this.solved = true
|
|
235
|
+
return
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const { overlappingTraceSegments } = overlapIssue
|
|
239
|
+
|
|
240
|
+
this.activeSubSolver = new TraceOverlapIssueSolver({
|
|
241
|
+
overlappingTraceSegments,
|
|
242
|
+
traceNetIslands: this.traceNetIslands,
|
|
243
|
+
})
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
override visualize() {
|
|
247
|
+
if (this.activeSubSolver) {
|
|
248
|
+
return this.activeSubSolver.visualize()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const graphics = visualizeInputProblem(this.inputProblem)
|
|
252
|
+
|
|
253
|
+
// Draw current corrected traces
|
|
254
|
+
for (const trace of Object.values(this.correctedTraceMap)) {
|
|
255
|
+
graphics.lines!.push({
|
|
256
|
+
points: trace.tracePath,
|
|
257
|
+
strokeColor: "purple",
|
|
258
|
+
strokeWidth: 0.005,
|
|
259
|
+
})
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return graphics
|
|
263
|
+
}
|
|
264
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
|
|
2
|
+
import type { FacingDirection } from "lib/utils/dir"
|
|
3
|
+
|
|
4
|
+
export type ChipId = string
|
|
5
|
+
export type PinId = string
|
|
6
|
+
export type NetId = string
|
|
7
|
+
|
|
8
|
+
export interface InputPin {
|
|
9
|
+
pinId: PinId
|
|
10
|
+
x: number
|
|
11
|
+
y: number
|
|
12
|
+
|
|
13
|
+
_facingDirection?: "x+" | "x-" | "y+" | "y-"
|
|
14
|
+
}
|
|
15
|
+
export interface InputChip {
|
|
16
|
+
chipId: ChipId
|
|
17
|
+
center: { x: number; y: number }
|
|
18
|
+
width: number
|
|
19
|
+
height: number
|
|
20
|
+
pins: Array<InputPin>
|
|
21
|
+
}
|
|
22
|
+
export interface InputDirectConnection {
|
|
23
|
+
pinIds: [PinId, PinId]
|
|
24
|
+
netId?: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface InputNetConnection {
|
|
28
|
+
netId: string
|
|
29
|
+
pinIds: Array<PinId>
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface InputProblem {
|
|
33
|
+
chips: Array<InputChip>
|
|
34
|
+
directConnections: Array<InputDirectConnection>
|
|
35
|
+
netConnections: Array<InputNetConnection>
|
|
36
|
+
|
|
37
|
+
availableNetLabelOrientations: Record<NetId, FacingDirection[]>
|
|
38
|
+
|
|
39
|
+
_chipObstacleSpatialIndex?: ChipObstacleSpatialIndex
|
|
40
|
+
}
|
package/lib/utils/dir.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type FacingDirection = "x+" | "x-" | "y+" | "y-"
|
|
2
|
+
export const dir = (
|
|
3
|
+
facingDirection: FacingDirection,
|
|
4
|
+
): { x: number; y: number } => {
|
|
5
|
+
switch (facingDirection) {
|
|
6
|
+
case "x+":
|
|
7
|
+
return { x: 1, y: 0 }
|
|
8
|
+
case "x-":
|
|
9
|
+
return { x: -1, y: 0 }
|
|
10
|
+
case "y+":
|
|
11
|
+
return { x: 0, y: 1 }
|
|
12
|
+
case "y-":
|
|
13
|
+
return { x: 0, y: -1 }
|
|
14
|
+
}
|
|
15
|
+
throw new Error(`Invalid facing direction: ${facingDirection}`)
|
|
16
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lazily generates **all permutations** (orderings) of the given array.
|
|
3
|
+
*
|
|
4
|
+
* This is a generator: it yields one permutation at a time without allocating
|
|
5
|
+
* the full result set up front, which is helpful because there are `n!`
|
|
6
|
+
* permutations for `n` items.
|
|
7
|
+
*
|
|
8
|
+
* - The input array is **not mutated**.
|
|
9
|
+
* - If `items` contains duplicates, duplicate permutations will be yielded.
|
|
10
|
+
* - For an empty array, it yields a single empty permutation `[]`.
|
|
11
|
+
*
|
|
12
|
+
* @typeParam T - Element type.
|
|
13
|
+
* @param items - The array whose permutations to generate.
|
|
14
|
+
* @yields A new array representing one permutation of `items`.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* for (const perm of getAllPossibleOrderingsGenerator([1, 2, 3])) {
|
|
18
|
+
* console.log(perm);
|
|
19
|
+
* // -> [1, 2, 3]
|
|
20
|
+
* // -> [1, 3, 2]
|
|
21
|
+
* // -> [2, 1, 3]
|
|
22
|
+
* // -> [2, 3, 1]
|
|
23
|
+
* // -> [3, 1, 2]
|
|
24
|
+
* // -> [3, 2, 1]
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* @remarks
|
|
28
|
+
* Complexity:
|
|
29
|
+
* - Time: Θ(n!) permutations, each of length n.
|
|
30
|
+
* - Additional space: O(n) for recursion + the arrays yielded.
|
|
31
|
+
*/
|
|
32
|
+
export function* getAllPossibleOrderingsGenerator<T>(
|
|
33
|
+
items: T[],
|
|
34
|
+
): Generator<T[], void, void> {
|
|
35
|
+
if (items.length === 0) {
|
|
36
|
+
yield []
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
for (let i = 0; i < items.length; i++) {
|
|
41
|
+
const head = items[i]
|
|
42
|
+
const rest = items.slice(0, i).concat(items.slice(i + 1))
|
|
43
|
+
for (const tail of getAllPossibleOrderingsGenerator(rest)) {
|
|
44
|
+
yield [head, ...tail]
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tscircuit/schematic-trace-solver",
|
|
3
|
+
"main": "dist/index.js",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "cosmos",
|
|
8
|
+
"build": "tsup lib/index.ts --format esm --dts",
|
|
9
|
+
"format": "biome format --write ."
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@biomejs/biome": "^2.2.2",
|
|
13
|
+
"@tscircuit/math-utils": "^0.0.19",
|
|
14
|
+
"@types/bun": "latest",
|
|
15
|
+
"calculate-elbow": "^0.0.10",
|
|
16
|
+
"connectivity-map": "^1.0.0",
|
|
17
|
+
"flatbush": "^4.5.0",
|
|
18
|
+
"graphics-debug": "^0.0.62",
|
|
19
|
+
"react-cosmos": "^7.0.0",
|
|
20
|
+
"react-cosmos-plugin-vite": "^7.0.0",
|
|
21
|
+
"tsup": "^8.5.0",
|
|
22
|
+
"vite": "^7.1.3",
|
|
23
|
+
"@react-hook/resize-observer": "^2.0.2"
|
|
24
|
+
},
|
|
25
|
+
"peerDependencies": {
|
|
26
|
+
"typescript": "^5"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {}
|
|
29
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
2
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
3
|
+
import { useMemo, useReducer } from "react"
|
|
4
|
+
import { InteractiveGraphics } from "graphics-debug/react"
|
|
5
|
+
import { SolverToolbar } from "./SolverToolbar"
|
|
6
|
+
import { PipelineStageTable } from "./PipelineStageTable"
|
|
7
|
+
|
|
8
|
+
export const PipelineDebugger = ({
|
|
9
|
+
inputProblem,
|
|
10
|
+
}: {
|
|
11
|
+
inputProblem: InputProblem
|
|
12
|
+
}) => {
|
|
13
|
+
const [, incRenderCount] = useReducer((x) => x + 1, 0)
|
|
14
|
+
const solver = useMemo(
|
|
15
|
+
() => new SchematicTracePipelineSolver(inputProblem),
|
|
16
|
+
[],
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div>
|
|
21
|
+
<SolverToolbar triggerRender={() => incRenderCount()} solver={solver} />
|
|
22
|
+
<InteractiveGraphics graphics={solver.visualize()} />
|
|
23
|
+
<PipelineStageTable
|
|
24
|
+
triggerRender={() => incRenderCount()}
|
|
25
|
+
pipelineSolver={solver}
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
)
|
|
29
|
+
}
|