@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,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
|
+
}
|
package/lib/solvers/SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection.ts
ADDED
|
@@ -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
|
+
}
|