@tscircuit/schematic-trace-solver 0.0.40 → 0.0.41

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
@@ -404,6 +404,35 @@ declare class TraceLabelOverlapAvoidanceSolver extends BaseSolver {
404
404
  visualize(): GraphicsObject;
405
405
  }
406
406
 
407
+ declare class LongDistancePairSolver extends BaseSolver {
408
+ private params;
409
+ solvedLongDistanceTraces: SolvedTracePath[];
410
+ private queuedCandidatePairs;
411
+ private currentCandidatePair;
412
+ private subSolver;
413
+ private chipMap;
414
+ private inputProblem;
415
+ private netConnMap;
416
+ private newlyConnectedPinIds;
417
+ private allSolvedTraces;
418
+ constructor(params: {
419
+ inputProblem: InputProblem;
420
+ alreadySolvedTraces: SolvedTracePath[];
421
+ primaryMspConnectionPairs: MspConnectionPair[];
422
+ });
423
+ getConstructorParams(): {
424
+ inputProblem: InputProblem;
425
+ alreadySolvedTraces: SolvedTracePath[];
426
+ primaryMspConnectionPairs: MspConnectionPair[];
427
+ };
428
+ _step(): void;
429
+ visualize(): graphics_debug.GraphicsObject;
430
+ getOutput(): {
431
+ newTraces: SolvedTracePath[];
432
+ allTracesMerged: SolvedTracePath[];
433
+ };
434
+ }
435
+
407
436
  /**
408
437
  * Pipeline solver that runs a series of solvers to find the best schematic layout.
409
438
  * Coordinates the entire layout process from chip partitioning through final packing.
@@ -414,10 +443,16 @@ type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
414
443
  solverClass: T;
415
444
  getConstructorParams: (instance: SchematicTracePipelineSolver) => ConstructorParameters<T>;
416
445
  onSolved?: (instance: SchematicTracePipelineSolver) => void;
446
+ shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean;
417
447
  };
448
+ interface SchematicTracePipelineSolverParams {
449
+ inputProblem: InputProblem;
450
+ allowLongDistanceTraces?: boolean;
451
+ }
418
452
  declare class SchematicTracePipelineSolver extends BaseSolver {
419
453
  mspConnectionPairSolver?: MspConnectionPairSolver;
420
454
  schematicTraceLinesSolver?: SchematicTraceLinesSolver;
455
+ longDistancePairSolver?: LongDistancePairSolver;
421
456
  traceOverlapShiftSolver?: TraceOverlapShiftSolver;
422
457
  netLabelPlacementSolver?: NetLabelPlacementSolver;
423
458
  traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver;
@@ -426,7 +461,7 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
426
461
  timeSpentOnPhase: Record<string, number>;
427
462
  firstIterationOfPhase: Record<string, number>;
428
463
  inputProblem: InputProblem;
429
- pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver>)[];
464
+ pipelineDef: (PipelineStep<typeof MspConnectionPairSolver> | PipelineStep<typeof SchematicTraceLinesSolver> | PipelineStep<typeof LongDistancePairSolver> | PipelineStep<typeof TraceOverlapShiftSolver> | PipelineStep<typeof NetLabelPlacementSolver> | PipelineStep<typeof TraceLabelOverlapAvoidanceSolver>)[];
430
465
  constructor(inputProblem: InputProblem);
431
466
  getConstructorParams(): ConstructorParameters<typeof SchematicTracePipelineSolver>[0];
432
467
  currentPipelineStepIndex: number;
@@ -442,4 +477,4 @@ declare class SchematicTracePipelineSolver extends BaseSolver {
442
477
  preview(): GraphicsObject;
443
478
  }
444
479
 
445
- export { type ChipId, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type PinId, SchematicTracePipelineSolver, SchematicTraceSingleLineSolver2 };
480
+ export { type ChipId, type InputChip, type InputDirectConnection, type InputNetConnection, type InputPin, type InputProblem, type NetId, type PinId, SchematicTracePipelineSolver, type SchematicTracePipelineSolverParams, SchematicTraceSingleLineSolver2 };
package/dist/index.js CHANGED
@@ -3100,19 +3100,203 @@ var expandChipsToFitPins = (problem) => {
3100
3100
  }
3101
3101
  };
3102
3102
 
3103
+ // lib/utils/does-trace-overlap-with-existing-traces.ts
3104
+ import { doSegmentsIntersect } from "@tscircuit/math-utils";
3105
+ function doesTraceOverlapWithExistingTraces(newTracePath, existingTraces) {
3106
+ for (let i = 0; i < newTracePath.length - 1; i++) {
3107
+ const newSegmentP1 = newTracePath[i];
3108
+ const newSegmentP2 = newTracePath[i + 1];
3109
+ for (const existingTrace of existingTraces) {
3110
+ for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
3111
+ const existingSegmentP1 = existingTrace.tracePath[j];
3112
+ const existingSegmentP2 = existingTrace.tracePath[j + 1];
3113
+ if (doSegmentsIntersect(
3114
+ newSegmentP1,
3115
+ newSegmentP2,
3116
+ existingSegmentP1,
3117
+ existingSegmentP2
3118
+ )) {
3119
+ return true;
3120
+ }
3121
+ }
3122
+ }
3123
+ }
3124
+ return false;
3125
+ }
3126
+
3127
+ // lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts
3128
+ var NEAREST_NEIGHBOR_COUNT = 3;
3129
+ var distance = (p1, p2) => {
3130
+ return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2));
3131
+ };
3132
+ var LongDistancePairSolver = class extends BaseSolver {
3133
+ constructor(params) {
3134
+ super();
3135
+ this.params = params;
3136
+ const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } = this.params;
3137
+ this.inputProblem = inputProblem;
3138
+ this.allSolvedTraces = [...alreadySolvedTraces];
3139
+ const primaryConnectedPinIds = /* @__PURE__ */ new Set();
3140
+ for (const pair of primaryMspConnectionPairs) {
3141
+ primaryConnectedPinIds.add(pair.pins[0].pinId);
3142
+ primaryConnectedPinIds.add(pair.pins[1].pinId);
3143
+ }
3144
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem);
3145
+ this.netConnMap = netConnMap;
3146
+ const pinMap = /* @__PURE__ */ new Map();
3147
+ for (const chip of inputProblem.chips) {
3148
+ this.chipMap[chip.chipId] = chip;
3149
+ for (const pin of chip.pins) {
3150
+ pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId });
3151
+ }
3152
+ }
3153
+ const candidatePairs = [];
3154
+ const addedPairKeys = /* @__PURE__ */ new Set();
3155
+ for (const netId of Object.keys(netConnMap.netMap)) {
3156
+ const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId);
3157
+ if (allPinIdsInNet.length < 2) continue;
3158
+ const unconnectedPinIds = allPinIdsInNet.filter(
3159
+ (pinId) => !primaryConnectedPinIds.has(pinId)
3160
+ );
3161
+ for (const unconnectedPinId of unconnectedPinIds) {
3162
+ const sourcePin = pinMap.get(unconnectedPinId);
3163
+ if (!sourcePin) continue;
3164
+ const neighbors = allPinIdsInNet.filter((otherPinId) => otherPinId !== unconnectedPinId).flatMap((otherPinId) => {
3165
+ const targetPin = pinMap.get(otherPinId);
3166
+ if (!targetPin) return [];
3167
+ return [
3168
+ {
3169
+ pin: targetPin,
3170
+ distance: distance(sourcePin, targetPin)
3171
+ }
3172
+ ];
3173
+ }).sort((a, b) => a.distance - b.distance).slice(0, NEAREST_NEIGHBOR_COUNT);
3174
+ for (const neighbor of neighbors) {
3175
+ const pair = [sourcePin, neighbor.pin];
3176
+ const pairKey = pair.map((p) => p.pinId).sort().join("--");
3177
+ if (!addedPairKeys.has(pairKey)) {
3178
+ candidatePairs.push(pair);
3179
+ addedPairKeys.add(pairKey);
3180
+ }
3181
+ }
3182
+ }
3183
+ }
3184
+ this.queuedCandidatePairs = candidatePairs;
3185
+ }
3186
+ solvedLongDistanceTraces = [];
3187
+ queuedCandidatePairs = [];
3188
+ currentCandidatePair = null;
3189
+ subSolver = null;
3190
+ chipMap = {};
3191
+ inputProblem;
3192
+ netConnMap;
3193
+ newlyConnectedPinIds = /* @__PURE__ */ new Set();
3194
+ allSolvedTraces = [];
3195
+ getConstructorParams() {
3196
+ return this.params;
3197
+ }
3198
+ _step() {
3199
+ if (this.subSolver?.solved) {
3200
+ const newTracePath = this.subSolver.solvedTracePath;
3201
+ if (newTracePath && this.currentCandidatePair) {
3202
+ const isTraceClear = !doesTraceOverlapWithExistingTraces(
3203
+ newTracePath,
3204
+ this.allSolvedTraces
3205
+ );
3206
+ if (isTraceClear) {
3207
+ const [p1, p2] = this.currentCandidatePair;
3208
+ const globalConnNetId = this.netConnMap.getNetConnectedToId(p1.pinId);
3209
+ const mspPairId = `${p1.pinId}-${p2.pinId}`;
3210
+ const newSolvedTrace = {
3211
+ mspPairId,
3212
+ dcConnNetId: globalConnNetId,
3213
+ globalConnNetId,
3214
+ pins: [p1, p2],
3215
+ tracePath: newTracePath,
3216
+ mspConnectionPairIds: [mspPairId],
3217
+ pinIds: [p1.pinId, p2.pinId]
3218
+ };
3219
+ this.solvedLongDistanceTraces.push(newSolvedTrace);
3220
+ this.allSolvedTraces.push(newSolvedTrace);
3221
+ this.newlyConnectedPinIds.add(p1.pinId);
3222
+ this.newlyConnectedPinIds.add(p2.pinId);
3223
+ }
3224
+ }
3225
+ this.subSolver = null;
3226
+ this.currentCandidatePair = null;
3227
+ } else if (this.subSolver?.failed) {
3228
+ this.subSolver = null;
3229
+ this.currentCandidatePair = null;
3230
+ }
3231
+ if (this.subSolver) {
3232
+ this.subSolver.step();
3233
+ return;
3234
+ }
3235
+ while (this.queuedCandidatePairs.length > 0) {
3236
+ const nextPair = this.queuedCandidatePairs.shift();
3237
+ const [p1, p2] = nextPair;
3238
+ if (this.newlyConnectedPinIds.has(p1.pinId) || this.newlyConnectedPinIds.has(p2.pinId)) {
3239
+ continue;
3240
+ }
3241
+ this.currentCandidatePair = nextPair;
3242
+ this.subSolver = new SchematicTraceSingleLineSolver2({
3243
+ inputProblem: this.params.inputProblem,
3244
+ pins: this.currentCandidatePair,
3245
+ chipMap: this.chipMap
3246
+ });
3247
+ return;
3248
+ }
3249
+ this.solved = true;
3250
+ }
3251
+ visualize() {
3252
+ if (this.subSolver) {
3253
+ return this.subSolver.visualize();
3254
+ }
3255
+ const graphics = visualizeInputProblem(this.inputProblem);
3256
+ for (const trace of this.solvedLongDistanceTraces) {
3257
+ graphics.lines.push({
3258
+ points: trace.tracePath,
3259
+ strokeColor: "purple"
3260
+ });
3261
+ }
3262
+ for (const [p1, p2] of this.queuedCandidatePairs) {
3263
+ graphics.lines.push({
3264
+ points: [p1, p2],
3265
+ strokeColor: "gray",
3266
+ strokeDash: "4 4"
3267
+ });
3268
+ }
3269
+ return graphics;
3270
+ }
3271
+ getOutput() {
3272
+ if (!this.solved) {
3273
+ return { newTraces: [], allTracesMerged: this.params.alreadySolvedTraces };
3274
+ }
3275
+ return {
3276
+ newTraces: this.solvedLongDistanceTraces,
3277
+ allTracesMerged: [
3278
+ ...this.params.alreadySolvedTraces,
3279
+ ...this.solvedLongDistanceTraces
3280
+ ]
3281
+ };
3282
+ }
3283
+ };
3284
+
3103
3285
  // lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts
3104
3286
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
3105
3287
  return {
3106
3288
  solverName,
3107
3289
  solverClass,
3108
3290
  getConstructorParams,
3109
- onSolved: opts.onSolved
3291
+ onSolved: opts.onSolved,
3292
+ shouldSkip: opts.shouldSkip
3110
3293
  };
3111
3294
  }
3112
3295
  var SchematicTracePipelineSolver = class extends BaseSolver {
3113
3296
  mspConnectionPairSolver;
3114
3297
  // guidelinesSolver?: GuidelinesSolver
3115
3298
  schematicTraceLinesSolver;
3299
+ longDistancePairSolver;
3116
3300
  traceOverlapShiftSolver;
3117
3301
  netLabelPlacementSolver;
3118
3302
  traceLabelOverlapAvoidanceSolver;
@@ -3155,6 +3339,17 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3155
3339
  // guidelines: this.guidelinesSolver!.guidelines,
3156
3340
  chipMap: this.mspConnectionPairSolver.chipMap
3157
3341
  }
3342
+ ]
3343
+ ),
3344
+ definePipelineStep(
3345
+ "longDistancePairSolver",
3346
+ LongDistancePairSolver,
3347
+ (instance) => [
3348
+ {
3349
+ inputProblem: instance.inputProblem,
3350
+ primaryMspConnectionPairs: instance.mspConnectionPairSolver.mspConnectionPairs,
3351
+ alreadySolvedTraces: instance.schematicTraceLinesSolver.solvedTracePaths
3352
+ }
3158
3353
  ],
3159
3354
  {
3160
3355
  onSolved: (schematicTraceLinesSolver) => {
@@ -3167,7 +3362,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3167
3362
  () => [
3168
3363
  {
3169
3364
  inputProblem: this.inputProblem,
3170
- inputTracePaths: this.schematicTraceLinesSolver.solvedTracePaths,
3365
+ inputTracePaths: this.longDistancePairSolver?.getOutput().allTracesMerged,
3171
3366
  globalConnMap: this.mspConnectionPairSolver.globalConnMap
3172
3367
  }
3173
3368
  ],
@@ -3183,10 +3378,9 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3183
3378
  {
3184
3379
  inputProblem: this.inputProblem,
3185
3380
  inputTraceMap: this.traceOverlapShiftSolver?.correctedTraceMap ?? Object.fromEntries(
3186
- this.schematicTraceLinesSolver.solvedTracePaths.map((p) => [
3187
- p.mspPairId,
3188
- p
3189
- ])
3381
+ this.longDistancePairSolver.getOutput().allTracesMerged.map(
3382
+ (p) => [p.mspPairId, p]
3383
+ )
3190
3384
  )
3191
3385
  }
3192
3386
  ],
@@ -3200,10 +3394,7 @@ var SchematicTracePipelineSolver = class extends BaseSolver {
3200
3394
  TraceLabelOverlapAvoidanceSolver,
3201
3395
  (instance) => {
3202
3396
  const traceMap = instance.traceOverlapShiftSolver?.correctedTraceMap ?? Object.fromEntries(
3203
- instance.schematicTraceLinesSolver.solvedTracePaths.map((p) => [
3204
- p.mspPairId,
3205
- p
3206
- ])
3397
+ instance.longDistancePairSolver.getOutput().allTracesMerged.map((p) => [p.mspPairId, p])
3207
3398
  );
3208
3399
  const traces = Object.values(traceMap);
3209
3400
  const netLabelPlacements = instance.netLabelPlacementSolver.netLabelPlacements;
@@ -0,0 +1,238 @@
1
+ import { getConnectivityMapsFromInputProblem } from "lib/solvers/MspConnectionPairSolver/getConnectivityMapFromInputProblem"
2
+ import type { MspConnectionPair } from "lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver"
3
+ import type {
4
+ InputProblem,
5
+ InputPin,
6
+ PinId,
7
+ InputChip,
8
+ } from "lib/types/InputProblem"
9
+ import { BaseSolver } from "../BaseSolver/BaseSolver"
10
+ import { SchematicTraceSingleLineSolver2 } from "../SchematicTraceLinesSolver/SchematicTraceSingleLineSolver2/SchematicTraceSingleLineSolver2"
11
+ import { doesTraceOverlapWithExistingTraces } from "lib/utils/does-trace-overlap-with-existing-traces"
12
+ import { visualizeInputProblem } from "../SchematicTracePipelineSolver/visualizeInputProblem"
13
+ import type { SolvedTracePath } from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver"
14
+ import type { ConnectivityMap } from "connectivity-map"
15
+
16
+ const NEAREST_NEIGHBOR_COUNT = 3
17
+
18
+ const distance = (p1: InputPin, p2: InputPin) => {
19
+ return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2))
20
+ }
21
+
22
+ export class LongDistancePairSolver extends BaseSolver {
23
+ public solvedLongDistanceTraces: SolvedTracePath[] = []
24
+ private queuedCandidatePairs: Array<
25
+ [InputPin & { chipId: string }, InputPin & { chipId: string }]
26
+ > = []
27
+ private currentCandidatePair:
28
+ | [InputPin & { chipId: string }, InputPin & { chipId: string }]
29
+ | null = null
30
+ private subSolver: SchematicTraceSingleLineSolver2 | null = null
31
+ private chipMap: Record<string, InputChip> = {}
32
+ private inputProblem: InputProblem
33
+ private netConnMap: ConnectivityMap
34
+ private newlyConnectedPinIds = new Set<PinId>()
35
+ private allSolvedTraces: SolvedTracePath[] = []
36
+
37
+ constructor(
38
+ private params: {
39
+ inputProblem: InputProblem
40
+ alreadySolvedTraces: SolvedTracePath[]
41
+ primaryMspConnectionPairs: MspConnectionPair[]
42
+ },
43
+ ) {
44
+ super()
45
+
46
+ const { inputProblem, primaryMspConnectionPairs, alreadySolvedTraces } =
47
+ this.params
48
+
49
+ this.inputProblem = inputProblem
50
+ this.allSolvedTraces = [...alreadySolvedTraces]
51
+
52
+ // 1. Create initial maps and sets for efficient lookup
53
+ const primaryConnectedPinIds = new Set<PinId>()
54
+ for (const pair of primaryMspConnectionPairs) {
55
+ primaryConnectedPinIds.add(pair.pins[0].pinId)
56
+ primaryConnectedPinIds.add(pair.pins[1].pinId)
57
+ }
58
+
59
+ const { netConnMap } = getConnectivityMapsFromInputProblem(inputProblem)
60
+ this.netConnMap = netConnMap
61
+ const pinMap = new Map<PinId, InputPin & { chipId: string }>()
62
+ for (const chip of inputProblem.chips) {
63
+ this.chipMap[chip.chipId] = chip
64
+ for (const pin of chip.pins) {
65
+ pinMap.set(pin.pinId, { ...pin, chipId: chip.chipId })
66
+ }
67
+ }
68
+
69
+ // 2. Generate candidate pairs using N-Nearest-Neighbors approach
70
+ const candidatePairs: Array<
71
+ [InputPin & { chipId: string }, InputPin & { chipId: string }]
72
+ > = []
73
+ const addedPairKeys = new Set<string>()
74
+
75
+ for (const netId of Object.keys(netConnMap.netMap)) {
76
+ const allPinIdsInNet = netConnMap.getIdsConnectedToNet(netId)
77
+ if (allPinIdsInNet.length < 2) continue
78
+
79
+ const unconnectedPinIds = allPinIdsInNet.filter(
80
+ (pinId) => !primaryConnectedPinIds.has(pinId),
81
+ )
82
+
83
+ for (const unconnectedPinId of unconnectedPinIds) {
84
+ const sourcePin = pinMap.get(unconnectedPinId)
85
+ if (!sourcePin) continue
86
+
87
+ const neighbors = allPinIdsInNet
88
+ .filter((otherPinId) => otherPinId !== unconnectedPinId)
89
+ .flatMap((otherPinId) => {
90
+ const targetPin = pinMap.get(otherPinId)
91
+ if (!targetPin) return [] // Gracefully handle missing pins
92
+ return [
93
+ {
94
+ pin: targetPin,
95
+ distance: distance(sourcePin, targetPin),
96
+ },
97
+ ]
98
+ })
99
+ .sort((a, b) => a.distance - b.distance)
100
+ .slice(0, NEAREST_NEIGHBOR_COUNT)
101
+
102
+ for (const neighbor of neighbors) {
103
+ const pair: [
104
+ InputPin & { chipId: string },
105
+ InputPin & { chipId: string },
106
+ ] = [sourcePin, neighbor.pin]
107
+ const pairKey = pair
108
+ .map((p) => p.pinId)
109
+ .sort()
110
+ .join("--")
111
+
112
+ if (!addedPairKeys.has(pairKey)) {
113
+ candidatePairs.push(pair)
114
+ addedPairKeys.add(pairKey)
115
+ }
116
+ }
117
+ }
118
+ }
119
+ this.queuedCandidatePairs = candidatePairs
120
+ }
121
+
122
+ override getConstructorParams() {
123
+ return this.params
124
+ }
125
+
126
+ override _step() {
127
+ // 1. Check if a sub-solver has finished and process its result
128
+ if (this.subSolver?.solved) {
129
+ const newTracePath = this.subSolver.solvedTracePath
130
+ if (newTracePath && this.currentCandidatePair) {
131
+ const isTraceClear = !doesTraceOverlapWithExistingTraces(
132
+ newTracePath,
133
+ this.allSolvedTraces,
134
+ )
135
+
136
+ if (isTraceClear) {
137
+ const [p1, p2] = this.currentCandidatePair
138
+ const globalConnNetId = this.netConnMap.getNetConnectedToId(p1.pinId)!
139
+ const mspPairId = `${p1.pinId}-${p2.pinId}`
140
+
141
+ const newSolvedTrace: SolvedTracePath = {
142
+ mspPairId,
143
+ dcConnNetId: globalConnNetId,
144
+ globalConnNetId,
145
+ pins: [p1, p2],
146
+ tracePath: newTracePath,
147
+ mspConnectionPairIds: [mspPairId],
148
+ pinIds: [p1.pinId, p2.pinId],
149
+ }
150
+
151
+ this.solvedLongDistanceTraces.push(newSolvedTrace)
152
+ this.allSolvedTraces.push(newSolvedTrace)
153
+
154
+ this.newlyConnectedPinIds.add(p1.pinId)
155
+ this.newlyConnectedPinIds.add(p2.pinId)
156
+ }
157
+ }
158
+ this.subSolver = null
159
+ this.currentCandidatePair = null
160
+ } else if (this.subSolver?.failed) {
161
+ this.subSolver = null
162
+ this.currentCandidatePair = null
163
+ }
164
+
165
+ // 2. If a sub-solver is already running, let it continue
166
+ if (this.subSolver) {
167
+ this.subSolver.step()
168
+ return
169
+ }
170
+
171
+ // 3. Find the next valid candidate pair and start a new sub-solver
172
+ while (this.queuedCandidatePairs.length > 0) {
173
+ const nextPair = this.queuedCandidatePairs.shift()!
174
+ const [p1, p2] = nextPair
175
+
176
+ if (
177
+ this.newlyConnectedPinIds.has(p1.pinId) ||
178
+ this.newlyConnectedPinIds.has(p2.pinId)
179
+ ) {
180
+ continue
181
+ }
182
+
183
+ this.currentCandidatePair = nextPair
184
+ this.subSolver = new SchematicTraceSingleLineSolver2({
185
+ inputProblem: this.params.inputProblem,
186
+ pins: this.currentCandidatePair,
187
+ chipMap: this.chipMap,
188
+ })
189
+ return
190
+ }
191
+
192
+ // 4. If we've exited the loop, there are no more valid pairs to process
193
+ this.solved = true
194
+ }
195
+
196
+ override visualize() {
197
+ if (this.subSolver) {
198
+ return this.subSolver.visualize()
199
+ }
200
+
201
+ const graphics = visualizeInputProblem(this.inputProblem)
202
+
203
+ // Draw solved long-distance traces
204
+ for (const trace of this.solvedLongDistanceTraces) {
205
+ graphics.lines!.push({
206
+ points: trace.tracePath,
207
+ strokeColor: "purple",
208
+ })
209
+ }
210
+
211
+ // Draw queued candidate pairs
212
+ for (const [p1, p2] of this.queuedCandidatePairs) {
213
+ graphics.lines!.push({
214
+ points: [p1, p2],
215
+ strokeColor: "gray",
216
+ strokeDash: "4 4",
217
+ })
218
+ }
219
+
220
+ return graphics
221
+ }
222
+
223
+ public getOutput(): {
224
+ newTraces: SolvedTracePath[]
225
+ allTracesMerged: SolvedTracePath[]
226
+ } {
227
+ if (!this.solved) {
228
+ return { newTraces: [], allTracesMerged: this.params.alreadySolvedTraces }
229
+ }
230
+ return {
231
+ newTraces: this.solvedLongDistanceTraces,
232
+ allTracesMerged: [
233
+ ...this.params.alreadySolvedTraces,
234
+ ...this.solvedLongDistanceTraces,
235
+ ],
236
+ }
237
+ }
238
+ }
@@ -16,6 +16,7 @@ import { TraceLabelOverlapAvoidanceSolver } from "../TraceLabelOverlapAvoidanceS
16
16
  import { getInputChipBounds } from "../GuidelinesSolver/getInputChipBounds"
17
17
  import { correctPinsInsideChips } from "./correctPinsInsideChip"
18
18
  import { expandChipsToFitPins } from "./expandChipsToFitPins"
19
+ import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePairSolver"
19
20
 
20
21
  type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
21
22
  solverName: string
@@ -24,6 +25,7 @@ type PipelineStep<T extends new (...args: any[]) => BaseSolver> = {
24
25
  instance: SchematicTracePipelineSolver,
25
26
  ) => ConstructorParameters<T>
26
27
  onSolved?: (instance: SchematicTracePipelineSolver) => void
28
+ shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean
27
29
  }
28
30
 
29
31
  function definePipelineStep<
@@ -37,6 +39,7 @@ function definePipelineStep<
37
39
  getConstructorParams: (instance: SchematicTracePipelineSolver) => P,
38
40
  opts: {
39
41
  onSolved?: (instance: SchematicTracePipelineSolver) => void
42
+ shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean
40
43
  } = {},
41
44
  ): PipelineStep<T> {
42
45
  return {
@@ -44,13 +47,20 @@ function definePipelineStep<
44
47
  solverClass,
45
48
  getConstructorParams,
46
49
  onSolved: opts.onSolved,
50
+ shouldSkip: opts.shouldSkip,
47
51
  }
48
52
  }
49
53
 
54
+ export interface SchematicTracePipelineSolverParams {
55
+ inputProblem: InputProblem
56
+ allowLongDistanceTraces?: boolean
57
+ }
58
+
50
59
  export class SchematicTracePipelineSolver extends BaseSolver {
51
60
  mspConnectionPairSolver?: MspConnectionPairSolver
52
61
  // guidelinesSolver?: GuidelinesSolver
53
62
  schematicTraceLinesSolver?: SchematicTraceLinesSolver
63
+ longDistancePairSolver?: LongDistancePairSolver
54
64
  traceOverlapShiftSolver?: TraceOverlapShiftSolver
55
65
  netLabelPlacementSolver?: NetLabelPlacementSolver
56
66
  traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver
@@ -96,6 +106,19 @@ export class SchematicTracePipelineSolver extends BaseSolver {
96
106
  chipMap: this.mspConnectionPairSolver!.chipMap,
97
107
  },
98
108
  ],
109
+ ),
110
+ definePipelineStep(
111
+ "longDistancePairSolver",
112
+ LongDistancePairSolver,
113
+ (instance) => [
114
+ {
115
+ inputProblem: instance.inputProblem,
116
+ primaryMspConnectionPairs:
117
+ instance.mspConnectionPairSolver!.mspConnectionPairs,
118
+ alreadySolvedTraces:
119
+ instance.schematicTraceLinesSolver!.solvedTracePaths,
120
+ },
121
+ ],
99
122
  {
100
123
  onSolved: (schematicTraceLinesSolver) => {},
101
124
  },
@@ -106,7 +129,8 @@ export class SchematicTracePipelineSolver extends BaseSolver {
106
129
  () => [
107
130
  {
108
131
  inputProblem: this.inputProblem,
109
- inputTracePaths: this.schematicTraceLinesSolver!.solvedTracePaths,
132
+ inputTracePaths:
133
+ this.longDistancePairSolver?.getOutput().allTracesMerged!,
110
134
  globalConnMap: this.mspConnectionPairSolver!.globalConnMap,
111
135
  },
112
136
  ],
@@ -123,10 +147,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
123
147
  inputTraceMap:
124
148
  this.traceOverlapShiftSolver?.correctedTraceMap ??
125
149
  Object.fromEntries(
126
- this.schematicTraceLinesSolver!.solvedTracePaths.map((p) => [
127
- p.mspPairId,
128
- p,
129
- ]),
150
+ this.longDistancePairSolver!.getOutput().allTracesMerged.map(
151
+ (p) => [p.mspPairId, p],
152
+ ),
130
153
  ),
131
154
  },
132
155
  ],
@@ -143,10 +166,9 @@ export class SchematicTracePipelineSolver extends BaseSolver {
143
166
  const traceMap =
144
167
  instance.traceOverlapShiftSolver?.correctedTraceMap ??
145
168
  Object.fromEntries(
146
- instance.schematicTraceLinesSolver!.solvedTracePaths.map((p) => [
147
- p.mspPairId,
148
- p,
149
- ]),
169
+ instance
170
+ .longDistancePairSolver!.getOutput()
171
+ .allTracesMerged.map((p) => [p.mspPairId, p]),
150
172
  )
151
173
  const traces = Object.values(traceMap)
152
174
  const netLabelPlacements =
@@ -0,0 +1,33 @@
1
+ import type { Point } from "@tscircuit/math-utils"
2
+ import type { SolvedTracePath } from "../solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver"
3
+ import { doSegmentsIntersect } from "@tscircuit/math-utils"
4
+
5
+ export function doesTraceOverlapWithExistingTraces(
6
+ newTracePath: Point[],
7
+ existingTraces: SolvedTracePath[],
8
+ ): boolean {
9
+ for (let i = 0; i < newTracePath.length - 1; i++) {
10
+ const newSegmentP1 = newTracePath[i]
11
+ const newSegmentP2 = newTracePath[i + 1]
12
+
13
+ for (const existingTrace of existingTraces) {
14
+ for (let j = 0; j < existingTrace.tracePath.length - 1; j++) {
15
+ const existingSegmentP1 = existingTrace.tracePath[j]
16
+ const existingSegmentP2 = existingTrace.tracePath[j + 1]
17
+
18
+ if (
19
+ doSegmentsIntersect(
20
+ newSegmentP1,
21
+ newSegmentP2,
22
+ existingSegmentP1,
23
+ existingSegmentP2,
24
+ )
25
+ ) {
26
+ return true // Found an intersection
27
+ }
28
+ }
29
+ }
30
+ }
31
+
32
+ return false // No intersections found
33
+ }