@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
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,2 @@
1
+ export * from "./solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
2
+ export * from "./types/InputProblem"
@@ -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
+ }