@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.
@@ -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("repro: PGA300 adds redundant ground branches at COMP", async () => {
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 } = solver.netLabelToTraceSolver!.getOutput()
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
- // Current bug: the global GND spanning tree substitutes these two branches
27
- // for the explicitly requested COMP -> R1 connection in the source circuit.
28
- expect(hasPair(COMP, GND)).toBe(true)
29
- expect(hasPair(COMP, C3_GND)).toBe(true)
30
- expect(hasPair(COMP, R1_GND)).toBe(false)
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
+ })