@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,191 @@
1
+ import { getBounds, type GraphicsObject } from "graphics-debug"
2
+ import { ChipObstacleSpatialIndex } from "lib/data-structures/ChipObstacleSpatialIndex"
3
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
4
+ import type { Guideline } from "lib/solvers/GuidelinesSolver/GuidelinesSolver"
5
+ import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
6
+ import { visualizeInputProblem } from "lib/solvers/SchematicTracePipelineSolver/visualizeInputProblem"
7
+ import type { InputChip, InputProblem } from "lib/types/InputProblem"
8
+ import { calculateElbow } from "calculate-elbow"
9
+ import { getPinDirection } from "./getPinDirection"
10
+ import {
11
+ generateElbowVariants,
12
+ type MovableSegment,
13
+ } from "./generateElbowVariants"
14
+ import type { Point } from "@tscircuit/math-utils"
15
+ import { visualizeGuidelines } from "lib/solvers/GuidelinesSolver/visualizeGuidelines"
16
+ import { getColorFromString } from "lib/utils/getColorFromString"
17
+
18
+ export class SchematicTraceSingleLineSolver extends BaseSolver {
19
+ pins: MspConnectionPair["pins"]
20
+ inputProblem: InputProblem
21
+ guidelines: Guideline[]
22
+ chipMap: Record<string, InputChip>
23
+ movableSegments: Array<MovableSegment>
24
+ baseElbow: Point[]
25
+
26
+ queuedCandidatePaths: Array<Point[]>
27
+
28
+ chipObstacleSpatialIndex: ChipObstacleSpatialIndex
29
+
30
+ solvedTracePath: { x: number; y: number }[] | null = null
31
+
32
+ constructor(params: {
33
+ pins: MspConnectionPair["pins"]
34
+ guidelines: Guideline[]
35
+ inputProblem: InputProblem
36
+ chipMap: Record<string, InputChip>
37
+ }) {
38
+ super()
39
+ this.pins = params.pins
40
+ this.inputProblem = params.inputProblem
41
+ this.guidelines = params.guidelines
42
+ this.chipMap = params.chipMap
43
+ this.chipObstacleSpatialIndex =
44
+ this.inputProblem._chipObstacleSpatialIndex ||
45
+ new ChipObstacleSpatialIndex(this.inputProblem.chips)
46
+
47
+ if (!this.inputProblem._chipObstacleSpatialIndex) {
48
+ this.inputProblem._chipObstacleSpatialIndex =
49
+ this.chipObstacleSpatialIndex
50
+ }
51
+
52
+ for (const pin of this.pins) {
53
+ if (!pin._facingDirection) {
54
+ const chip = this.chipMap[pin.chipId]
55
+ pin._facingDirection = getPinDirection(pin, chip)
56
+ }
57
+ }
58
+
59
+ const [pin1, pin2] = this.pins
60
+ this.baseElbow = calculateElbow(
61
+ {
62
+ x: pin1.x,
63
+ y: pin1.y,
64
+ facingDirection: pin1._facingDirection,
65
+ },
66
+ {
67
+ x: pin2.x,
68
+ y: pin2.y,
69
+ facingDirection: pin2._facingDirection,
70
+ },
71
+ {
72
+ overshoot: 0.2,
73
+ },
74
+ )
75
+
76
+ const { elbowVariants, movableSegments } = generateElbowVariants({
77
+ baseElbow: this.baseElbow,
78
+ guidelines: this.guidelines,
79
+ })
80
+
81
+ this.movableSegments = movableSegments
82
+
83
+ this.queuedCandidatePaths = [this.baseElbow, ...elbowVariants]
84
+ }
85
+
86
+ override getConstructorParams(): ConstructorParameters<
87
+ typeof SchematicTraceSingleLineSolver
88
+ >[0] {
89
+ return {
90
+ chipMap: this.chipMap,
91
+ pins: this.pins,
92
+ guidelines: this.guidelines,
93
+ inputProblem: this.inputProblem,
94
+ }
95
+ }
96
+
97
+ override _step() {
98
+ if (this.queuedCandidatePaths.length === 0) {
99
+ this.failed = true
100
+ this.error = "No more candidate elbows, everything had collisions"
101
+ return
102
+ }
103
+
104
+ const nextCandidatePath = this.queuedCandidatePaths.shift()!
105
+
106
+ const obstacleOps = {
107
+ excludeChipIds: [this.pins[0].chipId, this.pins[1].chipId],
108
+ }
109
+ for (let i = 0; i < nextCandidatePath.length - 1; i++) {
110
+ const start = nextCandidatePath[i]
111
+ const end = nextCandidatePath[i + 1]
112
+ const intersects =
113
+ this.chipObstacleSpatialIndex.doesOrthogonalLineIntersectChip(
114
+ [start, end],
115
+ obstacleOps,
116
+ )
117
+ if (intersects) return
118
+ }
119
+
120
+ this.solvedTracePath = nextCandidatePath
121
+ this.solved = true
122
+ }
123
+
124
+ override visualize(): GraphicsObject {
125
+ const graphics = visualizeInputProblem(this.inputProblem, {
126
+ chipAlpha: 0.1,
127
+ connectionAlpha: 0.1,
128
+ })
129
+
130
+ const bounds = getBounds(graphics)
131
+ const boundsWidth = bounds.maxX - bounds.minX
132
+ const boundsHeight = bounds.maxY - bounds.minY
133
+ visualizeGuidelines({ guidelines: this.guidelines, graphics })
134
+
135
+ // Visualize movable segments
136
+ for (const { start, end, dir } of this.movableSegments) {
137
+ const mid = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 }
138
+ const dist = Math.sqrt((start.x - end.x) ** 2 + (start.y - end.y) ** 2)
139
+ graphics.lines!.push({
140
+ points: [start, mid, end],
141
+ strokeColor: "rgba(0,0,255,0.5)",
142
+ strokeDash: "2 2",
143
+ })
144
+ graphics.lines!.push({
145
+ points: [
146
+ mid,
147
+ { x: mid.x + dir.x * dist * 0.1, y: mid.y + dir.y * dist * 0.1 },
148
+ ],
149
+ strokeColor: "rgba(0,0,255,0.5)",
150
+ strokeDash: "2 2",
151
+ })
152
+ }
153
+
154
+ // Draw the next candidate path in orange
155
+ if (!this.solvedTracePath) {
156
+ if (this.queuedCandidatePaths.length > 0) {
157
+ graphics.lines!.push({
158
+ points: this.queuedCandidatePaths[0],
159
+ strokeColor: "orange",
160
+ strokeWidth: boundsWidth * 0.005,
161
+ })
162
+ }
163
+
164
+ // Visualize all the other queued candidates in faded yellow
165
+ for (let i = 1; i < this.queuedCandidatePaths.length; i++) {
166
+ const candidatePath = this.queuedCandidatePaths[i]
167
+ const pi = i / this.queuedCandidatePaths.length
168
+ graphics.lines!.push({
169
+ points: candidatePath.map((p) => ({
170
+ x: p.x + pi * boundsWidth * 0.005,
171
+ y: p.y + pi * boundsHeight * 0.005,
172
+ })),
173
+ strokeColor: getColorFromString(
174
+ `${candidatePath.reduce((acc, p) => `${acc},${p.x},${p.y}`, "")}`,
175
+ 0.5,
176
+ ),
177
+ strokeDash: "8 8",
178
+ })
179
+ }
180
+ }
181
+
182
+ if (this.solvedTracePath) {
183
+ graphics.lines!.push({
184
+ points: this.solvedTracePath,
185
+ strokeColor: "green",
186
+ })
187
+ }
188
+
189
+ return graphics
190
+ }
191
+ }
@@ -0,0 +1,132 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { Guideline } from "lib/solvers/GuidelinesSolver/GuidelinesSolver"
3
+ import { dir, type FacingDirection } from "lib/utils/dir"
4
+
5
+ export interface MovableSegment {
6
+ start: Point
7
+ end: Point
8
+ freedom: "x+" | "x-" | "y+" | "y-"
9
+ dir: { x: number; y: number }
10
+ }
11
+
12
+ export const generateElbowVariants = ({
13
+ baseElbow,
14
+ guidelines,
15
+ }: {
16
+ baseElbow: Point[]
17
+ guidelines: Guideline[]
18
+ }): {
19
+ elbowVariants: Array<Point[]>
20
+ movableSegments: Array<MovableSegment>
21
+ } => {
22
+ // First we find the movable segments, movable segments are the any segments
23
+ // of the baseElbow that are not the first or last segment
24
+ const movableSegments: Array<MovableSegment> = []
25
+ for (let i = 1; i < baseElbow.length - 2; i++) {
26
+ const prev = baseElbow[i - 1]
27
+ const start = baseElbow[i]
28
+ const end = baseElbow[i + 1]
29
+
30
+ const isHorz = Math.abs(start.y - end.y) < 1e-6
31
+
32
+ let freedom: FacingDirection
33
+ if (isHorz) {
34
+ freedom = prev.y <= start.y ? "y+" : "y-"
35
+ } else {
36
+ freedom = prev.x <= start.x ? "x+" : "x-"
37
+ }
38
+
39
+ movableSegments.push({ start, end, freedom, dir: dir(freedom) })
40
+ }
41
+
42
+ // Find relevant guidelines for each movable segment
43
+ const segmentGuidelineOptions: Array<Array<number>> = []
44
+
45
+ for (const segment of movableSegments) {
46
+ const relevantPositions: number[] = []
47
+
48
+ if (segment.freedom === "x+" || segment.freedom === "x-") {
49
+ // Segment can move horizontally, find vertical guidelines
50
+ for (const guideline of guidelines) {
51
+ if (guideline.orientation === "vertical" && guideline.x !== undefined) {
52
+ relevantPositions.push(guideline.x)
53
+ }
54
+ }
55
+ // Add current position as an option (no movement)
56
+ relevantPositions.push(segment.start.x)
57
+ } else {
58
+ // Segment can move vertically, find horizontal guidelines
59
+ for (const guideline of guidelines) {
60
+ if (
61
+ guideline.orientation === "horizontal" &&
62
+ guideline.y !== undefined
63
+ ) {
64
+ relevantPositions.push(guideline.y)
65
+ }
66
+ }
67
+ // Add current position as an option (no movement)
68
+ relevantPositions.push(segment.start.y)
69
+ }
70
+
71
+ segmentGuidelineOptions.push(
72
+ [...new Set(relevantPositions)].sort((a, b) => a - b),
73
+ )
74
+ }
75
+
76
+ // Generate all combinations of segment positions
77
+ const generateCombinations = (
78
+ options: Array<Array<number>>,
79
+ ): Array<Array<number>> => {
80
+ if (options.length === 0) return [[]]
81
+ if (options.length === 1) return options[0].map((pos) => [pos])
82
+
83
+ const combinations: Array<Array<number>> = []
84
+ const firstOptions = options[0]
85
+ const restCombinations = generateCombinations(options.slice(1))
86
+
87
+ for (const firstOption of firstOptions) {
88
+ for (const restCombination of restCombinations) {
89
+ combinations.push([firstOption, ...restCombination])
90
+ }
91
+ }
92
+
93
+ return combinations
94
+ }
95
+
96
+ const positionCombinations = generateCombinations(segmentGuidelineOptions)
97
+
98
+ // Create elbow variants by applying each combination
99
+ const elbowVariants: Array<Point[]> = []
100
+
101
+ for (const combination of positionCombinations) {
102
+ const variant = [...baseElbow]
103
+
104
+ // Apply each segment movement
105
+ for (
106
+ let segmentIndex = 0;
107
+ segmentIndex < movableSegments.length;
108
+ segmentIndex++
109
+ ) {
110
+ const segment = movableSegments[segmentIndex]
111
+ const newPosition = combination[segmentIndex]
112
+ const elbowIndex = segmentIndex + 1 // movable segments start at index 1
113
+
114
+ if (segment.freedom === "x+" || segment.freedom === "x-") {
115
+ // Move horizontally
116
+ variant[elbowIndex] = { ...variant[elbowIndex], x: newPosition }
117
+ variant[elbowIndex + 1] = { ...variant[elbowIndex + 1], x: newPosition }
118
+ } else {
119
+ // Move vertically
120
+ variant[elbowIndex] = { ...variant[elbowIndex], y: newPosition }
121
+ variant[elbowIndex + 1] = { ...variant[elbowIndex + 1], y: newPosition }
122
+ }
123
+ }
124
+
125
+ elbowVariants.push(variant)
126
+ }
127
+
128
+ return {
129
+ elbowVariants,
130
+ movableSegments,
131
+ }
132
+ }
@@ -0,0 +1,42 @@
1
+ import type { InputChip, InputPin } from "lib/types/InputProblem"
2
+
3
+ export const getPinDirection = (
4
+ pin: InputPin,
5
+ chip: InputChip,
6
+ ): "x+" | "x-" | "y+" | "y-" => {
7
+ // Determine what edge the pin lies on
8
+ const { x, y } = pin
9
+ const { center, width, height } = chip
10
+
11
+ const yPlusEdge = center.y + height / 2
12
+ const yMinusEdge = center.y - height / 2
13
+ const xPlusEdge = center.x + width / 2
14
+ const xMinusEdge = center.x - width / 2
15
+
16
+ // Which edge is the pin closest to?
17
+ const yPlusDistance = Math.abs(y - yPlusEdge)
18
+ const yMinusDistance = Math.abs(y - yMinusEdge)
19
+ const xPlusDistance = Math.abs(x - xPlusEdge)
20
+ const xMinusDistance = Math.abs(x - xMinusEdge)
21
+
22
+ const minDistance = Math.min(
23
+ yPlusDistance,
24
+ yMinusDistance,
25
+ xPlusDistance,
26
+ xMinusDistance,
27
+ )
28
+
29
+ if (minDistance === yPlusDistance) {
30
+ return "y+"
31
+ }
32
+
33
+ if (minDistance === yMinusDistance) {
34
+ return "y-"
35
+ }
36
+
37
+ if (minDistance === xPlusDistance) {
38
+ return "x+"
39
+ }
40
+
41
+ return "x-"
42
+ }
@@ -0,0 +1,244 @@
1
+ /**
2
+ * Pipeline solver that runs a series of solvers to find the best schematic layout.
3
+ * Coordinates the entire layout process from chip partitioning through final packing.
4
+ */
5
+
6
+ import type { GraphicsObject } from "graphics-debug"
7
+ import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
8
+ import type { InputProblem } from "lib/types/InputProblem"
9
+ import { MspConnectionPairSolver } from "../MspConnectionPairSolver/MspConnectionPairSolver"
10
+ import { SchematicTraceLinesSolver } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
11
+ import { TraceOverlapShiftSolver } from "../TraceOverlapShiftSolver/TraceOverlapShiftSolver"
12
+ import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver"
13
+ import { visualizeInputProblem } from "./visualizeInputProblem"
14
+ import { GuidelinesSolver } from "../GuidelinesSolver/GuidelinesSolver"
15
+
16
+ type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
17
+ solverName: string
18
+ solverClass: T
19
+ getConstructorParams: (
20
+ instance: SchematicTracePipelineSolver,
21
+ ) => ConstructorParameters<T>
22
+ onSolved?: (instance: SchematicTracePipelineSolver) => void
23
+ }
24
+
25
+ function definePipelineStep<
26
+ T extends new (
27
+ ...args: any[]
28
+ ) => BaseSolver,
29
+ const P extends ConstructorParameters<T>,
30
+ >(
31
+ solverName: keyof SchematicTracePipelineSolver,
32
+ solverClass: T,
33
+ getConstructorParams: (instance: SchematicTracePipelineSolver) => P,
34
+ opts: {
35
+ onSolved?: (instance: SchematicTracePipelineSolver) => void
36
+ } = {},
37
+ ): PipelineStep<T> {
38
+ return {
39
+ solverName,
40
+ solverClass,
41
+ getConstructorParams,
42
+ onSolved: opts.onSolved,
43
+ }
44
+ }
45
+
46
+ export class SchematicTracePipelineSolver extends BaseSolver {
47
+ mspConnectionPairSolver?: MspConnectionPairSolver
48
+ guidelinesSolver?: GuidelinesSolver
49
+ schematicTraceLinesSolver?: SchematicTraceLinesSolver
50
+ traceOverlapShiftSolver?: TraceOverlapShiftSolver
51
+ netLabelPlacementSolver?: NetLabelPlacementSolver
52
+
53
+ startTimeOfPhase: Record<string, number>
54
+ endTimeOfPhase: Record<string, number>
55
+ timeSpentOnPhase: Record<string, number>
56
+ firstIterationOfPhase: Record<string, number>
57
+
58
+ inputProblem: InputProblem
59
+
60
+ pipelineDef = [
61
+ definePipelineStep(
62
+ "mspConnectionPairSolver",
63
+ MspConnectionPairSolver,
64
+ () => [{ inputProblem: this.inputProblem }],
65
+ {
66
+ onSolved: (mspSolver) => {},
67
+ },
68
+ ),
69
+ definePipelineStep(
70
+ "guidelinesSolver",
71
+ GuidelinesSolver,
72
+ () => [
73
+ {
74
+ inputProblem: this.inputProblem,
75
+ },
76
+ ],
77
+ {
78
+ onSolved: (guidelinesSolver) => {},
79
+ },
80
+ ),
81
+ definePipelineStep(
82
+ "schematicTraceLinesSolver",
83
+ SchematicTraceLinesSolver,
84
+ () => [
85
+ {
86
+ mspConnectionPairs: this.mspConnectionPairSolver!.mspConnectionPairs,
87
+ dcConnMap: this.mspConnectionPairSolver!.dcConnMap,
88
+ globalConnMap: this.mspConnectionPairSolver!.globalConnMap,
89
+ inputProblem: this.inputProblem,
90
+ guidelines: this.guidelinesSolver!.guidelines,
91
+ chipMap: this.mspConnectionPairSolver!.chipMap,
92
+ },
93
+ ],
94
+ {
95
+ onSolved: (schematicTraceLinesSolver) => {},
96
+ },
97
+ ),
98
+ definePipelineStep(
99
+ "traceOverlapShiftSolver",
100
+ TraceOverlapShiftSolver,
101
+ () => [
102
+ {
103
+ inputProblem: this.inputProblem,
104
+ inputTracePaths: this.schematicTraceLinesSolver!.solvedTracePaths,
105
+ globalConnMap: this.mspConnectionPairSolver!.globalConnMap,
106
+ },
107
+ ],
108
+ {
109
+ onSolved: (_solver) => {},
110
+ },
111
+ ),
112
+ definePipelineStep(
113
+ "netLabelPlacementSolver",
114
+ NetLabelPlacementSolver,
115
+ () => [
116
+ {
117
+ inputProblem: this.inputProblem,
118
+ inputTraceMap:
119
+ this.traceOverlapShiftSolver?.correctedTraceMap ??
120
+ Object.fromEntries(
121
+ this.schematicTraceLinesSolver!.solvedTracePaths.map((p) => [
122
+ p.mspPairId,
123
+ p,
124
+ ]),
125
+ ),
126
+ },
127
+ ],
128
+ {
129
+ onSolved: (_solver) => {
130
+ // TODO
131
+ },
132
+ },
133
+ ),
134
+ ]
135
+
136
+ constructor(inputProblem: InputProblem) {
137
+ super()
138
+ this.inputProblem = inputProblem
139
+ this.MAX_ITERATIONS = 1e6
140
+ this.startTimeOfPhase = {}
141
+ this.endTimeOfPhase = {}
142
+ this.timeSpentOnPhase = {}
143
+ this.firstIterationOfPhase = {}
144
+ }
145
+
146
+ currentPipelineStepIndex = 0
147
+
148
+ override _step() {
149
+ const pipelineStepDef = this.pipelineDef[this.currentPipelineStepIndex]
150
+ if (!pipelineStepDef) {
151
+ this.solved = true
152
+ return
153
+ }
154
+
155
+ if (this.activeSubSolver) {
156
+ this.activeSubSolver.step()
157
+ if (this.activeSubSolver.solved) {
158
+ this.endTimeOfPhase[pipelineStepDef.solverName] = performance.now()
159
+ this.timeSpentOnPhase[pipelineStepDef.solverName] =
160
+ this.endTimeOfPhase[pipelineStepDef.solverName]! -
161
+ this.startTimeOfPhase[pipelineStepDef.solverName]!
162
+ pipelineStepDef.onSolved?.(this)
163
+ this.activeSubSolver = null
164
+ this.currentPipelineStepIndex++
165
+ } else if (this.activeSubSolver.failed) {
166
+ this.error = this.activeSubSolver?.error
167
+ this.failed = true
168
+ this.activeSubSolver = null
169
+ }
170
+ return
171
+ }
172
+
173
+ const constructorParams = pipelineStepDef.getConstructorParams(this)
174
+ // @ts-ignore
175
+ this.activeSubSolver = new pipelineStepDef.solverClass(...constructorParams)
176
+ ;(this as any)[pipelineStepDef.solverName] = this.activeSubSolver
177
+ this.timeSpentOnPhase[pipelineStepDef.solverName] = 0
178
+ this.startTimeOfPhase[pipelineStepDef.solverName] = performance.now()
179
+ this.firstIterationOfPhase[pipelineStepDef.solverName] = this.iterations
180
+ }
181
+
182
+ solveUntilPhase(phase: string) {
183
+ while (this.getCurrentPhase().toLowerCase() !== phase.toLowerCase()) {
184
+ this.step()
185
+ }
186
+ }
187
+
188
+ getCurrentPhase(): string {
189
+ return this.pipelineDef[this.currentPipelineStepIndex]?.solverName ?? "none"
190
+ }
191
+
192
+ override visualize(): GraphicsObject {
193
+ if (!this.solved && this.activeSubSolver)
194
+ return this.activeSubSolver.visualize()
195
+
196
+ const visualizations = [
197
+ visualizeInputProblem(this.inputProblem),
198
+ ...(this.pipelineDef
199
+ .map((p) => (this as any)[p.solverName]?.visualize())
200
+ .filter(Boolean)
201
+ .map((viz, stepIndex) => {
202
+ for (const rect of viz!.rects ?? []) {
203
+ rect.step = stepIndex
204
+ }
205
+ for (const point of viz!.points ?? []) {
206
+ point.step = stepIndex
207
+ }
208
+ for (const circle of viz!.circles ?? []) {
209
+ circle.step = stepIndex
210
+ }
211
+ for (const text of viz!.texts ?? []) {
212
+ text.step = stepIndex
213
+ }
214
+ for (const line of viz!.lines ?? []) {
215
+ line.step = stepIndex
216
+ }
217
+ return viz
218
+ }) as GraphicsObject[]),
219
+ ]
220
+
221
+ if (visualizations.length === 1) return visualizations[0]!
222
+
223
+ // Simple combination of visualizations
224
+ return {
225
+ points: visualizations.flatMap((v) => v.points || []),
226
+ rects: visualizations.flatMap((v) => v.rects || []),
227
+ lines: visualizations.flatMap((v) => v.lines || []),
228
+ circles: visualizations.flatMap((v) => v.circles || []),
229
+ texts: visualizations.flatMap((v) => v.texts || []),
230
+ }
231
+ }
232
+
233
+ /**
234
+ * A lightweight version of the visualize method that can be used to stream
235
+ * progress
236
+ */
237
+ override preview(): GraphicsObject {
238
+ if (this.activeSubSolver) {
239
+ return this.activeSubSolver.preview()
240
+ }
241
+
242
+ return super.preview()
243
+ }
244
+ }
@@ -0,0 +1,89 @@
1
+ import type { GraphicsObject } from "graphics-debug"
2
+ import type { PinId, InputPin, InputProblem } from "lib/types/InputProblem"
3
+ import { getColorFromString } from "lib/utils/getColorFromString"
4
+
5
+ export const visualizeInputProblem = (
6
+ inputProblem: InputProblem,
7
+ opts: {
8
+ chipAlpha?: number
9
+ connectionAlpha?: number
10
+ } = {},
11
+ ): GraphicsObject => {
12
+ const { connectionAlpha = 0.8, chipAlpha = 0.8 } = opts
13
+ const graphics: Pick<
14
+ Required<GraphicsObject>,
15
+ "lines" | "points" | "rects"
16
+ > = {
17
+ lines: [],
18
+ points: [],
19
+ rects: [],
20
+ }
21
+
22
+ const pinIdMap = new Map<PinId, InputPin>()
23
+ for (const chip of inputProblem.chips) {
24
+ for (const pin of chip.pins) {
25
+ pinIdMap.set(pin.pinId, pin)
26
+ }
27
+ }
28
+
29
+ for (const chip of inputProblem.chips) {
30
+ graphics.rects.push({
31
+ label: chip.chipId,
32
+ center: chip.center,
33
+ width: chip.width,
34
+ height: chip.height,
35
+ fill: getColorFromString(chip.chipId, chipAlpha),
36
+ })
37
+
38
+ for (const pin of chip.pins) {
39
+ graphics.points.push({
40
+ label: pin.pinId,
41
+ x: pin.x,
42
+ y: pin.y,
43
+ color: getColorFromString(pin.pinId, 0.8),
44
+ })
45
+ }
46
+ }
47
+
48
+ for (const directConn of inputProblem.directConnections) {
49
+ const [pinId1, pinId2] = directConn.pinIds
50
+ const pin1 = pinIdMap.get(pinId1)!
51
+ const pin2 = pinIdMap.get(pinId2)!
52
+ graphics.lines.push({
53
+ points: [
54
+ {
55
+ x: pin1.x,
56
+ y: pin1.y,
57
+ },
58
+ {
59
+ x: pin2.x,
60
+ y: pin2.y,
61
+ },
62
+ ],
63
+ strokeColor: getColorFromString(
64
+ directConn.netId ?? `${pinId1}-${pinId2}`,
65
+ connectionAlpha,
66
+ ),
67
+ })
68
+ }
69
+
70
+ for (const netConn of inputProblem.netConnections) {
71
+ const pins = netConn.pinIds.map((pinId) => pinIdMap.get(pinId)!)
72
+ for (let i = 0; i < pins.length - 1; i++) {
73
+ for (let j = i + 1; j < pins.length; j++) {
74
+ const pin1 = pins[i]!
75
+ const pin2 = pins[j]!
76
+ graphics.lines.push({
77
+ points: [
78
+ { x: pin1.x, y: pin1.y },
79
+ { x: pin2.x, y: pin2.y },
80
+ ],
81
+ strokeColor: getColorFromString(netConn.netId, connectionAlpha),
82
+ strokeDash: "4 2",
83
+ })
84
+ }
85
+ }
86
+ }
87
+
88
+ return graphics
89
+ }