@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 +37 -2
- package/dist/index.js +201 -10
- package/lib/solvers/LongDistancePairSolver/LongDistancePairSolver.ts +238 -0
- package/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +31 -9
- package/lib/utils/does-trace-overlap-with-existing-traces.ts +33 -0
- package/package.json +1 -1
- package/site/examples/example25.page.tsx +4 -0
- package/tests/assets/example25.json +96 -0
- package/tests/examples/__snapshots__/example03.snap.svg +67 -76
- package/tests/examples/__snapshots__/example10.snap.svg +24 -30
- package/tests/examples/__snapshots__/example13.snap.svg +81 -84
- package/tests/examples/__snapshots__/example15.snap.svg +5 -8
- package/tests/examples/__snapshots__/example21.snap.svg +68 -74
- package/tests/examples/__snapshots__/example28.snap.svg +273 -0
- package/tests/examples/example28.test.ts +13 -0
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.
|
|
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.
|
|
3187
|
-
p.mspPairId,
|
|
3188
|
-
|
|
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.
|
|
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:
|
|
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.
|
|
127
|
-
p.mspPairId,
|
|
128
|
-
|
|
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
|
|
147
|
-
|
|
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
|
+
}
|