@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/index.html
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Schematic Trace Solver</title>
|
|
7
|
+
<script>
|
|
8
|
+
const script = document.createElement("script")
|
|
9
|
+
script.src = "https://cdn.tailwindcss.com"
|
|
10
|
+
document.head.appendChild(script)
|
|
11
|
+
</script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<div id="root"></div>
|
|
15
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { InputChip } from "lib/types/InputProblem"
|
|
2
|
+
import type { Bounds, Point } from "@tscircuit/math-utils"
|
|
3
|
+
import Flatbush from "flatbush"
|
|
4
|
+
import { getInputChipBounds } from "lib/solvers/GuidelinesSolver/getInputChipBounds"
|
|
5
|
+
|
|
6
|
+
export interface SpatiallyIndexedChip extends InputChip {
|
|
7
|
+
bounds: Bounds
|
|
8
|
+
spatialIndexId: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export class ChipObstacleSpatialIndex {
|
|
12
|
+
chips: Array<SpatiallyIndexedChip>
|
|
13
|
+
spatialIndex: Flatbush
|
|
14
|
+
spatialIndexIdToChip: Map<number, SpatiallyIndexedChip>
|
|
15
|
+
|
|
16
|
+
constructor(chips: InputChip[]) {
|
|
17
|
+
this.chips = chips.map((chip) => ({
|
|
18
|
+
...chip,
|
|
19
|
+
bounds: getInputChipBounds(chip),
|
|
20
|
+
spatialIndexId: null as any,
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
this.spatialIndexIdToChip = new Map()
|
|
24
|
+
this.spatialIndex = new Flatbush(chips.length)
|
|
25
|
+
|
|
26
|
+
for (const chip of this.chips) {
|
|
27
|
+
chip.spatialIndexId = this.spatialIndex.add(
|
|
28
|
+
chip.bounds.minX,
|
|
29
|
+
chip.bounds.minY,
|
|
30
|
+
chip.bounds.maxX,
|
|
31
|
+
chip.bounds.maxY,
|
|
32
|
+
)
|
|
33
|
+
this.spatialIndexIdToChip.set(chip.spatialIndexId, chip)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
this.spatialIndex.finish()
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
getChipsInBounds(bounds: Bounds): Array<InputChip & { bounds: Bounds }> {
|
|
40
|
+
const chipSpatialIndexIds = this.spatialIndex.search(
|
|
41
|
+
bounds.minX,
|
|
42
|
+
bounds.minY,
|
|
43
|
+
bounds.maxX,
|
|
44
|
+
bounds.maxY,
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
return chipSpatialIndexIds.map((id) => this.spatialIndexIdToChip.get(id)!)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
doesOrthogonalLineIntersectChip(
|
|
51
|
+
line: [Point, Point],
|
|
52
|
+
opts: {
|
|
53
|
+
excludeChipIds?: string[]
|
|
54
|
+
} = {},
|
|
55
|
+
): boolean {
|
|
56
|
+
const excludeChipIds = opts.excludeChipIds ?? []
|
|
57
|
+
const [p1, p2] = line
|
|
58
|
+
const { x: x1, y: y1 } = p1
|
|
59
|
+
const { x: x2, y: y2 } = p2
|
|
60
|
+
|
|
61
|
+
const chips = this.getChipsInBounds({
|
|
62
|
+
minX: Math.min(x1, x2),
|
|
63
|
+
minY: Math.min(y1, y2),
|
|
64
|
+
maxX: Math.max(x1, x2),
|
|
65
|
+
maxY: Math.max(y1, y2),
|
|
66
|
+
}).filter((chip) => !excludeChipIds.includes(chip.chipId))
|
|
67
|
+
|
|
68
|
+
return chips.length > 0
|
|
69
|
+
}
|
|
70
|
+
}
|
package/lib/index.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
|
|
3
|
+
export class BaseSolver {
|
|
4
|
+
MAX_ITERATIONS = 100e3
|
|
5
|
+
solved = false
|
|
6
|
+
failed = false
|
|
7
|
+
iterations = 0
|
|
8
|
+
progress = 0
|
|
9
|
+
error: string | null = null
|
|
10
|
+
activeSubSolver?: BaseSolver | null
|
|
11
|
+
failedSubSolvers?: BaseSolver[]
|
|
12
|
+
timeToSolve?: number
|
|
13
|
+
stats: Record<string, any> = {}
|
|
14
|
+
|
|
15
|
+
/** DO NOT OVERRIDE! Override _step() instead */
|
|
16
|
+
step() {
|
|
17
|
+
if (this.solved) return
|
|
18
|
+
if (this.failed) return
|
|
19
|
+
this.iterations++
|
|
20
|
+
try {
|
|
21
|
+
this._step()
|
|
22
|
+
} catch (e) {
|
|
23
|
+
this.error = `${this.constructor.name} error: ${e}`
|
|
24
|
+
this.failed = true
|
|
25
|
+
throw e
|
|
26
|
+
}
|
|
27
|
+
if (!this.solved && this.iterations > this.MAX_ITERATIONS) {
|
|
28
|
+
this.tryFinalAcceptance()
|
|
29
|
+
}
|
|
30
|
+
if (!this.solved && this.iterations > this.MAX_ITERATIONS) {
|
|
31
|
+
this.error = `${this.constructor.name} ran out of iterations`
|
|
32
|
+
this.failed = true
|
|
33
|
+
}
|
|
34
|
+
if ("computeProgress" in this) {
|
|
35
|
+
// @ts-ignore
|
|
36
|
+
this.progress = this.computeProgress() as number
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
_step() {}
|
|
41
|
+
|
|
42
|
+
getConstructorParams() {
|
|
43
|
+
throw new Error("getConstructorParams not implemented")
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
solve() {
|
|
47
|
+
const startTime = Date.now()
|
|
48
|
+
while (!this.solved && !this.failed) {
|
|
49
|
+
this.step()
|
|
50
|
+
}
|
|
51
|
+
const endTime = Date.now()
|
|
52
|
+
this.timeToSolve = endTime - startTime
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
visualize(): GraphicsObject {
|
|
56
|
+
return {
|
|
57
|
+
lines: [],
|
|
58
|
+
points: [],
|
|
59
|
+
rects: [],
|
|
60
|
+
circles: [],
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Called when the solver is about to fail, but we want to see if we have an
|
|
66
|
+
* "acceptable" or "passable" solution. Mostly used for optimizers that
|
|
67
|
+
* have an aggressive early stopping criterion.
|
|
68
|
+
*/
|
|
69
|
+
tryFinalAcceptance() {}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A lightweight version of the visualize method that can be used to stream
|
|
73
|
+
* progress
|
|
74
|
+
*/
|
|
75
|
+
preview(): GraphicsObject {
|
|
76
|
+
return {
|
|
77
|
+
lines: [],
|
|
78
|
+
points: [],
|
|
79
|
+
rects: [],
|
|
80
|
+
circles: [],
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
2
|
+
import type { InputChip, InputProblem } from "lib/types/InputProblem"
|
|
3
|
+
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
4
|
+
import { getBounds, type GraphicsObject } from "graphics-debug"
|
|
5
|
+
import { getGeneratorForAllChipPairs } from "./getGeneratorForAllChipPairs"
|
|
6
|
+
import { getInputChipBounds } from "./getInputChipBounds"
|
|
7
|
+
import { getHorizontalGuidelineY } from "./getHorizontalGuidelineY"
|
|
8
|
+
import { getVerticalGuidelineX } from "./getVerticalGuidelineX"
|
|
9
|
+
|
|
10
|
+
export type Guideline =
|
|
11
|
+
| {
|
|
12
|
+
orientation: "horizontal"
|
|
13
|
+
y: number
|
|
14
|
+
x: undefined
|
|
15
|
+
}
|
|
16
|
+
| {
|
|
17
|
+
orientation: "vertical"
|
|
18
|
+
y: undefined
|
|
19
|
+
x: number
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class GuidelinesSolver extends BaseSolver {
|
|
23
|
+
inputProblem: InputProblem
|
|
24
|
+
guidelines: Guideline[]
|
|
25
|
+
|
|
26
|
+
chipPairsGenerator: Generator<readonly [InputChip, InputChip]>
|
|
27
|
+
|
|
28
|
+
usedXGuidelines: Set<number>
|
|
29
|
+
usedYGuidelines: Set<number>
|
|
30
|
+
|
|
31
|
+
constructor(params: {
|
|
32
|
+
inputProblem: InputProblem
|
|
33
|
+
}) {
|
|
34
|
+
super()
|
|
35
|
+
this.inputProblem = params.inputProblem
|
|
36
|
+
this.guidelines = []
|
|
37
|
+
this.chipPairsGenerator = getGeneratorForAllChipPairs(
|
|
38
|
+
this.inputProblem.chips,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
this.usedXGuidelines = new Set()
|
|
42
|
+
this.usedYGuidelines = new Set()
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override getConstructorParams(): ConstructorParameters<
|
|
46
|
+
typeof GuidelinesSolver
|
|
47
|
+
>[0] {
|
|
48
|
+
return {
|
|
49
|
+
inputProblem: this.inputProblem,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
override _step() {
|
|
54
|
+
const { done, value: chipPair } = this.chipPairsGenerator.next()
|
|
55
|
+
if (done) {
|
|
56
|
+
this.solved = true
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const [chip1, chip2] = chipPair
|
|
61
|
+
|
|
62
|
+
const chip1Bounds = getInputChipBounds(chip1)
|
|
63
|
+
const chip2Bounds = getInputChipBounds(chip2)
|
|
64
|
+
|
|
65
|
+
const horizontalGuidelineY = getHorizontalGuidelineY(
|
|
66
|
+
chip1Bounds,
|
|
67
|
+
chip2Bounds,
|
|
68
|
+
)
|
|
69
|
+
const verticalGuidelineX = getVerticalGuidelineX(chip1Bounds, chip2Bounds)
|
|
70
|
+
|
|
71
|
+
if (!this.usedYGuidelines.has(horizontalGuidelineY)) {
|
|
72
|
+
this.usedYGuidelines.add(horizontalGuidelineY)
|
|
73
|
+
this.guidelines.push({
|
|
74
|
+
orientation: "horizontal",
|
|
75
|
+
y: horizontalGuidelineY,
|
|
76
|
+
x: undefined,
|
|
77
|
+
})
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (!this.usedXGuidelines.has(verticalGuidelineX)) {
|
|
81
|
+
this.usedXGuidelines.add(verticalGuidelineX)
|
|
82
|
+
this.guidelines.push({
|
|
83
|
+
orientation: "vertical",
|
|
84
|
+
y: undefined,
|
|
85
|
+
x: verticalGuidelineX,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
override visualize(): GraphicsObject {
|
|
91
|
+
const graphics = visualizeInputProblem(this.inputProblem)
|
|
92
|
+
|
|
93
|
+
const bounds = getBounds(graphics)
|
|
94
|
+
const boundsWidth = bounds.maxX - bounds.minX
|
|
95
|
+
const boundsHeight = bounds.maxY - bounds.minY
|
|
96
|
+
bounds.minX -= boundsWidth * 0.3
|
|
97
|
+
bounds.maxX += boundsWidth * 0.3
|
|
98
|
+
bounds.minY -= boundsHeight * 0.3
|
|
99
|
+
bounds.maxY += boundsHeight * 0.3
|
|
100
|
+
|
|
101
|
+
for (const guideline of this.guidelines) {
|
|
102
|
+
if (guideline.orientation === "horizontal") {
|
|
103
|
+
graphics.lines!.push({
|
|
104
|
+
points: [
|
|
105
|
+
{
|
|
106
|
+
x: bounds.minX,
|
|
107
|
+
y: guideline.y,
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
x: bounds.maxX,
|
|
111
|
+
y: guideline.y,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
strokeColor: "rgba(0, 0, 0, 0.5)",
|
|
115
|
+
strokeDash: "2 2",
|
|
116
|
+
})
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (guideline.orientation === "vertical") {
|
|
120
|
+
graphics.lines!.push({
|
|
121
|
+
points: [
|
|
122
|
+
{
|
|
123
|
+
x: guideline.x,
|
|
124
|
+
y: bounds.minY,
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
x: guideline.x,
|
|
128
|
+
y: bounds.maxY,
|
|
129
|
+
},
|
|
130
|
+
],
|
|
131
|
+
strokeColor: "rgba(0, 0, 0, 0.5)",
|
|
132
|
+
strokeDash: "2 2",
|
|
133
|
+
})
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return graphics
|
|
137
|
+
}
|
|
138
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { InputChip, InputProblem } from "lib/types/InputProblem"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates a generator function that yields all possible pairs of chips from the given array.
|
|
5
|
+
* Each pair consists of two different chips (a chip is never paired with itself).
|
|
6
|
+
*
|
|
7
|
+
* @param chips - Array of chips to generate pairs from
|
|
8
|
+
* @returns A generator function that yields [chip, otherChip] tuples
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* const chips = [chip1, chip2, chip3];
|
|
13
|
+
* const pairGenerator = getGeneratorForAllChipPairs(chips);
|
|
14
|
+
*
|
|
15
|
+
* for (const [chip, otherChip] of pairGenerator()) {
|
|
16
|
+
* console.log(`Pair: ${chip.id} -> ${otherChip.id}`);
|
|
17
|
+
* }
|
|
18
|
+
* // Output:
|
|
19
|
+
* // Pair: chip1 -> chip2
|
|
20
|
+
* // Pair: chip1 -> chip3
|
|
21
|
+
* // Pair: chip2 -> chip1
|
|
22
|
+
* // Pair: chip2 -> chip3
|
|
23
|
+
* // Pair: chip3 -> chip1
|
|
24
|
+
* // Pair: chip3 -> chip2
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export const getGeneratorForAllChipPairs = (
|
|
28
|
+
chips: InputChip[],
|
|
29
|
+
): Generator<readonly [InputChip, InputChip]> => {
|
|
30
|
+
return (function* () {
|
|
31
|
+
for (let i = 0; i < chips.length; i++) {
|
|
32
|
+
for (let j = 0; j < chips.length; j++) {
|
|
33
|
+
if (i !== j) {
|
|
34
|
+
yield [chips[i], chips[j]] as const
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
})()
|
|
39
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ChipBounds } from "./getInputChipBounds"
|
|
2
|
+
|
|
3
|
+
export function getHorizontalGuidelineY(
|
|
4
|
+
chip1Bounds: ChipBounds,
|
|
5
|
+
chip2Bounds: ChipBounds,
|
|
6
|
+
): number {
|
|
7
|
+
if (chip1Bounds.maxY <= chip2Bounds.minY) {
|
|
8
|
+
return (chip1Bounds.maxY + chip2Bounds.minY) / 2
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (chip2Bounds.maxY <= chip1Bounds.minY) {
|
|
12
|
+
return (chip2Bounds.maxY + chip1Bounds.minY) / 2
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const overlapMinY = Math.max(chip1Bounds.minY, chip2Bounds.minY)
|
|
16
|
+
const overlapMaxY = Math.min(chip1Bounds.maxY, chip2Bounds.maxY)
|
|
17
|
+
return (overlapMinY + overlapMaxY) / 2
|
|
18
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { InputChip } from "lib/types/InputProblem"
|
|
2
|
+
|
|
3
|
+
export interface ChipBounds {
|
|
4
|
+
minX: number
|
|
5
|
+
maxX: number
|
|
6
|
+
minY: number
|
|
7
|
+
maxY: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getInputChipBounds(chip: InputChip): ChipBounds {
|
|
11
|
+
const halfWidth = chip.width / 2
|
|
12
|
+
const halfHeight = chip.height / 2
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
minX: chip.center.x - halfWidth,
|
|
16
|
+
maxX: chip.center.x + halfWidth,
|
|
17
|
+
minY: chip.center.y - halfHeight,
|
|
18
|
+
maxY: chip.center.y + halfHeight,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ChipBounds } from "./getInputChipBounds"
|
|
2
|
+
|
|
3
|
+
export function getVerticalGuidelineX(
|
|
4
|
+
chip1Bounds: ChipBounds,
|
|
5
|
+
chip2Bounds: ChipBounds,
|
|
6
|
+
): number {
|
|
7
|
+
if (chip1Bounds.maxX <= chip2Bounds.minX) {
|
|
8
|
+
return (chip1Bounds.maxX + chip2Bounds.minX) / 2
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (chip2Bounds.maxX <= chip1Bounds.minX) {
|
|
12
|
+
return (chip2Bounds.maxX + chip1Bounds.minX) / 2
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const overlapMinX = Math.max(chip1Bounds.minX, chip2Bounds.minX)
|
|
16
|
+
const overlapMaxX = Math.min(chip1Bounds.maxX, chip2Bounds.maxX)
|
|
17
|
+
return (overlapMinX + overlapMaxX) / 2
|
|
18
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
2
|
+
import type { Guideline } from "./GuidelinesSolver"
|
|
3
|
+
import { getBounds } from "graphics-debug"
|
|
4
|
+
|
|
5
|
+
export const visualizeGuidelines = ({
|
|
6
|
+
guidelines,
|
|
7
|
+
graphics,
|
|
8
|
+
}: {
|
|
9
|
+
guidelines: Guideline[]
|
|
10
|
+
graphics: GraphicsObject
|
|
11
|
+
}) => {
|
|
12
|
+
const globalBounds = getBounds(graphics)
|
|
13
|
+
const boundsWidth = globalBounds.maxX - globalBounds.minX
|
|
14
|
+
const boundsHeight = globalBounds.maxY - globalBounds.minY
|
|
15
|
+
globalBounds.minX -= boundsWidth * 0.3
|
|
16
|
+
globalBounds.maxX += boundsWidth * 0.3
|
|
17
|
+
globalBounds.minY -= boundsHeight * 0.3
|
|
18
|
+
globalBounds.maxY += boundsHeight * 0.3
|
|
19
|
+
|
|
20
|
+
for (const guideline of guidelines) {
|
|
21
|
+
if (guideline.orientation === "horizontal") {
|
|
22
|
+
graphics.lines!.push({
|
|
23
|
+
points: [
|
|
24
|
+
{ x: globalBounds.minX, y: guideline.y },
|
|
25
|
+
{ x: globalBounds.maxX, y: guideline.y },
|
|
26
|
+
],
|
|
27
|
+
strokeColor: "rgba(0, 0, 0, 0.5)",
|
|
28
|
+
strokeDash: "2 2",
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (guideline.orientation === "vertical") {
|
|
33
|
+
graphics.lines!.push({
|
|
34
|
+
points: [
|
|
35
|
+
{ x: guideline.x, y: globalBounds.minY },
|
|
36
|
+
{ x: guideline.x, y: globalBounds.maxY },
|
|
37
|
+
],
|
|
38
|
+
strokeColor: "rgba(0, 0, 0, 0.5)",
|
|
39
|
+
strokeDash: "2 2",
|
|
40
|
+
})
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return graphics
|
|
45
|
+
}
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver"
|
|
2
|
+
import type { InputChip, InputPin, InputProblem } from "lib/types/InputProblem"
|
|
3
|
+
import { ConnectivityMap } from "connectivity-map"
|
|
4
|
+
import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
|
|
5
|
+
import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
|
|
6
|
+
import type { GraphicsObject } from "graphics-debug"
|
|
7
|
+
import { getColorFromString } from "lib/utils/getColorFromString"
|
|
8
|
+
import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
|
|
9
|
+
|
|
10
|
+
export type MspConnectionPairId = string
|
|
11
|
+
|
|
12
|
+
export type MspConnectionPair = {
|
|
13
|
+
mspPairId: MspConnectionPairId
|
|
14
|
+
dcConnNetId: string
|
|
15
|
+
globalConnNetId: string
|
|
16
|
+
userNetId?: string
|
|
17
|
+
pins: [InputPin & { chipId: string }, InputPin & { chipId: string }]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class MspConnectionPairSolver extends BaseSolver {
|
|
21
|
+
inputProblem: InputProblem
|
|
22
|
+
|
|
23
|
+
mspConnectionPairs: MspConnectionPair[] = []
|
|
24
|
+
dcConnMap: ConnectivityMap
|
|
25
|
+
globalConnMap: ConnectivityMap
|
|
26
|
+
queuedDcNetIds: string[]
|
|
27
|
+
chipMap: Record<string, InputChip>
|
|
28
|
+
|
|
29
|
+
pinMap: Record<string, InputPin & { chipId: string }>
|
|
30
|
+
userNetIdByPinId: Record<string, string | undefined>
|
|
31
|
+
|
|
32
|
+
constructor({ inputProblem }: { inputProblem: InputProblem }) {
|
|
33
|
+
super()
|
|
34
|
+
|
|
35
|
+
this.inputProblem = inputProblem
|
|
36
|
+
|
|
37
|
+
const { directConnMap, netConnMap } =
|
|
38
|
+
getConnectivityMapsFromInputProblem(inputProblem)
|
|
39
|
+
this.dcConnMap = directConnMap
|
|
40
|
+
this.globalConnMap = netConnMap
|
|
41
|
+
|
|
42
|
+
this.pinMap = {}
|
|
43
|
+
for (const chip of inputProblem.chips) {
|
|
44
|
+
for (const pin of chip.pins) {
|
|
45
|
+
this.pinMap[pin.pinId] = { ...pin, chipId: chip.chipId }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
this.chipMap = {}
|
|
50
|
+
for (const chip of inputProblem.chips) {
|
|
51
|
+
this.chipMap[chip.chipId] = chip
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Build a mapping from PinId to user-provided netId (if any)
|
|
55
|
+
this.userNetIdByPinId = {}
|
|
56
|
+
for (const dc of inputProblem.directConnections) {
|
|
57
|
+
if (dc.netId) {
|
|
58
|
+
const [a, b] = dc.pinIds
|
|
59
|
+
this.userNetIdByPinId[a] = dc.netId
|
|
60
|
+
this.userNetIdByPinId[b] = dc.netId
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
for (const nc of inputProblem.netConnections) {
|
|
64
|
+
for (const pid of nc.pinIds) {
|
|
65
|
+
this.userNetIdByPinId[pid] = nc.netId
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.queuedDcNetIds = Object.keys(directConnMap.netMap)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
override getConstructorParams(): ConstructorParameters<
|
|
73
|
+
typeof MspConnectionPairSolver
|
|
74
|
+
>[0] {
|
|
75
|
+
return {
|
|
76
|
+
inputProblem: this.inputProblem,
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
override _step() {
|
|
81
|
+
if (this.queuedDcNetIds.length === 0) {
|
|
82
|
+
this.solved = true
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const dcNetId = this.queuedDcNetIds.shift()!
|
|
87
|
+
|
|
88
|
+
const directlyConnectedPins = this.dcConnMap.getIdsConnectedToNet(dcNetId)
|
|
89
|
+
|
|
90
|
+
if (directlyConnectedPins.length === 1) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (directlyConnectedPins.length === 2) {
|
|
95
|
+
const [pin1, pin2] = directlyConnectedPins
|
|
96
|
+
const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1!)!
|
|
97
|
+
const userNetId =
|
|
98
|
+
this.userNetIdByPinId[pin1!] ?? this.userNetIdByPinId[pin2!]
|
|
99
|
+
|
|
100
|
+
this.mspConnectionPairs.push({
|
|
101
|
+
mspPairId: `${pin1}-${pin2}`,
|
|
102
|
+
dcConnNetId: dcNetId,
|
|
103
|
+
globalConnNetId,
|
|
104
|
+
userNetId,
|
|
105
|
+
pins: [this.pinMap[pin1!]!, this.pinMap[pin2!]!],
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// There are more than 3 pins, so we need to run MSP to find the best pairs
|
|
112
|
+
|
|
113
|
+
const msp = getOrthogonalMinimumSpanningTree(
|
|
114
|
+
directlyConnectedPins.map((p) => this.pinMap[p]!),
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
for (const [pin1, pin2] of msp) {
|
|
118
|
+
const globalConnNetId = this.globalConnMap.getNetConnectedToId(pin1!)!
|
|
119
|
+
const userNetId =
|
|
120
|
+
this.userNetIdByPinId[pin1!] ?? this.userNetIdByPinId[pin2!]
|
|
121
|
+
this.mspConnectionPairs.push({
|
|
122
|
+
mspPairId: `${pin1}-${pin2}`,
|
|
123
|
+
dcConnNetId: dcNetId,
|
|
124
|
+
globalConnNetId,
|
|
125
|
+
userNetId,
|
|
126
|
+
pins: [this.pinMap[pin1!]!, this.pinMap[pin2!]!],
|
|
127
|
+
})
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
override visualize(): GraphicsObject {
|
|
132
|
+
const graphics = visualizeInputProblem(this.inputProblem, {
|
|
133
|
+
chipAlpha: 0.1,
|
|
134
|
+
connectionAlpha: 0.1,
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
// Draw all the solved MSP with lines, and the next-to-be-solved points with points
|
|
138
|
+
for (const pair of this.mspConnectionPairs) {
|
|
139
|
+
graphics.lines!.push({
|
|
140
|
+
points: [
|
|
141
|
+
{
|
|
142
|
+
x: pair.pins[0].x,
|
|
143
|
+
y: pair.pins[0].y,
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
x: pair.pins[1].x,
|
|
147
|
+
y: pair.pins[1].y,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
strokeColor: getColorFromString(pair.mspPairId, 0.75),
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return graphics
|
|
155
|
+
}
|
|
156
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ConnectivityMap } from "connectivity-map"
|
|
2
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
3
|
+
|
|
4
|
+
export const getConnectivityMapsFromInputProblem = (
|
|
5
|
+
inputProblem: InputProblem,
|
|
6
|
+
): { directConnMap: ConnectivityMap; netConnMap: ConnectivityMap } => {
|
|
7
|
+
const directConnMap = new ConnectivityMap({})
|
|
8
|
+
|
|
9
|
+
for (const directConn of inputProblem.directConnections) {
|
|
10
|
+
directConnMap.addConnections([directConn.pinIds])
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const netConnMap = new ConnectivityMap(directConnMap.netMap)
|
|
14
|
+
|
|
15
|
+
for (const netConn of inputProblem.netConnections) {
|
|
16
|
+
netConnMap.addConnections([netConn.pinIds])
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return { directConnMap, netConnMap }
|
|
20
|
+
}
|