@tscircuit/schematic-trace-solver 0.0.166 → 0.0.167
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/dist/index.d.ts +5 -1
- package/dist/index.js +25 -3
- package/lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy.ts +38 -4
- package/lib/types/InputProblem.ts +5 -1
- package/package.json +1 -1
- package/tests/repros/__snapshots__/repro-pga300-redundant-ground-traces.snap.svg +44 -36
- package/tests/repros/repro-pga300-redundant-ground-traces.test.ts +35 -7
- package/tests/solvers/MspConnectionPairSolver/ground-direct-connection-groups.test.ts +120 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { expect, test } from "bun:test"
|
|
2
|
+
import { ConnectivityMap } from "connectivity-map"
|
|
2
3
|
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
3
4
|
import type { InputProblem } from "lib/types/InputProblem"
|
|
4
5
|
import "tests/fixtures/matcher"
|
|
@@ -12,22 +13,49 @@ const GND = "schematic_port_7"
|
|
|
12
13
|
const C3_GND = "schematic_port_32"
|
|
13
14
|
const R1_GND = "schematic_port_38"
|
|
14
15
|
|
|
15
|
-
test("
|
|
16
|
+
test("PGA300 preserves COMP to R1 without redundant ground branches", async () => {
|
|
16
17
|
const solver = new SchematicTracePipelineSolver(
|
|
17
18
|
inputProblem as unknown as InputProblem,
|
|
18
19
|
)
|
|
19
20
|
solver.solve()
|
|
20
21
|
|
|
21
22
|
expect(solver.solved).toBe(true)
|
|
22
|
-
const { traces } =
|
|
23
|
+
const { traces, netLabelPlacements } =
|
|
24
|
+
solver.netLabelToTraceSolver!.getOutput()
|
|
23
25
|
const hasPair = (a: string, b: string) =>
|
|
24
26
|
traces.some((trace) => trace.pinIds.includes(a) && trace.pinIds.includes(b))
|
|
25
27
|
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
expect(hasPair(COMP, GND)).toBe(
|
|
29
|
-
expect(hasPair(COMP, C3_GND)).toBe(
|
|
30
|
-
expect(hasPair(COMP, R1_GND)).toBe(
|
|
28
|
+
// GND labels provide global connectivity; they must not replace the
|
|
29
|
+
// explicitly requested COMP -> R1 connection with unrelated ground branches.
|
|
30
|
+
expect(hasPair(COMP, GND)).toBe(false)
|
|
31
|
+
expect(hasPair(COMP, C3_GND)).toBe(false)
|
|
32
|
+
expect(hasPair(COMP, R1_GND)).toBe(true)
|
|
33
|
+
|
|
34
|
+
// Removing physical branches must leave every original GND pin connected
|
|
35
|
+
// through a routed island and a ground label, including the capacitor pins.
|
|
36
|
+
const renderedConnectivity = new ConnectivityMap({})
|
|
37
|
+
for (const trace of traces)
|
|
38
|
+
renderedConnectivity.addConnections([trace.pinIds])
|
|
39
|
+
for (const label of netLabelPlacements) {
|
|
40
|
+
if (label.netId) {
|
|
41
|
+
renderedConnectivity.addConnections([[label.netId, ...label.pinIds]])
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const groundNet = renderedConnectivity.getNetConnectedToId("GND")
|
|
45
|
+
expect(groundNet).toBeDefined()
|
|
46
|
+
for (const pinId of inputProblem.netConnections.find((net) => net.isGround)!
|
|
47
|
+
.pinIds) {
|
|
48
|
+
expect(renderedConnectivity.getNetConnectedToId(pinId)).toBe(groundNet)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Keep the already-fixed vertical Q1 emitter/R1 connection.
|
|
52
|
+
const emitterTrace = traces.find(
|
|
53
|
+
(trace) =>
|
|
54
|
+
trace.pinIds.includes("schematic_port_34") &&
|
|
55
|
+
trace.pinIds.includes("schematic_port_37"),
|
|
56
|
+
)!
|
|
57
|
+
expect(emitterTrace).toBeDefined()
|
|
58
|
+
for (const point of emitterTrace.tracePath) expect(point.x).toBeCloseTo(5.49)
|
|
31
59
|
|
|
32
60
|
await expect(solver).toMatchSolverSnapshot(import.meta.path)
|
|
33
61
|
})
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
import { LongDistancePairSolver } from "lib/solvers/LongDistancePairSolver/LongDistancePairSolver"
|
|
3
|
+
import { MspConnectionPairSolver } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
|
|
4
|
+
import { getGroundConnectionPolicy } from "lib/solvers/MspConnectionPairSolver/getGroundConnectionPolicy"
|
|
5
|
+
import { SchematicTracePipelineSolver } from "lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver"
|
|
6
|
+
import type { InputProblem } from "lib/types/InputProblem"
|
|
7
|
+
|
|
8
|
+
// The explicit A-B and C-D wires are farther apart than their neighbors on
|
|
9
|
+
// the same named net. E is label-only, with no requested physical connection.
|
|
10
|
+
const createInput = (): InputProblem => ({
|
|
11
|
+
chips: [0, 4, 1, 5, 2].map((x, index) => {
|
|
12
|
+
const chipId = "ABCDE"[index]!
|
|
13
|
+
return {
|
|
14
|
+
chipId,
|
|
15
|
+
center: { x, y: 0 },
|
|
16
|
+
width: 0.2,
|
|
17
|
+
height: 0.2,
|
|
18
|
+
pins: [{ pinId: chipId, x, y: 0.1, _facingDirection: "y+" }],
|
|
19
|
+
}
|
|
20
|
+
}),
|
|
21
|
+
directConnections: [
|
|
22
|
+
{ pinIds: ["A", "B"], netId: "AGND" },
|
|
23
|
+
{ pinIds: ["C", "D"], netId: "AGND" },
|
|
24
|
+
],
|
|
25
|
+
netConnections: [
|
|
26
|
+
{ netId: "AGND", pinIds: ["A", "B", "C", "D", "E"], isGround: true },
|
|
27
|
+
],
|
|
28
|
+
availableNetLabelOrientations: { AGND: ["y+"] },
|
|
29
|
+
maxMspPairDistance: 100,
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
const pairKeys = (solver: MspConnectionPairSolver) =>
|
|
33
|
+
solver.mspConnectionPairs
|
|
34
|
+
.map((pair) =>
|
|
35
|
+
pair.pins
|
|
36
|
+
.map((pin) => pin.pinId)
|
|
37
|
+
.sort()
|
|
38
|
+
.join("-"),
|
|
39
|
+
)
|
|
40
|
+
.sort()
|
|
41
|
+
|
|
42
|
+
test("marked ground nets retain explicit islands even with identical netIds", () => {
|
|
43
|
+
const inputProblem = createInput()
|
|
44
|
+
const canRoute = getGroundConnectionPolicy(inputProblem)
|
|
45
|
+
expect(canRoute("A", "B")).toBe(true)
|
|
46
|
+
expect(canRoute("C", "D")).toBe(true)
|
|
47
|
+
expect(canRoute("A", "C")).toBe(false)
|
|
48
|
+
expect(canRoute("A", "E")).toBe(false)
|
|
49
|
+
|
|
50
|
+
const solver = new MspConnectionPairSolver({ inputProblem })
|
|
51
|
+
solver.solve()
|
|
52
|
+
expect(pairKeys(solver)).toEqual(["A-B", "C-D"])
|
|
53
|
+
// Physical partitioning must not split the electrical net.
|
|
54
|
+
expect(solver.globalConnMap.getNetConnectedToId("A")).toBe(
|
|
55
|
+
solver.globalConnMap.getNetConnectedToId("E"),
|
|
56
|
+
)
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
test.each([false, undefined])(
|
|
60
|
+
"isGround=%s preserves automatic net routing",
|
|
61
|
+
(isGround) => {
|
|
62
|
+
const inputProblem = createInput()
|
|
63
|
+
inputProblem.netConnections[0]!.isGround = isGround
|
|
64
|
+
const solver = new MspConnectionPairSolver({ inputProblem })
|
|
65
|
+
solver.solve()
|
|
66
|
+
expect(getGroundConnectionPolicy(inputProblem)("A", "C")).toBe(true)
|
|
67
|
+
expect(solver.mspConnectionPairs).toHaveLength(4)
|
|
68
|
+
},
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
test("ground nets without explicit wires retain automatic shared buses", () => {
|
|
72
|
+
const inputProblem = createInput()
|
|
73
|
+
inputProblem.directConnections = []
|
|
74
|
+
const solver = new MspConnectionPairSolver({ inputProblem })
|
|
75
|
+
solver.solve()
|
|
76
|
+
expect(getGroundConnectionPolicy(inputProblem)("A", "C")).toBe(true)
|
|
77
|
+
expect(solver.mspConnectionPairs).toHaveLength(4)
|
|
78
|
+
})
|
|
79
|
+
|
|
80
|
+
test("long-distance recovery cannot reconnect separate ground islands", () => {
|
|
81
|
+
const inputProblem = createInput()
|
|
82
|
+
const solver = new LongDistancePairSolver({
|
|
83
|
+
inputProblem,
|
|
84
|
+
alreadySolvedTraces: [],
|
|
85
|
+
primaryMspConnectionPairs: [],
|
|
86
|
+
failedConnectionPairs: [],
|
|
87
|
+
})
|
|
88
|
+
solver.solve()
|
|
89
|
+
expect(solver.solvedLongDistanceTraces.length).toBeGreaterThan(0)
|
|
90
|
+
for (const trace of solver.solvedLongDistanceTraces) {
|
|
91
|
+
expect(["A-B", "C-D"]).toContain([...trace.pinIds].sort().join("-"))
|
|
92
|
+
}
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
test("explicit ground connections still respect distance and section limits", () => {
|
|
96
|
+
const inputProblem = createInput()
|
|
97
|
+
inputProblem.maxMspPairDistance = 0.5
|
|
98
|
+
const solver = new MspConnectionPairSolver({ inputProblem })
|
|
99
|
+
solver.solve()
|
|
100
|
+
expect(solver.mspConnectionPairs).toHaveLength(0)
|
|
101
|
+
|
|
102
|
+
inputProblem.maxMspPairDistance = 100
|
|
103
|
+
inputProblem.chips[0]!.sectionId = "left"
|
|
104
|
+
inputProblem.chips[1]!.sectionId = "right"
|
|
105
|
+
const sectionSolver = new MspConnectionPairSolver({ inputProblem })
|
|
106
|
+
sectionSolver.solve()
|
|
107
|
+
expect(pairKeys(sectionSolver)).toEqual(["C-D"])
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
test("label-only ground pins remain connected in the pipeline output", () => {
|
|
111
|
+
const solver = new SchematicTracePipelineSolver(createInput())
|
|
112
|
+
solver.solve()
|
|
113
|
+
const output = solver.netLabelToTraceSolver!.getOutput()
|
|
114
|
+
expect(solver.solved).toBe(true)
|
|
115
|
+
expect(
|
|
116
|
+
output.netLabelPlacements.some(
|
|
117
|
+
(label) => label.netId === "AGND" && label.pinIds.includes("E"),
|
|
118
|
+
),
|
|
119
|
+
).toBe(true)
|
|
120
|
+
})
|