@tscircuit/schematic-trace-solver 0.0.113 → 0.0.114

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 CHANGED
@@ -586,6 +586,8 @@ declare class LongDistancePairSolver extends BaseSolver {
586
586
  solvedLongDistanceTraces: SolvedTracePath[];
587
587
  private queuedCandidatePairs;
588
588
  private currentCandidatePair;
589
+ private queuedFailedConnectionPairs;
590
+ private currentFailedConnectionPair;
589
591
  private subSolver;
590
592
  private chipMap;
591
593
  private inputProblem;
@@ -596,12 +598,15 @@ declare class LongDistancePairSolver extends BaseSolver {
596
598
  inputProblem: InputProblem;
597
599
  alreadySolvedTraces: SolvedTracePath[];
598
600
  primaryMspConnectionPairs: MspConnectionPair[];
601
+ failedConnectionPairs: MspConnectionPair[];
599
602
  });
600
603
  getConstructorParams(): {
601
604
  inputProblem: InputProblem;
602
605
  alreadySolvedTraces: SolvedTracePath[];
603
606
  primaryMspConnectionPairs: MspConnectionPair[];
607
+ failedConnectionPairs: MspConnectionPair[];
604
608
  };
609
+ private acceptFailedConnectionPair;
605
610
  _step(): void;
606
611
  visualize(): graphics_debug.GraphicsObject;
607
612
  getOutput(): {
package/dist/index.js CHANGED
@@ -420,6 +420,33 @@ function getOrthogonalMinimumSpanningTree(pins, opts = {}) {
420
420
  return edges;
421
421
  }
422
422
 
423
+ // lib/solvers/MspConnectionPairSolver/isLabeledPeripheralConnection.ts
424
+ var isLabeledPeripheralConnection = ({
425
+ inputProblem,
426
+ chipMap,
427
+ pins
428
+ }) => {
429
+ const [firstPin, secondPin] = pins;
430
+ const directConnection = inputProblem.directConnections.find(
431
+ (connection) => connection.pinIds.includes(firstPin.pinId) && connection.pinIds.includes(secondPin.pinId)
432
+ );
433
+ if (directConnection?.netLabelWidth === void 0) return false;
434
+ const firstChip = chipMap[firstPin.chipId];
435
+ const secondChip = chipMap[secondPin.chipId];
436
+ if (!firstChip || !secondChip) return false;
437
+ const hasSinglePinPeripheral = firstChip.pins.length === 1 || secondChip.pins.length === 1;
438
+ if (!hasSinglePinPeripheral) return false;
439
+ let firstFacingDirection = firstPin._facingDirection;
440
+ if (!firstFacingDirection) {
441
+ firstFacingDirection = getPinDirection(firstPin, firstChip);
442
+ }
443
+ let secondFacingDirection = secondPin._facingDirection;
444
+ if (!secondFacingDirection) {
445
+ secondFacingDirection = getPinDirection(secondPin, secondChip);
446
+ }
447
+ return firstFacingDirection === "x-" && secondFacingDirection === "x+" || firstFacingDirection === "x+" && secondFacingDirection === "x-";
448
+ };
449
+
423
450
  // lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts
424
451
  var DEFAULT_MAX_MSP_PAIR_DISTANCE = 1;
425
452
  var getPinPairKey = (pinIds) => [...pinIds].sort().join("::");
@@ -489,8 +516,16 @@ var MspConnectionPairSolver = class extends BaseSolver {
489
516
  const p1 = this.pinMap[pin1];
490
517
  const p2 = this.pinMap[pin2];
491
518
  const pinPairKey = getPinPairKey([pin1, pin2]);
492
- const pairDistance = this.directConnectionPinPairKeys.has(pinPairKey) ? distance(p1, p2) : Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
493
- if (pairDistance > this.maxMspPairDistance) {
519
+ let pairDistance = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y);
520
+ if (this.directConnectionPinPairKeys.has(pinPairKey)) {
521
+ pairDistance = distance(p1, p2);
522
+ }
523
+ const isLabeledPeripheral = isLabeledPeripheralConnection({
524
+ inputProblem: this.inputProblem,
525
+ chipMap: this.chipMap,
526
+ pins: [p1, p2]
527
+ });
528
+ if (pairDistance > this.maxMspPairDistance && !isLabeledPeripheral) {
494
529
  return;
495
530
  }
496
531
  if (arePinsInDifferentSchematicSections(this.inputProblem, p1, p2)) {
@@ -4495,6 +4530,13 @@ var LongDistancePairSolver = class extends BaseSolver {
4495
4530
  pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
4496
4531
  }
4497
4532
  }
4533
+ this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
4534
+ (connectionPair) => isLabeledPeripheralConnection({
4535
+ inputProblem: this.inputProblem,
4536
+ chipMap: this.chipMap,
4537
+ pins: connectionPair.pins
4538
+ })
4539
+ );
4498
4540
  const candidatePairs = [];
4499
4541
  const addedPairKeys = /* @__PURE__ */ new Set();
4500
4542
  for (const netId of Object.keys(netConnMap.netMap)) {
@@ -4535,6 +4577,8 @@ var LongDistancePairSolver = class extends BaseSolver {
4535
4577
  solvedLongDistanceTraces = [];
4536
4578
  queuedCandidatePairs = [];
4537
4579
  currentCandidatePair = null;
4580
+ queuedFailedConnectionPairs = [];
4581
+ currentFailedConnectionPair = null;
4538
4582
  subSolver = null;
4539
4583
  chipMap = {};
4540
4584
  inputProblem;
@@ -4544,8 +4588,31 @@ var LongDistancePairSolver = class extends BaseSolver {
4544
4588
  getConstructorParams() {
4545
4589
  return this.params;
4546
4590
  }
4591
+ acceptFailedConnectionPair(tracePath) {
4592
+ const connectionPair = this.currentFailedConnectionPair;
4593
+ if (!connectionPair) return;
4594
+ const solvedTrace = {
4595
+ ...connectionPair,
4596
+ tracePath,
4597
+ mspConnectionPairIds: [connectionPair.mspPairId],
4598
+ pinIds: connectionPair.pins.map((pin) => pin.pinId)
4599
+ };
4600
+ this.solvedLongDistanceTraces.push(solvedTrace);
4601
+ this.allSolvedTraces.push(solvedTrace);
4602
+ for (const pin of connectionPair.pins) {
4603
+ this.newlyConnectedPinIds.add(pin.pinId);
4604
+ }
4605
+ }
4547
4606
  _step() {
4548
- if (this.subSolver?.solved) {
4607
+ if (this.subSolver?.solved && this.currentFailedConnectionPair) {
4608
+ const tracePath = this.subSolver.solvedTracePath;
4609
+ if (tracePath) {
4610
+ this.acceptFailedConnectionPair(tracePath);
4611
+ }
4612
+ this.subSolver = null;
4613
+ this.currentCandidatePair = null;
4614
+ this.currentFailedConnectionPair = null;
4615
+ } else if (this.subSolver?.solved) {
4549
4616
  const newTracePath = this.subSolver.solvedTracePath;
4550
4617
  if (newTracePath && this.currentCandidatePair) {
4551
4618
  const isTraceClear = !doesTraceOverlapWithExistingTraces(
@@ -4576,11 +4643,23 @@ var LongDistancePairSolver = class extends BaseSolver {
4576
4643
  } else if (this.subSolver?.failed) {
4577
4644
  this.subSolver = null;
4578
4645
  this.currentCandidatePair = null;
4646
+ this.currentFailedConnectionPair = null;
4579
4647
  }
4580
4648
  if (this.subSolver) {
4581
4649
  this.subSolver.step();
4582
4650
  return;
4583
4651
  }
4652
+ const failedConnectionPair = this.queuedFailedConnectionPairs.shift();
4653
+ if (failedConnectionPair) {
4654
+ this.currentFailedConnectionPair = failedConnectionPair;
4655
+ this.currentCandidatePair = failedConnectionPair.pins;
4656
+ this.subSolver = new SchematicTraceSingleLineSolver2({
4657
+ inputProblem: this.params.inputProblem,
4658
+ pins: failedConnectionPair.pins,
4659
+ chipMap: this.chipMap
4660
+ });
4661
+ return;
4662
+ }
4584
4663
  while (this.queuedCandidatePairs.length > 0) {
4585
4664
  const nextPair = this.queuedCandidatePairs.shift();
4586
4665
  const [p1, p2] = nextPair;
@@ -10703,7 +10782,8 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
10703
10782
  {
10704
10783
  inputProblem: instance.inputProblem,
10705
10784
  primaryMspConnectionPairs: instance.mspConnectionPairSolver.mspConnectionPairs,
10706
- alreadySolvedTraces: instance.schematicTraceLinesSolver.solvedTracePaths
10785
+ alreadySolvedTraces: instance.schematicTraceLinesSolver.solvedTracePaths,
10786
+ failedConnectionPairs: instance.schematicTraceLinesSolver.failedConnectionPairs
10707
10787
  }
10708
10788
  ],
10709
10789
  {
@@ -13,6 +13,7 @@ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualize
13
13
  import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
14
14
  import type { ConnectivityMap } from "connectivity-map"
15
15
  import { arePinsInDifferentSchematicSections } from "../../utils/arePinsInDifferentSchematicSections"
16
+ import { isLabeledPeripheralConnection } from "../MspConnectionPairSolver/isLabeledPeripheralConnection"
16
17
 
17
18
  const NEAREST_NEIGHBOR_COUNT = 3
18
19
 
@@ -28,6 +29,8 @@ export class LongDistancePairSolver extends BaseSolver {
28
29
  private currentCandidatePair:
29
30
  | [InputPin & { chipId: string }, InputPin & { chipId: string }]
30
31
  | null = null
32
+ private queuedFailedConnectionPairs: MspConnectionPair[] = []
33
+ private currentFailedConnectionPair: MspConnectionPair | null = null
31
34
  private subSolver: SchematicTraceSingleLineSolver2 | null = null
32
35
  private chipMap: Record<string, InputChip> = {}
33
36
  private inputProblem: InputProblem
@@ -40,6 +43,7 @@ export class LongDistancePairSolver extends BaseSolver {
40
43
  inputProblem: InputProblem
41
44
  alreadySolvedTraces: SolvedTracePath[]
42
45
  primaryMspConnectionPairs: MspConnectionPair[]
46
+ failedConnectionPairs: MspConnectionPair[]
43
47
  },
44
48
  ) {
45
49
  super()
@@ -66,6 +70,16 @@ export class LongDistancePairSolver extends BaseSolver {
66
70
  pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId })
67
71
  }
68
72
  }
73
+ // Retry failed MSP pairs with their existing identity before creating
74
+ // new nearest-neighbor candidates.
75
+ this.queuedFailedConnectionPairs = this.params.failedConnectionPairs.filter(
76
+ (connectionPair) =>
77
+ isLabeledPeripheralConnection({
78
+ inputProblem: this.inputProblem,
79
+ chipMap: this.chipMap,
80
+ pins: connectionPair.pins,
81
+ }),
82
+ )
69
83
 
70
84
  // 2. Generate candidate pairs using N-Nearest-Neighbors approach
71
85
  const candidatePairs: Array<
@@ -129,9 +143,36 @@ export class LongDistancePairSolver extends BaseSolver {
129
143
  return this.params
130
144
  }
131
145
 
146
+ private acceptFailedConnectionPair(tracePath: SolvedTracePath["tracePath"]) {
147
+ const connectionPair = this.currentFailedConnectionPair
148
+ if (!connectionPair) return
149
+
150
+ // Reuse the original pair so downstream solvers retain its connectivity
151
+ // IDs, user net ID, pins, and MSP pair ID.
152
+ const solvedTrace: SolvedTracePath = {
153
+ ...connectionPair,
154
+ tracePath,
155
+ mspConnectionPairIds: [connectionPair.mspPairId],
156
+ pinIds: connectionPair.pins.map((pin) => pin.pinId),
157
+ }
158
+ this.solvedLongDistanceTraces.push(solvedTrace)
159
+ this.allSolvedTraces.push(solvedTrace)
160
+ for (const pin of connectionPair.pins) {
161
+ this.newlyConnectedPinIds.add(pin.pinId)
162
+ }
163
+ }
164
+
132
165
  override _step() {
133
166
  // 1. Check if a sub-solver has finished and process its result
134
- if (this.subSolver?.solved) {
167
+ if (this.subSolver?.solved && this.currentFailedConnectionPair) {
168
+ const tracePath = this.subSolver.solvedTracePath
169
+ if (tracePath) {
170
+ this.acceptFailedConnectionPair(tracePath)
171
+ }
172
+ this.subSolver = null
173
+ this.currentCandidatePair = null
174
+ this.currentFailedConnectionPair = null
175
+ } else if (this.subSolver?.solved) {
135
176
  const newTracePath = this.subSolver.solvedTracePath
136
177
  if (newTracePath && this.currentCandidatePair) {
137
178
  const isTraceClear = !doesTraceOverlapWithExistingTraces(
@@ -166,6 +207,7 @@ export class LongDistancePairSolver extends BaseSolver {
166
207
  } else if (this.subSolver?.failed) {
167
208
  this.subSolver = null
168
209
  this.currentCandidatePair = null
210
+ this.currentFailedConnectionPair = null
169
211
  }
170
212
 
171
213
  // 2. If a sub-solver is already running, let it continue
@@ -174,6 +216,18 @@ export class LongDistancePairSolver extends BaseSolver {
174
216
  return
175
217
  }
176
218
 
219
+ const failedConnectionPair = this.queuedFailedConnectionPairs.shift()
220
+ if (failedConnectionPair) {
221
+ this.currentFailedConnectionPair = failedConnectionPair
222
+ this.currentCandidatePair = failedConnectionPair.pins
223
+ this.subSolver = new SchematicTraceSingleLineSolver2({
224
+ inputProblem: this.params.inputProblem,
225
+ pins: failedConnectionPair.pins,
226
+ chipMap: this.chipMap,
227
+ })
228
+ return
229
+ }
230
+
177
231
  // 3. Find the next valid candidate pair and start a new sub-solver
178
232
  while (this.queuedCandidatePairs.length > 0) {
179
233
  const nextPair = this.queuedCandidatePairs.shift()!
@@ -14,6 +14,7 @@ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualize
14
14
  import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"
15
15
  import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
16
16
  import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
17
+ import { isLabeledPeripheralConnection } from "./isLabeledPeripheralConnection"
17
18
 
18
19
  export type MspConnectionPairId = string
19
20
  export const DEFAULT_MAX_MSP_PAIR_DISTANCE = 1
@@ -116,10 +117,18 @@ export class MspConnectionPairSolver extends BaseSolver {
116
117
  const pinPairKey = getPinPairKey([pin1!, pin2!])
117
118
  // Explicit source traces are classified by straight-line distance when
118
119
  // their input is created; named nets retain the orthogonal route metric.
119
- const pairDistance = this.directConnectionPinPairKeys.has(pinPairKey)
120
- ? distance(p1, p2)
121
- : Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
122
- if (pairDistance > this.maxMspPairDistance) {
120
+ let pairDistance = Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
121
+ if (this.directConnectionPinPairKeys.has(pinPairKey)) {
122
+ pairDistance = distance(p1, p2)
123
+ }
124
+ // Labeled one-pin peripherals need a real trace even when they are far
125
+ // apart; skipping the MSP pair would leave only the fallback path.
126
+ const isLabeledPeripheral = isLabeledPeripheralConnection({
127
+ inputProblem: this.inputProblem,
128
+ chipMap: this.chipMap,
129
+ pins: [p1, p2],
130
+ })
131
+ if (pairDistance > this.maxMspPairDistance && !isLabeledPeripheral) {
123
132
  // Too far apart; skip creating an MSP pair for this net
124
133
  return
125
134
  }
@@ -0,0 +1,44 @@
1
+ import type { InputChip, InputPin, InputProblem } from "lib/types/InputProblem"
2
+ import { getPinDirection } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver/getPinDirection"
3
+
4
+ type PinWithChipId = InputPin & { chipId: string }
5
+
6
+ export const isLabeledPeripheralConnection = ({
7
+ inputProblem,
8
+ chipMap,
9
+ pins,
10
+ }: {
11
+ inputProblem: InputProblem
12
+ chipMap: Record<string, InputChip>
13
+ pins: [PinWithChipId, PinWithChipId]
14
+ }): boolean => {
15
+ const [firstPin, secondPin] = pins
16
+ const directConnection = inputProblem.directConnections.find(
17
+ (connection) =>
18
+ connection.pinIds.includes(firstPin.pinId) &&
19
+ connection.pinIds.includes(secondPin.pinId),
20
+ )
21
+ if (directConnection?.netLabelWidth === undefined) return false
22
+
23
+ const firstChip = chipMap[firstPin.chipId]
24
+ const secondChip = chipMap[secondPin.chipId]
25
+ if (!firstChip || !secondChip) return false
26
+
27
+ const hasSinglePinPeripheral =
28
+ firstChip.pins.length === 1 || secondChip.pins.length === 1
29
+ if (!hasSinglePinPeripheral) return false
30
+
31
+ let firstFacingDirection = firstPin._facingDirection
32
+ if (!firstFacingDirection) {
33
+ firstFacingDirection = getPinDirection(firstPin, firstChip)
34
+ }
35
+ let secondFacingDirection = secondPin._facingDirection
36
+ if (!secondFacingDirection) {
37
+ secondFacingDirection = getPinDirection(secondPin, secondChip)
38
+ }
39
+
40
+ return (
41
+ (firstFacingDirection === "x-" && secondFacingDirection === "x+") ||
42
+ (firstFacingDirection === "x+" && secondFacingDirection === "x-")
43
+ )
44
+ }
@@ -140,6 +140,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
140
140
  instance.mspConnectionPairSolver!.mspConnectionPairs,
141
141
  alreadySolvedTraces:
142
142
  instance.schematicTraceLinesSolver!.solvedTracePaths,
143
+ failedConnectionPairs:
144
+ instance.schematicTraceLinesSolver!.failedConnectionPairs,
143
145
  },
144
146
  ],
145
147
  {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/schematic-trace-solver",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.113",
4
+ "version": "0.0.114",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260728T144234Z/bug-report-20260728T144234Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />
@@ -0,0 +1,4 @@
1
+ import { PipelineDebugger } from "site/components/PipelineDebugger"
2
+ import inputProblem from "../../tests/bug-reports/bug-report-20260728T225606Z/bug-report-20260728T225606Z.json"
3
+
4
+ export default () => <PipelineDebugger inputProblem={inputProblem as any} />