@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,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
+ }
@@ -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
+ }
@@ -0,0 +1,7 @@
1
+ export const getColorFromString = (string: string, alpha = 1) => {
2
+ // pseudo random number from string
3
+ const hash = string.split("").reduce((acc, char) => {
4
+ return acc * 31 + char.charCodeAt(0)
5
+ }, 0)
6
+ return `hsl(${hash % 360}, 100%, 50%, ${alpha})`
7
+ }
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
+ }