@tscircuit/capacity-autorouter 0.0.19 → 0.0.20
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 +220 -1
- package/dist/index.js +1681 -1231
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -43,7 +43,9 @@ var BaseSolver = class {
|
|
|
43
43
|
iterations = 0;
|
|
44
44
|
progress = 0;
|
|
45
45
|
error = null;
|
|
46
|
+
activeSubSolver;
|
|
46
47
|
failedSubSolvers;
|
|
48
|
+
timeToSolve;
|
|
47
49
|
/** DO NOT OVERRIDE! Override _step() instead */
|
|
48
50
|
step() {
|
|
49
51
|
if (this.solved) return;
|
|
@@ -66,9 +68,12 @@ var BaseSolver = class {
|
|
|
66
68
|
_step() {
|
|
67
69
|
}
|
|
68
70
|
solve() {
|
|
71
|
+
const startTime = Date.now();
|
|
69
72
|
while (!this.solved && !this.failed) {
|
|
70
73
|
this.step();
|
|
71
74
|
}
|
|
75
|
+
const endTime = Date.now();
|
|
76
|
+
this.timeToSolve = endTime - startTime;
|
|
72
77
|
}
|
|
73
78
|
visualize() {
|
|
74
79
|
return {
|
|
@@ -3347,817 +3352,131 @@ var CapacityNodeTargetMerger = class extends BaseSolver {
|
|
|
3347
3352
|
}
|
|
3348
3353
|
};
|
|
3349
3354
|
|
|
3350
|
-
// lib/
|
|
3351
|
-
var
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
for (const { connectionName: aConnName, point: A } of portPoints) {
|
|
3358
|
-
if (pointPairs.some((p) => p.connectionName === aConnName)) {
|
|
3359
|
-
continue;
|
|
3360
|
-
}
|
|
3361
|
-
if (transitionPairPoints.some((p) => p.connectionName === aConnName)) {
|
|
3362
|
-
continue;
|
|
3363
|
-
}
|
|
3364
|
-
const pointPair = {
|
|
3365
|
-
connectionName: aConnName,
|
|
3366
|
-
z: A.z,
|
|
3367
|
-
points: [A]
|
|
3368
|
-
};
|
|
3369
|
-
for (const { connectionName: bConnName, point: B } of portPoints) {
|
|
3370
|
-
if (aConnName !== bConnName) continue;
|
|
3371
|
-
if (A === B) continue;
|
|
3372
|
-
pointPair.points.push(B);
|
|
3373
|
-
}
|
|
3374
|
-
if (pointPair.points.some((p) => p.z !== pointPair.z)) {
|
|
3375
|
-
numEntryExitLayerChanges++;
|
|
3376
|
-
transitionPairPoints.push(pointPair);
|
|
3377
|
-
continue;
|
|
3378
|
-
}
|
|
3379
|
-
pointPairs.push(pointPair);
|
|
3355
|
+
// lib/solvers/NetToPointPairsSolver/buildMinimumSpanningTree.ts
|
|
3356
|
+
var KDNode = class {
|
|
3357
|
+
point;
|
|
3358
|
+
left = null;
|
|
3359
|
+
right = null;
|
|
3360
|
+
constructor(point) {
|
|
3361
|
+
this.point = point;
|
|
3380
3362
|
}
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
pair1.points[1],
|
|
3388
|
-
pair2.points[0],
|
|
3389
|
-
pair2.points[1]
|
|
3390
|
-
)) {
|
|
3391
|
-
numSameLayerCrossings++;
|
|
3392
|
-
}
|
|
3363
|
+
};
|
|
3364
|
+
var KDTree = class {
|
|
3365
|
+
root = null;
|
|
3366
|
+
constructor(points) {
|
|
3367
|
+
if (points.length > 0) {
|
|
3368
|
+
this.root = this.buildTree(points, 0);
|
|
3393
3369
|
}
|
|
3394
3370
|
}
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
pair1.points[1],
|
|
3403
|
-
pair2.points[0],
|
|
3404
|
-
pair2.points[1]
|
|
3405
|
-
)) {
|
|
3406
|
-
numTransitionCrossings++;
|
|
3407
|
-
}
|
|
3371
|
+
buildTree(points, depth) {
|
|
3372
|
+
const axis = depth % 2 === 0 ? "x" : "y";
|
|
3373
|
+
points.sort((a, b) => a[axis] - b[axis]);
|
|
3374
|
+
const medianIndex = Math.floor(points.length / 2);
|
|
3375
|
+
const node = new KDNode(points[medianIndex]);
|
|
3376
|
+
if (medianIndex > 0) {
|
|
3377
|
+
node.left = this.buildTree(points.slice(0, medianIndex), depth + 1);
|
|
3408
3378
|
}
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
for (let j = 0; j < pointPairs.length; j++) {
|
|
3412
|
-
const pair1 = transitionPairPoints[i];
|
|
3413
|
-
const pair2 = pointPairs[j];
|
|
3414
|
-
if (doSegmentsIntersect(
|
|
3415
|
-
pair1.points[0],
|
|
3416
|
-
pair1.points[1],
|
|
3417
|
-
pair2.points[0],
|
|
3418
|
-
pair2.points[1]
|
|
3419
|
-
)) {
|
|
3420
|
-
numTransitionCrossings++;
|
|
3421
|
-
}
|
|
3379
|
+
if (medianIndex < points.length - 1) {
|
|
3380
|
+
node.right = this.buildTree(points.slice(medianIndex + 1), depth + 1);
|
|
3422
3381
|
}
|
|
3382
|
+
return node;
|
|
3423
3383
|
}
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
};
|
|
3429
|
-
};
|
|
3430
|
-
|
|
3431
|
-
// lib/solvers/CapacitySegmentPointOptimizer/CapacitySegmentPointOptimizer.ts
|
|
3432
|
-
var CapacitySegmentPointOptimizer = class extends BaseSolver {
|
|
3433
|
-
assignedSegments;
|
|
3434
|
-
colorMap;
|
|
3435
|
-
nodeMap;
|
|
3436
|
-
nodeIdToSegmentIds;
|
|
3437
|
-
segmentIdToNodeIds;
|
|
3438
|
-
currentMutatedSegments;
|
|
3439
|
-
allSegmentIds;
|
|
3440
|
-
lastAppliedOperation = null;
|
|
3441
|
-
lastCreatedOperation = null;
|
|
3442
|
-
currentNodeCosts;
|
|
3443
|
-
lastAcceptedIteration = 0;
|
|
3444
|
-
currentCost;
|
|
3445
|
-
randomSeed;
|
|
3446
|
-
numNodes;
|
|
3447
|
-
probabilityOfFailure;
|
|
3448
|
-
nodesThatCantFitVias;
|
|
3449
|
-
mutableSegments;
|
|
3450
|
-
VIA_DIAMETER = 0.6;
|
|
3451
|
-
OBSTACLE_MARGIN = 0.15;
|
|
3452
|
-
MAX_OPERATIONS_PER_MUTATION = 5;
|
|
3453
|
-
MAX_NODE_CHAIN_PER_MUTATION = 2;
|
|
3454
|
-
NOOP_ITERATIONS_BEFORE_EARLY_STOP = 2e4;
|
|
3455
|
-
// We use an extra property on segments to remember assigned points.
|
|
3456
|
-
// Each segment will get an added property "assignedPoints" which is an array of:
|
|
3457
|
-
// { connectionName: string, point: {x: number, y: number } }
|
|
3458
|
-
// This is a temporary extension used by the solver.
|
|
3459
|
-
constructor({
|
|
3460
|
-
assignedSegments,
|
|
3461
|
-
colorMap,
|
|
3462
|
-
nodes
|
|
3463
|
-
}) {
|
|
3464
|
-
super();
|
|
3465
|
-
this.MAX_ITERATIONS = 5e5;
|
|
3466
|
-
this.assignedSegments = assignedSegments;
|
|
3467
|
-
const dedupedSegments = [];
|
|
3468
|
-
const dedupedSegPointMap = /* @__PURE__ */ new Map();
|
|
3469
|
-
let highestSegmentId = -1;
|
|
3470
|
-
for (const seg of this.assignedSegments) {
|
|
3471
|
-
const segKey = `${seg.start.x}-${seg.start.y}-${seg.end.x}-${seg.end.y}`;
|
|
3472
|
-
const existingSeg = dedupedSegPointMap.get(segKey);
|
|
3473
|
-
if (!existingSeg) {
|
|
3474
|
-
highestSegmentId++;
|
|
3475
|
-
seg.nodePortSegmentId = `SEG${highestSegmentId}`;
|
|
3476
|
-
dedupedSegPointMap.set(segKey, seg);
|
|
3477
|
-
dedupedSegments.push(seg);
|
|
3478
|
-
continue;
|
|
3479
|
-
}
|
|
3480
|
-
seg.nodePortSegmentId = existingSeg.nodePortSegmentId;
|
|
3481
|
-
}
|
|
3482
|
-
this.currentMutatedSegments = /* @__PURE__ */ new Map();
|
|
3483
|
-
for (const seg of dedupedSegments) {
|
|
3484
|
-
this.currentMutatedSegments.set(seg.nodePortSegmentId, {
|
|
3485
|
-
...seg,
|
|
3486
|
-
assignedPoints: seg.assignedPoints?.map((p) => ({
|
|
3487
|
-
...p,
|
|
3488
|
-
point: { x: p.point.x, y: p.point.y, z: p.point.z }
|
|
3489
|
-
}))
|
|
3490
|
-
});
|
|
3491
|
-
}
|
|
3492
|
-
this.nodeIdToSegmentIds = /* @__PURE__ */ new Map();
|
|
3493
|
-
this.segmentIdToNodeIds = /* @__PURE__ */ new Map();
|
|
3494
|
-
for (const segment of this.assignedSegments) {
|
|
3495
|
-
this.segmentIdToNodeIds.set(segment.nodePortSegmentId, [
|
|
3496
|
-
...this.segmentIdToNodeIds.get(segment.nodePortSegmentId) ?? [],
|
|
3497
|
-
segment.capacityMeshNodeId
|
|
3498
|
-
]);
|
|
3499
|
-
this.nodeIdToSegmentIds.set(segment.capacityMeshNodeId, [
|
|
3500
|
-
...this.nodeIdToSegmentIds.get(segment.capacityMeshNodeId) ?? [],
|
|
3501
|
-
segment.nodePortSegmentId
|
|
3502
|
-
]);
|
|
3384
|
+
// Find the nearest neighbor to a query point
|
|
3385
|
+
findNearestNeighbor(queryPoint) {
|
|
3386
|
+
if (!this.root) {
|
|
3387
|
+
throw new Error("Tree is empty");
|
|
3503
3388
|
}
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3389
|
+
const best = this.root.point;
|
|
3390
|
+
const bestDistance = this.distance(queryPoint, best);
|
|
3391
|
+
this.nearestNeighborSearch(this.root, queryPoint, 0, best, bestDistance);
|
|
3392
|
+
return best;
|
|
3393
|
+
}
|
|
3394
|
+
nearestNeighborSearch(node, queryPoint, depth, best, bestDistance) {
|
|
3395
|
+
if (!node) {
|
|
3396
|
+
return best;
|
|
3508
3397
|
}
|
|
3509
|
-
|
|
3510
|
-
const
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
this.randomSeed = 1;
|
|
3515
|
-
this.allSegmentIds = Array.from(this.currentMutatedSegments.keys());
|
|
3516
|
-
this.nodesThatCantFitVias = /* @__PURE__ */ new Set();
|
|
3517
|
-
for (const nodeId of this.nodeIdToSegmentIds.keys()) {
|
|
3518
|
-
const node = this.nodeMap.get(nodeId);
|
|
3519
|
-
if (node.width < this.VIA_DIAMETER + this.OBSTACLE_MARGIN) {
|
|
3520
|
-
this.nodesThatCantFitVias.add(nodeId);
|
|
3521
|
-
}
|
|
3398
|
+
const axis = depth % 2 ? "x" : "y";
|
|
3399
|
+
const currentDistance = this.distance(queryPoint, node.point);
|
|
3400
|
+
if (currentDistance < bestDistance) {
|
|
3401
|
+
best = node.point;
|
|
3402
|
+
bestDistance = currentDistance;
|
|
3522
3403
|
}
|
|
3523
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
computeNodeCost(nodeId) {
|
|
3533
|
-
const node = this.nodeMap.get(nodeId);
|
|
3534
|
-
if (node?._containsTarget) return 0;
|
|
3535
|
-
const totalCapacity = getTunedTotalCapacity1(node);
|
|
3536
|
-
const usedViaCapacity = this.getUsedViaCapacity(nodeId);
|
|
3537
|
-
const usedTraceCapacity = this.getUsedTraceCapacity(nodeId);
|
|
3538
|
-
const approxProb = usedViaCapacity * usedTraceCapacity / totalCapacity ** 2;
|
|
3539
|
-
const K = -2.3;
|
|
3540
|
-
return 1 - Math.exp(approxProb * K);
|
|
3541
|
-
}
|
|
3542
|
-
/**
|
|
3543
|
-
* Number of traces that can go through this node if they are completely
|
|
3544
|
-
* straight without crossings
|
|
3545
|
-
*/
|
|
3546
|
-
getUsedTraceCapacity(nodeId) {
|
|
3547
|
-
const segmentIds = this.nodeIdToSegmentIds.get(nodeId);
|
|
3548
|
-
const segments = segmentIds.map(
|
|
3549
|
-
(segmentId) => this.currentMutatedSegments.get(segmentId)
|
|
3550
|
-
);
|
|
3551
|
-
const points = segments.flatMap((s) => s.assignedPoints);
|
|
3552
|
-
const numTracesThroughNode = points.length / 2;
|
|
3553
|
-
const numLayers = 2;
|
|
3554
|
-
return numTracesThroughNode / numLayers;
|
|
3555
|
-
}
|
|
3556
|
-
/**
|
|
3557
|
-
* Granular via capacity is a consideration of capacity that includes...
|
|
3558
|
-
* - The number of traces
|
|
3559
|
-
* - The number of trace crossings (0-2 vias per trace crossing)
|
|
3560
|
-
* - Empirically, each crossing typically results in 0.82 vias
|
|
3561
|
-
* - e.g. 17 traces would typically have 51 crossings & 42 vias
|
|
3562
|
-
* - The number of layer changes (at least 1 via per layer change)
|
|
3563
|
-
* - We don't know how a entry/exit being on separated layers effects
|
|
3564
|
-
* the capacity/number of vias yet
|
|
3565
|
-
*
|
|
3566
|
-
* - Generally minimizing the number of crossings is pretty good, if there
|
|
3567
|
-
* is no trace crossing you basically don't have any used capacity
|
|
3568
|
-
* - If the entry/exit layer is different, you're guaranteed to have at least
|
|
3569
|
-
* one via
|
|
3570
|
-
*
|
|
3571
|
-
* - Total capacity is computed by estimating the number of vias that could
|
|
3572
|
-
* be created using the formula (viaFitAcross / 2) ** 1.1
|
|
3573
|
-
*/
|
|
3574
|
-
getUsedViaCapacity(nodeId) {
|
|
3575
|
-
const segmentIds = this.nodeIdToSegmentIds.get(nodeId);
|
|
3576
|
-
const segments = segmentIds.map(
|
|
3577
|
-
(segmentId) => this.currentMutatedSegments.get(segmentId)
|
|
3404
|
+
const axisDiff = queryPoint[axis] - node.point[axis];
|
|
3405
|
+
const firstBranch = axisDiff <= 0 ? node.left : node.right;
|
|
3406
|
+
const secondBranch = axisDiff <= 0 ? node.right : node.left;
|
|
3407
|
+
best = this.nearestNeighborSearch(
|
|
3408
|
+
firstBranch,
|
|
3409
|
+
queryPoint,
|
|
3410
|
+
depth + 1,
|
|
3411
|
+
best,
|
|
3412
|
+
bestDistance
|
|
3578
3413
|
);
|
|
3579
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
}
|
|
3588
|
-
getRandomWeightedNodeId() {
|
|
3589
|
-
const nodeIdsWithCosts = [...this.currentNodeCosts.entries()].filter(([nodeId, cost]) => cost > 1e-5).filter(([nodeId]) => !this.nodeMap.get(nodeId)?._containsTarget);
|
|
3590
|
-
if (nodeIdsWithCosts.length === 0) {
|
|
3591
|
-
console.error(
|
|
3592
|
-
"No nodes with cost > 0.00001 (why are you even running this solver)"
|
|
3593
|
-
);
|
|
3594
|
-
return this.currentNodeCosts.keys().next().value;
|
|
3595
|
-
}
|
|
3596
|
-
const totalCost = nodeIdsWithCosts.reduce((acc, [, cost]) => acc + cost, 0);
|
|
3597
|
-
const randomValue = this.random() * totalCost;
|
|
3598
|
-
let cumulativeCost = 0;
|
|
3599
|
-
for (let i = 0; i < nodeIdsWithCosts.length; i++) {
|
|
3600
|
-
const [nodeId, cost] = nodeIdsWithCosts[i];
|
|
3601
|
-
cumulativeCost += cost;
|
|
3602
|
-
if (cumulativeCost >= randomValue) {
|
|
3603
|
-
return nodeId;
|
|
3604
|
-
}
|
|
3605
|
-
}
|
|
3606
|
-
throw new Error("RANDOM SELECTION FAILURE FOR NODES (this is a bug)");
|
|
3607
|
-
}
|
|
3608
|
-
getRandomWeightedSegmentId() {
|
|
3609
|
-
const nodeId = this.getRandomWeightedNodeId();
|
|
3610
|
-
const segmentsIds = this.nodeIdToSegmentIds.get(nodeId).filter((s) => this.isSegmentMutable(s));
|
|
3611
|
-
return segmentsIds[Math.floor(this.random() * segmentsIds.length)];
|
|
3612
|
-
}
|
|
3613
|
-
getMutableSegments() {
|
|
3614
|
-
const mutableSegments = /* @__PURE__ */ new Set();
|
|
3615
|
-
for (const segmentId of this.currentMutatedSegments.keys()) {
|
|
3616
|
-
const segment = this.currentMutatedSegments.get(segmentId);
|
|
3617
|
-
const nodes = this.segmentIdToNodeIds.get(segmentId);
|
|
3618
|
-
const isMutable = nodes.every(
|
|
3619
|
-
(nodeId) => !this.nodeMap.get(nodeId)?._containsTarget
|
|
3414
|
+
bestDistance = this.distance(queryPoint, best);
|
|
3415
|
+
if (Math.abs(axisDiff) < bestDistance) {
|
|
3416
|
+
best = this.nearestNeighborSearch(
|
|
3417
|
+
secondBranch,
|
|
3418
|
+
queryPoint,
|
|
3419
|
+
depth + 1,
|
|
3420
|
+
best,
|
|
3421
|
+
bestDistance
|
|
3620
3422
|
);
|
|
3621
|
-
if (isMutable) {
|
|
3622
|
-
mutableSegments.add(segmentId);
|
|
3623
|
-
}
|
|
3624
3423
|
}
|
|
3625
|
-
return
|
|
3626
|
-
}
|
|
3627
|
-
isSegmentMutable(segmentId) {
|
|
3628
|
-
return this.mutableSegments.has(segmentId);
|
|
3424
|
+
return best;
|
|
3629
3425
|
}
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
operationType = "changeLayer";
|
|
3635
|
-
}
|
|
3636
|
-
if (operationType === "switch") {
|
|
3637
|
-
const randomPointIndex1 = Math.floor(
|
|
3638
|
-
this.random() * segment.assignedPoints.length
|
|
3639
|
-
);
|
|
3640
|
-
let randomPointIndex2 = randomPointIndex1;
|
|
3641
|
-
while (randomPointIndex1 === randomPointIndex2) {
|
|
3642
|
-
randomPointIndex2 = Math.floor(
|
|
3643
|
-
this.random() * segment.assignedPoints.length
|
|
3644
|
-
);
|
|
3645
|
-
}
|
|
3646
|
-
return {
|
|
3647
|
-
op: "switch",
|
|
3648
|
-
segmentId: randomSegmentId,
|
|
3649
|
-
point1Index: randomPointIndex1,
|
|
3650
|
-
point2Index: randomPointIndex2
|
|
3651
|
-
};
|
|
3426
|
+
// Find k nearest neighbors
|
|
3427
|
+
findKNearestNeighbors(queryPoint, k) {
|
|
3428
|
+
if (!this.root) {
|
|
3429
|
+
return [];
|
|
3652
3430
|
}
|
|
3653
|
-
const
|
|
3654
|
-
|
|
3655
|
-
);
|
|
3656
|
-
const point = segment.assignedPoints[randomPointIndex];
|
|
3657
|
-
return {
|
|
3658
|
-
op: "changeLayer",
|
|
3659
|
-
segmentId: randomSegmentId,
|
|
3660
|
-
pointIndex: randomPointIndex,
|
|
3661
|
-
newLayer: point.point.z === 0 ? 1 : 0
|
|
3662
|
-
};
|
|
3431
|
+
const neighbors = [];
|
|
3432
|
+
this.kNearestNeighborSearch(this.root, queryPoint, 0, neighbors, k);
|
|
3433
|
+
return neighbors.sort((a, b) => a.distance - b.distance).slice(0, k).map((n) => n.point);
|
|
3663
3434
|
}
|
|
3664
|
-
|
|
3665
|
-
if (
|
|
3666
|
-
|
|
3667
|
-
const nodes = /* @__PURE__ */ new Set();
|
|
3668
|
-
for (const segmentId of segments) {
|
|
3669
|
-
const adjacentNodeIds = this.segmentIdToNodeIds.get(segmentId);
|
|
3670
|
-
for (const adjacentNodeId of adjacentNodeIds) {
|
|
3671
|
-
const ancestors = this.getNodesNearNode(adjacentNodeId, hops - 1);
|
|
3672
|
-
for (const ancestor of ancestors) {
|
|
3673
|
-
nodes.add(ancestor);
|
|
3674
|
-
}
|
|
3675
|
-
}
|
|
3435
|
+
kNearestNeighborSearch(node, queryPoint, depth, neighbors, k) {
|
|
3436
|
+
if (!node) {
|
|
3437
|
+
return;
|
|
3676
3438
|
}
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
const
|
|
3681
|
-
|
|
3682
|
-
|
|
3439
|
+
const axis = depth % 2 ? "x" : "y";
|
|
3440
|
+
const currentDistance = this.distance(queryPoint, node.point);
|
|
3441
|
+
neighbors.push({ point: node.point, distance: currentDistance });
|
|
3442
|
+
const axisDiff = queryPoint[axis] - node.point[axis];
|
|
3443
|
+
const firstBranch = axisDiff <= 0 ? node.left : node.right;
|
|
3444
|
+
const secondBranch = axisDiff <= 0 ? node.right : node.left;
|
|
3445
|
+
this.kNearestNeighborSearch(
|
|
3446
|
+
firstBranch,
|
|
3447
|
+
queryPoint,
|
|
3448
|
+
depth + 1,
|
|
3449
|
+
neighbors,
|
|
3450
|
+
k
|
|
3683
3451
|
);
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
const randomSegmentId = adjacentSegments[Math.floor(this.random() * adjacentSegments.length)];
|
|
3689
|
-
const newOp = this.getRandomOperationForSegment(randomSegmentId);
|
|
3690
|
-
if (newOp) {
|
|
3691
|
-
subOperations.push(newOp);
|
|
3692
|
-
}
|
|
3452
|
+
let kthDistance = Infinity;
|
|
3453
|
+
if (neighbors.length >= k) {
|
|
3454
|
+
neighbors.sort((a, b) => a.distance - b.distance);
|
|
3455
|
+
kthDistance = neighbors[k - 1]?.distance || Infinity;
|
|
3693
3456
|
}
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
* operations
|
|
3703
|
-
*/
|
|
3704
|
-
getRandomCombinedOperationOnSingleNode(max = 7) {
|
|
3705
|
-
const numSubOperations = max === 1 ? 1 : Math.floor(this.random() * max) + 1;
|
|
3706
|
-
const subOperations = [];
|
|
3707
|
-
const nodeId = this.getRandomWeightedNodeId();
|
|
3708
|
-
const segmentsIds = this.nodeIdToSegmentIds.get(nodeId).filter((s) => this.isSegmentMutable(s));
|
|
3709
|
-
for (let i = 0; i < numSubOperations; i++) {
|
|
3710
|
-
const randomSegmentId = segmentsIds[Math.floor(this.random() * segmentsIds.length)];
|
|
3711
|
-
const newOp = this.getRandomOperationForSegment(randomSegmentId);
|
|
3712
|
-
if (newOp) {
|
|
3713
|
-
subOperations.push(newOp);
|
|
3714
|
-
}
|
|
3457
|
+
if (Math.abs(axisDiff) < kthDistance || neighbors.length < k) {
|
|
3458
|
+
this.kNearestNeighborSearch(
|
|
3459
|
+
secondBranch,
|
|
3460
|
+
queryPoint,
|
|
3461
|
+
depth + 1,
|
|
3462
|
+
neighbors,
|
|
3463
|
+
k
|
|
3464
|
+
);
|
|
3715
3465
|
}
|
|
3716
|
-
return {
|
|
3717
|
-
op: "combined",
|
|
3718
|
-
subOperations
|
|
3719
|
-
};
|
|
3720
3466
|
}
|
|
3721
|
-
|
|
3722
|
-
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
3733
|
-
|
|
3734
|
-
let logProbabilityOfSuccess = 0;
|
|
3735
|
-
let costSum = 0;
|
|
3736
|
-
const nodeCosts = /* @__PURE__ */ new Map();
|
|
3737
|
-
for (const nodeId of this.nodeIdToSegmentIds.keys()) {
|
|
3738
|
-
const nodeProbOfFailure = this.computeNodeCost(nodeId);
|
|
3739
|
-
nodeCosts.set(nodeId, nodeProbOfFailure);
|
|
3740
|
-
costSum += nodeProbOfFailure;
|
|
3741
|
-
if (nodeProbOfFailure < 1) {
|
|
3742
|
-
logProbabilityOfSuccess += Math.log(1 - nodeProbOfFailure);
|
|
3743
|
-
} else {
|
|
3744
|
-
logProbabilityOfSuccess = -Infinity;
|
|
3745
|
-
}
|
|
3746
|
-
}
|
|
3747
|
-
const probabilityOfSuccess = Math.exp(logProbabilityOfSuccess);
|
|
3748
|
-
const probabilityOfFailure = 1 - probabilityOfSuccess;
|
|
3749
|
-
const numNodes = this.nodeIdToSegmentIds.size;
|
|
3750
|
-
const linearizedCost = numNodes > 0 ? -logProbabilityOfSuccess / numNodes : 0;
|
|
3751
|
-
return {
|
|
3752
|
-
cost: linearizedCost,
|
|
3753
|
-
// Replace cost with linearized version
|
|
3754
|
-
nodeCosts,
|
|
3755
|
-
probabilityOfFailure,
|
|
3756
|
-
linearizedCost
|
|
3757
|
-
// Also return as separate value if you need original cost sum
|
|
3758
|
-
};
|
|
3759
|
-
}
|
|
3760
|
-
applyOperation(op) {
|
|
3761
|
-
if (op.op === "combined") {
|
|
3762
|
-
for (const subOp of op.subOperations) {
|
|
3763
|
-
this.applyOperation(subOp);
|
|
3764
|
-
}
|
|
3765
|
-
return;
|
|
3766
|
-
}
|
|
3767
|
-
const segment = this.currentMutatedSegments.get(op.segmentId);
|
|
3768
|
-
if (!segment || !segment.assignedPoints) return;
|
|
3769
|
-
if (op.op === "changeLayer") {
|
|
3770
|
-
op.oldLayer = segment.assignedPoints[op.pointIndex].point.z;
|
|
3771
|
-
segment.assignedPoints[op.pointIndex].point.z = op.newLayer;
|
|
3772
|
-
} else if (op.op === "switch") {
|
|
3773
|
-
const point1 = segment.assignedPoints[op.point1Index].point;
|
|
3774
|
-
const point2 = segment.assignedPoints[op.point2Index].point;
|
|
3775
|
-
const tempX = point1.x;
|
|
3776
|
-
const tempY = point1.y;
|
|
3777
|
-
const tempZ = point1.z;
|
|
3778
|
-
point1.x = point2.x;
|
|
3779
|
-
point1.y = point2.y;
|
|
3780
|
-
point1.z = point2.z;
|
|
3781
|
-
point2.x = tempX;
|
|
3782
|
-
point2.y = tempY;
|
|
3783
|
-
point2.z = tempZ;
|
|
3784
|
-
}
|
|
3785
|
-
}
|
|
3786
|
-
reverseOperation(op) {
|
|
3787
|
-
if (op.op === "combined") {
|
|
3788
|
-
for (const subOp of [...op.subOperations].reverse()) {
|
|
3789
|
-
this.reverseOperation(subOp);
|
|
3790
|
-
}
|
|
3791
|
-
return;
|
|
3792
|
-
}
|
|
3793
|
-
const segment = this.currentMutatedSegments.get(op.segmentId);
|
|
3794
|
-
if (!segment || !segment.assignedPoints) return;
|
|
3795
|
-
if (op.op === "changeLayer") {
|
|
3796
|
-
const oldLayer = op.oldLayer;
|
|
3797
|
-
if (oldLayer === void 0) return;
|
|
3798
|
-
segment.assignedPoints[op.pointIndex].point.z = oldLayer;
|
|
3799
|
-
} else if (op.op === "switch") {
|
|
3800
|
-
const point1 = segment.assignedPoints[op.point1Index].point;
|
|
3801
|
-
const point2 = segment.assignedPoints[op.point2Index].point;
|
|
3802
|
-
const tempX = point1.x;
|
|
3803
|
-
const tempY = point1.y;
|
|
3804
|
-
const tempZ = point1.z;
|
|
3805
|
-
point1.x = point2.x;
|
|
3806
|
-
point1.y = point2.y;
|
|
3807
|
-
point1.z = point2.z;
|
|
3808
|
-
point2.x = tempX;
|
|
3809
|
-
point2.y = tempY;
|
|
3810
|
-
point2.z = tempZ;
|
|
3811
|
-
}
|
|
3812
|
-
}
|
|
3813
|
-
isNewCostAcceptable(oldPf, newPf) {
|
|
3814
|
-
if (newPf < oldPf) return true;
|
|
3815
|
-
return false;
|
|
3816
|
-
}
|
|
3817
|
-
/**
|
|
3818
|
-
* FOR OUTPUT: Return the assigned points for each segment.
|
|
3819
|
-
*/
|
|
3820
|
-
getNodesWithPortPoints() {
|
|
3821
|
-
if (!this.solved) {
|
|
3822
|
-
throw new Error(
|
|
3823
|
-
"CapacitySegmentToPointSolver not solved, can't give port points yet"
|
|
3824
|
-
);
|
|
3825
|
-
}
|
|
3826
|
-
const map = /* @__PURE__ */ new Map();
|
|
3827
|
-
for (const segId of this.allSegmentIds) {
|
|
3828
|
-
for (const nodeId of this.segmentIdToNodeIds.get(segId)) {
|
|
3829
|
-
const node = this.nodeMap.get(nodeId);
|
|
3830
|
-
if (!map.has(nodeId)) {
|
|
3831
|
-
map.set(nodeId, {
|
|
3832
|
-
capacityMeshNodeId: nodeId,
|
|
3833
|
-
portPoints: [],
|
|
3834
|
-
center: node.center,
|
|
3835
|
-
width: node.width,
|
|
3836
|
-
height: node.height
|
|
3837
|
-
});
|
|
3838
|
-
}
|
|
3839
|
-
map.get(nodeId).portPoints.push(
|
|
3840
|
-
...this.currentMutatedSegments.get(segId).assignedPoints.map((ap) => ({
|
|
3841
|
-
...ap.point,
|
|
3842
|
-
connectionName: ap.connectionName
|
|
3843
|
-
}))
|
|
3844
|
-
);
|
|
3845
|
-
}
|
|
3846
|
-
}
|
|
3847
|
-
return Array.from(map.values());
|
|
3848
|
-
}
|
|
3849
|
-
_step() {
|
|
3850
|
-
if (this.iterations === this.MAX_ITERATIONS - 1) {
|
|
3851
|
-
this.solved = true;
|
|
3852
|
-
return;
|
|
3853
|
-
}
|
|
3854
|
-
if (this.currentCost < 1e-3) {
|
|
3855
|
-
this.solved = true;
|
|
3856
|
-
return;
|
|
3857
|
-
}
|
|
3858
|
-
const op = this.getRandomCombinedOperationOnSingleNode();
|
|
3859
|
-
this.lastCreatedOperation = op;
|
|
3860
|
-
this.applyOperation(op);
|
|
3861
|
-
const {
|
|
3862
|
-
cost: newCost,
|
|
3863
|
-
nodeCosts: newNodeCosts,
|
|
3864
|
-
probabilityOfFailure: newProbabilityOfFailure
|
|
3865
|
-
} = this.computeCurrentCost();
|
|
3866
|
-
op.cost = newCost;
|
|
3867
|
-
const keepChange = this.isNewCostAcceptable(this.currentCost, newCost);
|
|
3868
|
-
if (!keepChange) {
|
|
3869
|
-
this.reverseOperation(op);
|
|
3870
|
-
if (this.iterations - this.lastAcceptedIteration > this.NOOP_ITERATIONS_BEFORE_EARLY_STOP) {
|
|
3871
|
-
this.solved = true;
|
|
3872
|
-
}
|
|
3873
|
-
return;
|
|
3874
|
-
}
|
|
3875
|
-
this.lastAcceptedIteration = this.iterations;
|
|
3876
|
-
this.currentCost = newCost;
|
|
3877
|
-
this.currentNodeCosts = newNodeCosts;
|
|
3878
|
-
this.lastAppliedOperation = op;
|
|
3879
|
-
this.probabilityOfFailure = newProbabilityOfFailure;
|
|
3880
|
-
}
|
|
3881
|
-
visualize() {
|
|
3882
|
-
const immutableSegments = new Set(
|
|
3883
|
-
[...this.currentMutatedSegments.values()].filter(
|
|
3884
|
-
(seg) => !this.isSegmentMutable(seg.nodePortSegmentId)
|
|
3885
|
-
)
|
|
3886
|
-
);
|
|
3887
|
-
const graphics = {
|
|
3888
|
-
points: [...this.currentMutatedSegments.values()].flatMap(
|
|
3889
|
-
(seg, i) => seg.assignedPoints.map((ap) => ({
|
|
3890
|
-
x: ap.point.x,
|
|
3891
|
-
y: ap.point.y,
|
|
3892
|
-
label: `${seg.nodePortSegmentId}
|
|
3893
|
-
layer: ${ap.point.z}
|
|
3894
|
-
${ap.connectionName}
|
|
3895
|
-
${immutableSegments.has(seg) ? "(IMMUTABLE)" : ""}`,
|
|
3896
|
-
color: this.colorMap[ap.connectionName]
|
|
3897
|
-
}))
|
|
3898
|
-
),
|
|
3899
|
-
lines: [...this.currentMutatedSegments.values()].map((seg) => ({
|
|
3900
|
-
points: [seg.start, seg.end]
|
|
3901
|
-
})),
|
|
3902
|
-
rects: [
|
|
3903
|
-
...[...this.nodeMap.values()].map((node) => {
|
|
3904
|
-
const segmentIds = this.nodeIdToSegmentIds.get(
|
|
3905
|
-
node.capacityMeshNodeId
|
|
3906
|
-
);
|
|
3907
|
-
if (!segmentIds) return null;
|
|
3908
|
-
const segments = segmentIds.map(
|
|
3909
|
-
(segmentId) => this.currentMutatedSegments.get(segmentId)
|
|
3910
|
-
);
|
|
3911
|
-
let label;
|
|
3912
|
-
if (node._containsTarget) {
|
|
3913
|
-
label = `${node.capacityMeshNodeId}
|
|
3914
|
-
${node.width.toFixed(2)}x${node.height.toFixed(2)}`;
|
|
3915
|
-
} else {
|
|
3916
|
-
const intraNodeCrossings = getIntraNodeCrossingsFromSegments(segments);
|
|
3917
|
-
label = `${node.capacityMeshNodeId}
|
|
3918
|
-
${this.computeNodeCost(node.capacityMeshNodeId).toFixed(2)}/${getTunedTotalCapacity1(node).toFixed(2)}
|
|
3919
|
-
Trace Capacity: ${this.getUsedTraceCapacity(node.capacityMeshNodeId).toFixed(2)}
|
|
3920
|
-
X'ings: ${intraNodeCrossings.numSameLayerCrossings}
|
|
3921
|
-
Ent/Ex LC: ${intraNodeCrossings.numEntryExitLayerChanges}
|
|
3922
|
-
T X'ings: ${intraNodeCrossings.numTransitionCrossings}
|
|
3923
|
-
${node.width.toFixed(2)}x${node.height.toFixed(2)}`;
|
|
3924
|
-
}
|
|
3925
|
-
return {
|
|
3926
|
-
center: node.center,
|
|
3927
|
-
label,
|
|
3928
|
-
color: "red",
|
|
3929
|
-
width: node.width / 8,
|
|
3930
|
-
height: node.height / 8
|
|
3931
|
-
};
|
|
3932
|
-
}).filter((r) => r !== null)
|
|
3933
|
-
],
|
|
3934
|
-
circles: [],
|
|
3935
|
-
coordinateSystem: "cartesian",
|
|
3936
|
-
title: "Capacity Segment Point Optimizer"
|
|
3937
|
-
};
|
|
3938
|
-
const dashedLines = [];
|
|
3939
|
-
const nodeConnections = {};
|
|
3940
|
-
for (const seg of this.currentMutatedSegments.values()) {
|
|
3941
|
-
const nodeIds = this.segmentIdToNodeIds.get(seg.nodePortSegmentId);
|
|
3942
|
-
for (const nodeId of nodeIds) {
|
|
3943
|
-
if (!nodeConnections[nodeId]) {
|
|
3944
|
-
nodeConnections[nodeId] = {};
|
|
3945
|
-
}
|
|
3946
|
-
for (const ap of seg.assignedPoints) {
|
|
3947
|
-
if (!nodeConnections[nodeId][ap.connectionName]) {
|
|
3948
|
-
nodeConnections[nodeId][ap.connectionName] = [];
|
|
3949
|
-
}
|
|
3950
|
-
nodeConnections[nodeId][ap.connectionName].push(ap.point);
|
|
3951
|
-
}
|
|
3952
|
-
}
|
|
3953
|
-
}
|
|
3954
|
-
for (const nodeId in nodeConnections) {
|
|
3955
|
-
for (const conn in nodeConnections[nodeId]) {
|
|
3956
|
-
const points = nodeConnections[nodeId][conn];
|
|
3957
|
-
if (points.length <= 1) continue;
|
|
3958
|
-
const sameLayer = points[0].z === points[1].z;
|
|
3959
|
-
const commonLayer = points[0].z;
|
|
3960
|
-
const type = sameLayer ? commonLayer === 0 ? "top" : "bottom" : "transition";
|
|
3961
|
-
dashedLines.push({
|
|
3962
|
-
points,
|
|
3963
|
-
strokeDash: type === "top" ? void 0 : type === "bottom" ? "10 5" : "3 3 10",
|
|
3964
|
-
strokeColor: this.colorMap[conn] || "#000"
|
|
3965
|
-
});
|
|
3966
|
-
}
|
|
3967
|
-
}
|
|
3968
|
-
graphics.lines.push(...dashedLines);
|
|
3969
|
-
const operationsToShow = [];
|
|
3970
|
-
if (this.lastCreatedOperation?.op === "combined") {
|
|
3971
|
-
operationsToShow.push(...this.lastCreatedOperation.subOperations);
|
|
3972
|
-
} else if (this.lastCreatedOperation) {
|
|
3973
|
-
operationsToShow.push(this.lastCreatedOperation);
|
|
3974
|
-
}
|
|
3975
|
-
for (const op of operationsToShow) {
|
|
3976
|
-
const segment = this.currentMutatedSegments.get(op.segmentId);
|
|
3977
|
-
const node = this.nodeMap.get(segment.capacityMeshNodeId);
|
|
3978
|
-
graphics.circles.push({
|
|
3979
|
-
center: { x: node.center.x, y: node.center.y },
|
|
3980
|
-
radius: node.width / 4,
|
|
3981
|
-
stroke: "#0000ff",
|
|
3982
|
-
fill: "rgba(0, 0, 255, 0.2)",
|
|
3983
|
-
label: `LAST OPERATION: ${op.op}
|
|
3984
|
-
Cost: ${op.cost?.toString()}
|
|
3985
|
-
${node.capacityMeshNodeId}
|
|
3986
|
-
${this.currentNodeCosts.get(node.capacityMeshNodeId)}/${getTunedTotalCapacity1(node)}
|
|
3987
|
-
${node.width.toFixed(2)}x${node.height.toFixed(2)}`
|
|
3988
|
-
});
|
|
3989
|
-
if (op.op === "changeLayer") {
|
|
3990
|
-
const point = segment.assignedPoints[op.pointIndex];
|
|
3991
|
-
graphics.circles.push({
|
|
3992
|
-
center: { x: point.point.x, y: point.point.y },
|
|
3993
|
-
radius: this.nodeMap.get(segment.capacityMeshNodeId).width / 8,
|
|
3994
|
-
stroke: "#ff0000",
|
|
3995
|
-
fill: "rgba(255, 0, 0, 0.2)",
|
|
3996
|
-
label: `Layer Changed
|
|
3997
|
-
oldLayer: ${op.oldLayer}
|
|
3998
|
-
newLayer: ${op.newLayer}`
|
|
3999
|
-
});
|
|
4000
|
-
} else if (op.op === "switch") {
|
|
4001
|
-
const point1 = segment.assignedPoints[op.point1Index];
|
|
4002
|
-
const point2 = segment.assignedPoints[op.point2Index];
|
|
4003
|
-
graphics.circles.push(
|
|
4004
|
-
{
|
|
4005
|
-
center: { x: point1.point.x, y: point1.point.y },
|
|
4006
|
-
radius: node.width / 16,
|
|
4007
|
-
stroke: "#00ff00",
|
|
4008
|
-
fill: "rgba(0, 255, 0, 0.2)",
|
|
4009
|
-
label: `Swapped 1
|
|
4010
|
-
${segment.nodePortSegmentId}`
|
|
4011
|
-
},
|
|
4012
|
-
{
|
|
4013
|
-
center: { x: point2.point.x, y: point2.point.y },
|
|
4014
|
-
radius: node.width / 16,
|
|
4015
|
-
stroke: "#00ff00",
|
|
4016
|
-
fill: "rgba(0, 255, 0, 0.2)",
|
|
4017
|
-
label: `Swapped 2
|
|
4018
|
-
${segment.nodePortSegmentId}`
|
|
4019
|
-
}
|
|
4020
|
-
);
|
|
4021
|
-
graphics.lines.push({
|
|
4022
|
-
points: [
|
|
4023
|
-
{ x: point1.point.x, y: point1.point.y },
|
|
4024
|
-
{ x: point2.point.x, y: point2.point.y }
|
|
4025
|
-
],
|
|
4026
|
-
strokeColor: "#00ff00",
|
|
4027
|
-
strokeDash: "3 3",
|
|
4028
|
-
strokeWidth: node.width / 32
|
|
4029
|
-
});
|
|
4030
|
-
}
|
|
4031
|
-
}
|
|
4032
|
-
return graphics;
|
|
4033
|
-
}
|
|
4034
|
-
};
|
|
4035
|
-
|
|
4036
|
-
// lib/solvers/NetToPointPairsSolver/buildMinimumSpanningTree.ts
|
|
4037
|
-
var KDNode = class {
|
|
4038
|
-
point;
|
|
4039
|
-
left = null;
|
|
4040
|
-
right = null;
|
|
4041
|
-
constructor(point) {
|
|
4042
|
-
this.point = point;
|
|
4043
|
-
}
|
|
4044
|
-
};
|
|
4045
|
-
var KDTree = class {
|
|
4046
|
-
root = null;
|
|
4047
|
-
constructor(points) {
|
|
4048
|
-
if (points.length > 0) {
|
|
4049
|
-
this.root = this.buildTree(points, 0);
|
|
4050
|
-
}
|
|
4051
|
-
}
|
|
4052
|
-
buildTree(points, depth) {
|
|
4053
|
-
const axis = depth % 2 === 0 ? "x" : "y";
|
|
4054
|
-
points.sort((a, b) => a[axis] - b[axis]);
|
|
4055
|
-
const medianIndex = Math.floor(points.length / 2);
|
|
4056
|
-
const node = new KDNode(points[medianIndex]);
|
|
4057
|
-
if (medianIndex > 0) {
|
|
4058
|
-
node.left = this.buildTree(points.slice(0, medianIndex), depth + 1);
|
|
4059
|
-
}
|
|
4060
|
-
if (medianIndex < points.length - 1) {
|
|
4061
|
-
node.right = this.buildTree(points.slice(medianIndex + 1), depth + 1);
|
|
4062
|
-
}
|
|
4063
|
-
return node;
|
|
4064
|
-
}
|
|
4065
|
-
// Find the nearest neighbor to a query point
|
|
4066
|
-
findNearestNeighbor(queryPoint) {
|
|
4067
|
-
if (!this.root) {
|
|
4068
|
-
throw new Error("Tree is empty");
|
|
4069
|
-
}
|
|
4070
|
-
const best = this.root.point;
|
|
4071
|
-
const bestDistance = this.distance(queryPoint, best);
|
|
4072
|
-
this.nearestNeighborSearch(this.root, queryPoint, 0, best, bestDistance);
|
|
4073
|
-
return best;
|
|
4074
|
-
}
|
|
4075
|
-
nearestNeighborSearch(node, queryPoint, depth, best, bestDistance) {
|
|
4076
|
-
if (!node) {
|
|
4077
|
-
return best;
|
|
4078
|
-
}
|
|
4079
|
-
const axis = depth % 2 ? "x" : "y";
|
|
4080
|
-
const currentDistance = this.distance(queryPoint, node.point);
|
|
4081
|
-
if (currentDistance < bestDistance) {
|
|
4082
|
-
best = node.point;
|
|
4083
|
-
bestDistance = currentDistance;
|
|
4084
|
-
}
|
|
4085
|
-
const axisDiff = queryPoint[axis] - node.point[axis];
|
|
4086
|
-
const firstBranch = axisDiff <= 0 ? node.left : node.right;
|
|
4087
|
-
const secondBranch = axisDiff <= 0 ? node.right : node.left;
|
|
4088
|
-
best = this.nearestNeighborSearch(
|
|
4089
|
-
firstBranch,
|
|
4090
|
-
queryPoint,
|
|
4091
|
-
depth + 1,
|
|
4092
|
-
best,
|
|
4093
|
-
bestDistance
|
|
4094
|
-
);
|
|
4095
|
-
bestDistance = this.distance(queryPoint, best);
|
|
4096
|
-
if (Math.abs(axisDiff) < bestDistance) {
|
|
4097
|
-
best = this.nearestNeighborSearch(
|
|
4098
|
-
secondBranch,
|
|
4099
|
-
queryPoint,
|
|
4100
|
-
depth + 1,
|
|
4101
|
-
best,
|
|
4102
|
-
bestDistance
|
|
4103
|
-
);
|
|
4104
|
-
}
|
|
4105
|
-
return best;
|
|
4106
|
-
}
|
|
4107
|
-
// Find k nearest neighbors
|
|
4108
|
-
findKNearestNeighbors(queryPoint, k) {
|
|
4109
|
-
if (!this.root) {
|
|
4110
|
-
return [];
|
|
4111
|
-
}
|
|
4112
|
-
const neighbors = [];
|
|
4113
|
-
this.kNearestNeighborSearch(this.root, queryPoint, 0, neighbors, k);
|
|
4114
|
-
return neighbors.sort((a, b) => a.distance - b.distance).slice(0, k).map((n) => n.point);
|
|
4115
|
-
}
|
|
4116
|
-
kNearestNeighborSearch(node, queryPoint, depth, neighbors, k) {
|
|
4117
|
-
if (!node) {
|
|
4118
|
-
return;
|
|
4119
|
-
}
|
|
4120
|
-
const axis = depth % 2 ? "x" : "y";
|
|
4121
|
-
const currentDistance = this.distance(queryPoint, node.point);
|
|
4122
|
-
neighbors.push({ point: node.point, distance: currentDistance });
|
|
4123
|
-
const axisDiff = queryPoint[axis] - node.point[axis];
|
|
4124
|
-
const firstBranch = axisDiff <= 0 ? node.left : node.right;
|
|
4125
|
-
const secondBranch = axisDiff <= 0 ? node.right : node.left;
|
|
4126
|
-
this.kNearestNeighborSearch(
|
|
4127
|
-
firstBranch,
|
|
4128
|
-
queryPoint,
|
|
4129
|
-
depth + 1,
|
|
4130
|
-
neighbors,
|
|
4131
|
-
k
|
|
4132
|
-
);
|
|
4133
|
-
let kthDistance = Infinity;
|
|
4134
|
-
if (neighbors.length >= k) {
|
|
4135
|
-
neighbors.sort((a, b) => a.distance - b.distance);
|
|
4136
|
-
kthDistance = neighbors[k - 1]?.distance || Infinity;
|
|
4137
|
-
}
|
|
4138
|
-
if (Math.abs(axisDiff) < kthDistance || neighbors.length < k) {
|
|
4139
|
-
this.kNearestNeighborSearch(
|
|
4140
|
-
secondBranch,
|
|
4141
|
-
queryPoint,
|
|
4142
|
-
depth + 1,
|
|
4143
|
-
neighbors,
|
|
4144
|
-
k
|
|
4145
|
-
);
|
|
4146
|
-
}
|
|
4147
|
-
}
|
|
4148
|
-
// Calculate Euclidean distance between two points
|
|
4149
|
-
distance(a, b) {
|
|
4150
|
-
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
|
|
4151
|
-
}
|
|
4152
|
-
};
|
|
4153
|
-
var DisjointSet = class {
|
|
4154
|
-
parent = /* @__PURE__ */ new Map();
|
|
4155
|
-
rank = /* @__PURE__ */ new Map();
|
|
4156
|
-
constructor(points) {
|
|
4157
|
-
for (const point of points) {
|
|
4158
|
-
const key = this.pointToKey(point);
|
|
4159
|
-
this.parent.set(key, key);
|
|
4160
|
-
this.rank.set(key, 0);
|
|
3467
|
+
// Calculate Euclidean distance between two points
|
|
3468
|
+
distance(a, b) {
|
|
3469
|
+
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
|
|
3470
|
+
}
|
|
3471
|
+
};
|
|
3472
|
+
var DisjointSet = class {
|
|
3473
|
+
parent = /* @__PURE__ */ new Map();
|
|
3474
|
+
rank = /* @__PURE__ */ new Map();
|
|
3475
|
+
constructor(points) {
|
|
3476
|
+
for (const point of points) {
|
|
3477
|
+
const key = this.pointToKey(point);
|
|
3478
|
+
this.parent.set(key, key);
|
|
3479
|
+
this.rank.set(key, 0);
|
|
4161
3480
|
}
|
|
4162
3481
|
}
|
|
4163
3482
|
pointToKey(point) {
|
|
@@ -4212,551 +3531,1670 @@ function buildMinimumSpanningTree(points) {
|
|
|
4212
3531
|
if (point.x === neighbor.x && point.y === neighbor.y) {
|
|
4213
3532
|
continue;
|
|
4214
3533
|
}
|
|
4215
|
-
const distance3 = Math.sqrt(
|
|
4216
|
-
(point.x - neighbor.x) ** 2 + (point.y - neighbor.y) ** 2
|
|
4217
|
-
);
|
|
4218
|
-
edges.push({
|
|
4219
|
-
from: point,
|
|
4220
|
-
to: neighbor,
|
|
4221
|
-
weight: distance3
|
|
3534
|
+
const distance3 = Math.sqrt(
|
|
3535
|
+
(point.x - neighbor.x) ** 2 + (point.y - neighbor.y) ** 2
|
|
3536
|
+
);
|
|
3537
|
+
edges.push({
|
|
3538
|
+
from: point,
|
|
3539
|
+
to: neighbor,
|
|
3540
|
+
weight: distance3
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
}
|
|
3544
|
+
edges.sort((a, b) => a.weight - b.weight);
|
|
3545
|
+
const disjointSet = new DisjointSet(points);
|
|
3546
|
+
const mstEdges = [];
|
|
3547
|
+
for (const edge of edges) {
|
|
3548
|
+
if (disjointSet.union(edge.from, edge.to)) {
|
|
3549
|
+
mstEdges.push(edge);
|
|
3550
|
+
if (mstEdges.length === points.length - 1) {
|
|
3551
|
+
break;
|
|
3552
|
+
}
|
|
3553
|
+
}
|
|
3554
|
+
}
|
|
3555
|
+
return mstEdges;
|
|
3556
|
+
}
|
|
3557
|
+
|
|
3558
|
+
// lib/solvers/NetToPointPairsSolver/NetToPointPairsSolver.ts
|
|
3559
|
+
var NetToPointPairsSolver = class extends BaseSolver {
|
|
3560
|
+
constructor(ogSrj, colorMap = {}) {
|
|
3561
|
+
super();
|
|
3562
|
+
this.ogSrj = ogSrj;
|
|
3563
|
+
this.colorMap = colorMap;
|
|
3564
|
+
this.unprocessedConnections = [...ogSrj.connections];
|
|
3565
|
+
this.newConnections = [];
|
|
3566
|
+
}
|
|
3567
|
+
unprocessedConnections;
|
|
3568
|
+
newConnections;
|
|
3569
|
+
_step() {
|
|
3570
|
+
if (this.unprocessedConnections.length === 0) {
|
|
3571
|
+
this.solved = true;
|
|
3572
|
+
return;
|
|
3573
|
+
}
|
|
3574
|
+
const connection = this.unprocessedConnections.pop();
|
|
3575
|
+
if (connection.pointsToConnect.length === 2) {
|
|
3576
|
+
this.newConnections.push(connection);
|
|
3577
|
+
return;
|
|
3578
|
+
}
|
|
3579
|
+
const edges = buildMinimumSpanningTree(connection.pointsToConnect);
|
|
3580
|
+
for (let i = 0; i < edges.length; i++) {
|
|
3581
|
+
const edge = edges[i];
|
|
3582
|
+
this.newConnections.push({
|
|
3583
|
+
pointsToConnect: [edge.from, edge.to],
|
|
3584
|
+
name: `${connection.name}_mst${i}`
|
|
3585
|
+
});
|
|
3586
|
+
}
|
|
3587
|
+
}
|
|
3588
|
+
getNewSimpleRouteJson() {
|
|
3589
|
+
return {
|
|
3590
|
+
...this.ogSrj,
|
|
3591
|
+
connections: this.newConnections
|
|
3592
|
+
};
|
|
3593
|
+
}
|
|
3594
|
+
visualize() {
|
|
3595
|
+
const graphics = {
|
|
3596
|
+
lines: [],
|
|
3597
|
+
points: [],
|
|
3598
|
+
rects: [],
|
|
3599
|
+
circles: [],
|
|
3600
|
+
coordinateSystem: "cartesian",
|
|
3601
|
+
title: "Net To Point Pairs Visualization"
|
|
3602
|
+
};
|
|
3603
|
+
this.unprocessedConnections.forEach((connection) => {
|
|
3604
|
+
connection.pointsToConnect.forEach((point) => {
|
|
3605
|
+
graphics.points.push({
|
|
3606
|
+
x: point.x,
|
|
3607
|
+
y: point.y,
|
|
3608
|
+
color: "red",
|
|
3609
|
+
label: connection.name
|
|
3610
|
+
});
|
|
3611
|
+
});
|
|
3612
|
+
const fullyConnectedEdgeCount = connection.pointsToConnect.length ** 2;
|
|
3613
|
+
const random = seededRandom(0);
|
|
3614
|
+
const alreadyPlacedEdges = /* @__PURE__ */ new Set();
|
|
3615
|
+
for (let i = 0; i < Math.max(
|
|
3616
|
+
fullyConnectedEdgeCount,
|
|
3617
|
+
connection.pointsToConnect.length * 2
|
|
3618
|
+
); i++) {
|
|
3619
|
+
const a = Math.floor(random() * connection.pointsToConnect.length);
|
|
3620
|
+
const b = Math.floor(random() * connection.pointsToConnect.length);
|
|
3621
|
+
if (alreadyPlacedEdges.has(`${a}-${b}`)) continue;
|
|
3622
|
+
alreadyPlacedEdges.add(`${a}-${b}`);
|
|
3623
|
+
graphics.lines.push({
|
|
3624
|
+
points: [
|
|
3625
|
+
connection.pointsToConnect[a],
|
|
3626
|
+
connection.pointsToConnect[b]
|
|
3627
|
+
],
|
|
3628
|
+
strokeColor: "rgba(255,0,0,0.25)"
|
|
3629
|
+
});
|
|
3630
|
+
}
|
|
3631
|
+
});
|
|
3632
|
+
this.newConnections.forEach((connection) => {
|
|
3633
|
+
const color = this.colorMap?.[connection.name] || "blue";
|
|
3634
|
+
connection.pointsToConnect.forEach((point) => {
|
|
3635
|
+
graphics.points.push({
|
|
3636
|
+
x: point.x,
|
|
3637
|
+
y: point.y,
|
|
3638
|
+
color,
|
|
3639
|
+
label: connection.name
|
|
3640
|
+
});
|
|
3641
|
+
});
|
|
3642
|
+
for (let i = 0; i < connection.pointsToConnect.length - 1; i++) {
|
|
3643
|
+
for (let j = i + 1; j < connection.pointsToConnect.length; j++) {
|
|
3644
|
+
graphics.lines.push({
|
|
3645
|
+
points: [
|
|
3646
|
+
connection.pointsToConnect[i],
|
|
3647
|
+
connection.pointsToConnect[j]
|
|
3648
|
+
],
|
|
3649
|
+
strokeColor: color
|
|
3650
|
+
});
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
});
|
|
3654
|
+
return graphics;
|
|
3655
|
+
}
|
|
3656
|
+
};
|
|
3657
|
+
|
|
3658
|
+
// lib/utils/mapZToLayerName.ts
|
|
3659
|
+
var mapZToLayerName = (z, layerCount) => {
|
|
3660
|
+
if (z < 0 || z >= layerCount) {
|
|
3661
|
+
throw new Error(`Invalid z "${z}" for layer count: ${layerCount}`);
|
|
3662
|
+
}
|
|
3663
|
+
if (z === 0) return "top";
|
|
3664
|
+
if (z === layerCount - 1) return "bottom";
|
|
3665
|
+
return `inner${z}`;
|
|
3666
|
+
};
|
|
3667
|
+
|
|
3668
|
+
// lib/utils/convertHdRouteToSimplifiedRoute.ts
|
|
3669
|
+
var convertHdRouteToSimplifiedRoute = (hdRoute, layerCount) => {
|
|
3670
|
+
const result = [];
|
|
3671
|
+
if (hdRoute.route.length === 0) return result;
|
|
3672
|
+
let currentLayerPoints = [];
|
|
3673
|
+
let currentZ = hdRoute.route[0].z;
|
|
3674
|
+
for (let i = 0; i < hdRoute.route.length; i++) {
|
|
3675
|
+
const point = hdRoute.route[i];
|
|
3676
|
+
if (point.z !== currentZ) {
|
|
3677
|
+
const layerName2 = mapZToLayerName(currentZ, layerCount);
|
|
3678
|
+
for (const layerPoint of currentLayerPoints) {
|
|
3679
|
+
result.push({
|
|
3680
|
+
route_type: "wire",
|
|
3681
|
+
x: layerPoint.x,
|
|
3682
|
+
y: layerPoint.y,
|
|
3683
|
+
width: hdRoute.traceThickness,
|
|
3684
|
+
layer: layerName2
|
|
3685
|
+
});
|
|
3686
|
+
}
|
|
3687
|
+
const viaExists = hdRoute.vias.some(
|
|
3688
|
+
(via) => Math.abs(via.x - point.x) < 1e-3 && Math.abs(via.y - point.y) < 1e-3
|
|
3689
|
+
);
|
|
3690
|
+
if (viaExists) {
|
|
3691
|
+
const fromLayer = mapZToLayerName(currentZ, layerCount);
|
|
3692
|
+
const toLayer = mapZToLayerName(point.z, layerCount);
|
|
3693
|
+
result.push({
|
|
3694
|
+
route_type: "via",
|
|
3695
|
+
x: point.x,
|
|
3696
|
+
y: point.y,
|
|
3697
|
+
from_layer: fromLayer,
|
|
3698
|
+
to_layer: toLayer
|
|
3699
|
+
});
|
|
3700
|
+
}
|
|
3701
|
+
currentLayerPoints = [point];
|
|
3702
|
+
currentZ = point.z;
|
|
3703
|
+
} else {
|
|
3704
|
+
currentLayerPoints.push(point);
|
|
3705
|
+
}
|
|
3706
|
+
}
|
|
3707
|
+
const layerName = mapZToLayerName(currentZ, layerCount);
|
|
3708
|
+
for (const layerPoint of currentLayerPoints) {
|
|
3709
|
+
result.push({
|
|
3710
|
+
route_type: "wire",
|
|
3711
|
+
x: layerPoint.x,
|
|
3712
|
+
y: layerPoint.y,
|
|
3713
|
+
width: hdRoute.traceThickness,
|
|
3714
|
+
layer: layerName
|
|
3715
|
+
});
|
|
3716
|
+
}
|
|
3717
|
+
return result;
|
|
3718
|
+
};
|
|
3719
|
+
|
|
3720
|
+
// lib/utils/mapLayerNameToZ.ts
|
|
3721
|
+
var mapLayerNameToZ = (layerName, layerCount) => {
|
|
3722
|
+
if (layerName === "top") return 0;
|
|
3723
|
+
if (layerName === "bottom") return layerCount - 1;
|
|
3724
|
+
return parseInt(layerName.slice(5));
|
|
3725
|
+
};
|
|
3726
|
+
|
|
3727
|
+
// lib/solvers/RouteStitchingSolver/SingleHighDensityRouteStitchSolver.ts
|
|
3728
|
+
var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
3729
|
+
mergedHdRoute;
|
|
3730
|
+
remainingHdRoutes;
|
|
3731
|
+
start;
|
|
3732
|
+
end;
|
|
3733
|
+
constructor(opts) {
|
|
3734
|
+
super();
|
|
3735
|
+
this.remainingHdRoutes = [...opts.hdRoutes];
|
|
3736
|
+
this.mergedHdRoute = {
|
|
3737
|
+
connectionName: opts.hdRoutes[0].connectionName,
|
|
3738
|
+
route: [
|
|
3739
|
+
{
|
|
3740
|
+
x: opts.start.x,
|
|
3741
|
+
y: opts.start.y,
|
|
3742
|
+
z: opts.start.z
|
|
3743
|
+
}
|
|
3744
|
+
],
|
|
3745
|
+
vias: [],
|
|
3746
|
+
viaDiameter: opts.hdRoutes[0].viaDiameter,
|
|
3747
|
+
traceThickness: opts.hdRoutes[0].traceThickness
|
|
3748
|
+
};
|
|
3749
|
+
this.start = opts.start;
|
|
3750
|
+
this.end = opts.end;
|
|
3751
|
+
}
|
|
3752
|
+
_step() {
|
|
3753
|
+
if (this.remainingHdRoutes.length === 0) {
|
|
3754
|
+
this.mergedHdRoute.route.push({
|
|
3755
|
+
x: this.end.x,
|
|
3756
|
+
y: this.end.y,
|
|
3757
|
+
z: this.end.z
|
|
3758
|
+
});
|
|
3759
|
+
this.solved = true;
|
|
3760
|
+
return;
|
|
3761
|
+
}
|
|
3762
|
+
const lastMergedPoint = this.mergedHdRoute.route[this.mergedHdRoute.route.length - 1];
|
|
3763
|
+
let closestRouteIndex = 0;
|
|
3764
|
+
let matchedOn = "first";
|
|
3765
|
+
let closestDistance = Infinity;
|
|
3766
|
+
for (let i = 0; i < this.remainingHdRoutes.length; i++) {
|
|
3767
|
+
const hdRoute = this.remainingHdRoutes[i];
|
|
3768
|
+
const lastPointInCandidate = hdRoute.route[hdRoute.route.length - 1];
|
|
3769
|
+
const firstPointInCandidate = hdRoute.route[0];
|
|
3770
|
+
const distToFirst = distance(lastMergedPoint, firstPointInCandidate);
|
|
3771
|
+
const distToLast = distance(lastMergedPoint, lastPointInCandidate);
|
|
3772
|
+
if (distToFirst < closestDistance) {
|
|
3773
|
+
closestDistance = distToFirst;
|
|
3774
|
+
closestRouteIndex = i;
|
|
3775
|
+
matchedOn = "first";
|
|
3776
|
+
}
|
|
3777
|
+
if (distToLast < closestDistance) {
|
|
3778
|
+
closestDistance = distToLast;
|
|
3779
|
+
closestRouteIndex = i;
|
|
3780
|
+
matchedOn = "last";
|
|
3781
|
+
}
|
|
3782
|
+
}
|
|
3783
|
+
const hdRouteToMerge = this.remainingHdRoutes[closestRouteIndex];
|
|
3784
|
+
this.remainingHdRoutes.splice(closestRouteIndex, 1);
|
|
3785
|
+
if (matchedOn === "first") {
|
|
3786
|
+
this.mergedHdRoute.route.push(...hdRouteToMerge.route);
|
|
3787
|
+
} else {
|
|
3788
|
+
this.mergedHdRoute.route.push(...[...hdRouteToMerge.route].reverse());
|
|
3789
|
+
}
|
|
3790
|
+
this.mergedHdRoute.vias.push(...hdRouteToMerge.vias);
|
|
3791
|
+
}
|
|
3792
|
+
visualize() {
|
|
3793
|
+
const graphics = {
|
|
3794
|
+
points: [],
|
|
3795
|
+
lines: [],
|
|
3796
|
+
circles: [],
|
|
3797
|
+
title: "Single High Density Route Stitch Solver"
|
|
3798
|
+
};
|
|
3799
|
+
graphics.points?.push(
|
|
3800
|
+
{
|
|
3801
|
+
x: this.start.x,
|
|
3802
|
+
y: this.start.y,
|
|
3803
|
+
color: "green",
|
|
3804
|
+
label: "Start"
|
|
3805
|
+
},
|
|
3806
|
+
{
|
|
3807
|
+
x: this.end.x,
|
|
3808
|
+
y: this.end.y,
|
|
3809
|
+
color: "red",
|
|
3810
|
+
label: "End"
|
|
3811
|
+
}
|
|
3812
|
+
);
|
|
3813
|
+
if (this.mergedHdRoute && this.mergedHdRoute.route.length > 1) {
|
|
3814
|
+
graphics.lines?.push({
|
|
3815
|
+
points: this.mergedHdRoute.route.map((point) => ({
|
|
3816
|
+
x: point.x,
|
|
3817
|
+
y: point.y
|
|
3818
|
+
})),
|
|
3819
|
+
strokeColor: "green"
|
|
4222
3820
|
});
|
|
3821
|
+
for (const point of this.mergedHdRoute.route) {
|
|
3822
|
+
graphics.points?.push({
|
|
3823
|
+
x: point.x,
|
|
3824
|
+
y: point.y,
|
|
3825
|
+
color: "green"
|
|
3826
|
+
});
|
|
3827
|
+
}
|
|
3828
|
+
for (const via of this.mergedHdRoute.vias) {
|
|
3829
|
+
graphics.circles?.push({
|
|
3830
|
+
center: { x: via.x, y: via.y },
|
|
3831
|
+
radius: this.mergedHdRoute.viaDiameter / 2,
|
|
3832
|
+
fill: "green"
|
|
3833
|
+
});
|
|
3834
|
+
}
|
|
4223
3835
|
}
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
3836
|
+
const colorList = Array.from(
|
|
3837
|
+
{ length: this.remainingHdRoutes.length },
|
|
3838
|
+
(_, i) => `hsl(${i * 360 / this.remainingHdRoutes.length}, 100%, 50%)`
|
|
3839
|
+
);
|
|
3840
|
+
for (const [i, hdRoute] of this.remainingHdRoutes.entries()) {
|
|
3841
|
+
if (hdRoute.route.length > 1) {
|
|
3842
|
+
graphics.lines?.push({
|
|
3843
|
+
points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
|
|
3844
|
+
strokeColor: colorList[i]
|
|
3845
|
+
});
|
|
3846
|
+
}
|
|
3847
|
+
for (let pi = 0; pi < hdRoute.route.length; pi++) {
|
|
3848
|
+
const point = hdRoute.route[pi];
|
|
3849
|
+
graphics.points?.push({
|
|
3850
|
+
x: point.x + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
|
|
3851
|
+
y: point.y + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
|
|
3852
|
+
color: colorList[i],
|
|
3853
|
+
label: `Route ${i} ${point === hdRoute.route[0] ? "First" : point === hdRoute.route[hdRoute.route.length - 1] ? "Last" : ""}`
|
|
3854
|
+
});
|
|
3855
|
+
}
|
|
3856
|
+
for (const via of hdRoute.vias) {
|
|
3857
|
+
graphics.circles?.push({
|
|
3858
|
+
center: { x: via.x, y: via.y },
|
|
3859
|
+
radius: hdRoute.viaDiameter / 2,
|
|
3860
|
+
fill: colorList[i]
|
|
3861
|
+
});
|
|
4233
3862
|
}
|
|
4234
3863
|
}
|
|
3864
|
+
return graphics;
|
|
4235
3865
|
}
|
|
4236
|
-
|
|
4237
|
-
}
|
|
3866
|
+
};
|
|
4238
3867
|
|
|
4239
|
-
// lib/solvers/
|
|
4240
|
-
var
|
|
4241
|
-
|
|
3868
|
+
// lib/solvers/RouteStitchingSolver/MultipleHighDensityRouteStitchSolver.ts
|
|
3869
|
+
var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
|
|
3870
|
+
unsolvedRoutes;
|
|
3871
|
+
activeSolver = null;
|
|
3872
|
+
mergedHdRoutes = [];
|
|
3873
|
+
constructor(opts) {
|
|
4242
3874
|
super();
|
|
4243
|
-
this.
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
3875
|
+
this.unsolvedRoutes = opts.connections.map((c) => ({
|
|
3876
|
+
connectionName: c.name,
|
|
3877
|
+
hdRoutes: opts.hdRoutes.filter((r) => r.connectionName === c.name),
|
|
3878
|
+
start: {
|
|
3879
|
+
...c.pointsToConnect[0],
|
|
3880
|
+
z: mapLayerNameToZ(c.pointsToConnect[0].layer, opts.layerCount)
|
|
3881
|
+
},
|
|
3882
|
+
end: {
|
|
3883
|
+
...c.pointsToConnect[1],
|
|
3884
|
+
z: mapLayerNameToZ(c.pointsToConnect[1].layer, opts.layerCount)
|
|
3885
|
+
}
|
|
3886
|
+
}));
|
|
4247
3887
|
}
|
|
4248
|
-
unprocessedConnections;
|
|
4249
|
-
newConnections;
|
|
4250
3888
|
_step() {
|
|
4251
|
-
if (this.
|
|
4252
|
-
this.
|
|
3889
|
+
if (this.activeSolver) {
|
|
3890
|
+
this.activeSolver.step();
|
|
3891
|
+
if (this.activeSolver.solved) {
|
|
3892
|
+
this.mergedHdRoutes.push(this.activeSolver.mergedHdRoute);
|
|
3893
|
+
this.activeSolver = null;
|
|
3894
|
+
} else if (this.activeSolver.failed) {
|
|
3895
|
+
this.failed = true;
|
|
3896
|
+
this.error = this.activeSolver.error;
|
|
3897
|
+
}
|
|
4253
3898
|
return;
|
|
4254
3899
|
}
|
|
4255
|
-
const
|
|
4256
|
-
if (
|
|
4257
|
-
this.
|
|
3900
|
+
const unsolvedRoute = this.unsolvedRoutes.pop();
|
|
3901
|
+
if (!unsolvedRoute) {
|
|
3902
|
+
this.solved = true;
|
|
4258
3903
|
return;
|
|
4259
3904
|
}
|
|
4260
|
-
|
|
4261
|
-
|
|
4262
|
-
|
|
4263
|
-
this.newConnections.push({
|
|
4264
|
-
pointsToConnect: [edge.from, edge.to],
|
|
4265
|
-
name: `${connection.name}_mst${i}`
|
|
4266
|
-
});
|
|
3905
|
+
if (unsolvedRoute.hdRoutes.length === 0) {
|
|
3906
|
+
console.warn(`No routes to stitch for ${unsolvedRoute.connectionName}`);
|
|
3907
|
+
return;
|
|
4267
3908
|
}
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
};
|
|
3909
|
+
this.activeSolver = new SingleHighDensityRouteStitchSolver({
|
|
3910
|
+
hdRoutes: unsolvedRoute.hdRoutes,
|
|
3911
|
+
start: unsolvedRoute.start,
|
|
3912
|
+
end: unsolvedRoute.end
|
|
3913
|
+
});
|
|
4274
3914
|
}
|
|
4275
3915
|
visualize() {
|
|
4276
3916
|
const graphics = {
|
|
4277
|
-
lines: [],
|
|
4278
3917
|
points: [],
|
|
4279
|
-
|
|
3918
|
+
lines: [],
|
|
4280
3919
|
circles: [],
|
|
4281
|
-
|
|
4282
|
-
title: "Net To Point Pairs Visualization"
|
|
3920
|
+
title: "Multiple High Density Route Stitch Solver"
|
|
4283
3921
|
};
|
|
4284
|
-
this.
|
|
4285
|
-
|
|
4286
|
-
|
|
3922
|
+
if (this.activeSolver) {
|
|
3923
|
+
const activeSolverGraphics = this.activeSolver.visualize();
|
|
3924
|
+
if (activeSolverGraphics.points?.length) {
|
|
3925
|
+
graphics.points?.push(...activeSolverGraphics.points);
|
|
3926
|
+
}
|
|
3927
|
+
if (activeSolverGraphics.lines?.length) {
|
|
3928
|
+
graphics.lines?.push(...activeSolverGraphics.lines);
|
|
3929
|
+
}
|
|
3930
|
+
if (activeSolverGraphics.circles?.length) {
|
|
3931
|
+
graphics.circles?.push(...activeSolverGraphics.circles);
|
|
3932
|
+
}
|
|
3933
|
+
if (activeSolverGraphics.rects?.length) {
|
|
3934
|
+
graphics.rects = activeSolverGraphics.rects;
|
|
3935
|
+
}
|
|
3936
|
+
}
|
|
3937
|
+
for (const [i, mergedRoute] of this.mergedHdRoutes.entries()) {
|
|
3938
|
+
const solvedColor = `hsl(120, 100%, ${40 + i * 10 % 40}%)`;
|
|
3939
|
+
if (mergedRoute.route.length > 1) {
|
|
3940
|
+
graphics.lines?.push({
|
|
3941
|
+
points: mergedRoute.route.map((point) => ({
|
|
3942
|
+
x: point.x,
|
|
3943
|
+
y: point.y
|
|
3944
|
+
})),
|
|
3945
|
+
strokeColor: solvedColor,
|
|
3946
|
+
strokeWidth: mergedRoute.traceThickness
|
|
3947
|
+
});
|
|
3948
|
+
}
|
|
3949
|
+
for (const point of mergedRoute.route) {
|
|
3950
|
+
graphics.points?.push({
|
|
4287
3951
|
x: point.x,
|
|
4288
3952
|
y: point.y,
|
|
4289
|
-
color:
|
|
4290
|
-
label: connection.name
|
|
3953
|
+
color: solvedColor
|
|
4291
3954
|
});
|
|
4292
|
-
}
|
|
4293
|
-
const
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
4298
|
-
connection.pointsToConnect.length * 2
|
|
4299
|
-
); i++) {
|
|
4300
|
-
const a = Math.floor(random() * connection.pointsToConnect.length);
|
|
4301
|
-
const b = Math.floor(random() * connection.pointsToConnect.length);
|
|
4302
|
-
if (alreadyPlacedEdges.has(`${a}-${b}`)) continue;
|
|
4303
|
-
alreadyPlacedEdges.add(`${a}-${b}`);
|
|
4304
|
-
graphics.lines.push({
|
|
4305
|
-
points: [
|
|
4306
|
-
connection.pointsToConnect[a],
|
|
4307
|
-
connection.pointsToConnect[b]
|
|
4308
|
-
],
|
|
4309
|
-
strokeColor: "rgba(255,0,0,0.25)"
|
|
3955
|
+
}
|
|
3956
|
+
for (const via of mergedRoute.vias) {
|
|
3957
|
+
graphics.circles?.push({
|
|
3958
|
+
center: { x: via.x, y: via.y },
|
|
3959
|
+
radius: mergedRoute.viaDiameter / 2,
|
|
3960
|
+
fill: solvedColor
|
|
4310
3961
|
});
|
|
4311
3962
|
}
|
|
4312
|
-
}
|
|
4313
|
-
|
|
4314
|
-
|
|
4315
|
-
|
|
4316
|
-
|
|
3963
|
+
}
|
|
3964
|
+
const colorList = Array.from(
|
|
3965
|
+
{ length: this.unsolvedRoutes.length },
|
|
3966
|
+
(_, i) => `hsl(${i * 360 / this.unsolvedRoutes.length}, 100%, 50%)`
|
|
3967
|
+
);
|
|
3968
|
+
for (const [i, unsolvedRoute] of this.unsolvedRoutes.entries()) {
|
|
3969
|
+
graphics.points?.push(
|
|
3970
|
+
{
|
|
3971
|
+
x: unsolvedRoute.start.x,
|
|
3972
|
+
y: unsolvedRoute.start.y,
|
|
3973
|
+
color: colorList[i],
|
|
3974
|
+
label: `${unsolvedRoute.connectionName} Start`
|
|
3975
|
+
},
|
|
3976
|
+
{
|
|
3977
|
+
x: unsolvedRoute.end.x,
|
|
3978
|
+
y: unsolvedRoute.end.y,
|
|
3979
|
+
color: colorList[i],
|
|
3980
|
+
label: `${unsolvedRoute.connectionName} End`
|
|
3981
|
+
}
|
|
3982
|
+
);
|
|
3983
|
+
graphics.lines?.push({
|
|
3984
|
+
points: [
|
|
3985
|
+
{ x: unsolvedRoute.start.x, y: unsolvedRoute.start.y },
|
|
3986
|
+
{ x: unsolvedRoute.end.x, y: unsolvedRoute.end.y }
|
|
3987
|
+
],
|
|
3988
|
+
strokeColor: colorList[i],
|
|
3989
|
+
strokeDash: "2 2"
|
|
3990
|
+
});
|
|
3991
|
+
for (const hdRoute of unsolvedRoute.hdRoutes) {
|
|
3992
|
+
if (hdRoute.route.length > 1) {
|
|
3993
|
+
graphics.lines?.push({
|
|
3994
|
+
points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
|
|
3995
|
+
strokeColor: safeTransparentize(colorList[i], 0.5),
|
|
3996
|
+
strokeDash: "10 5"
|
|
3997
|
+
});
|
|
3998
|
+
}
|
|
3999
|
+
for (const via of hdRoute.vias) {
|
|
4000
|
+
graphics.circles?.push({
|
|
4001
|
+
center: { x: via.x, y: via.y },
|
|
4002
|
+
radius: hdRoute.viaDiameter / 2,
|
|
4003
|
+
fill: colorList[i]
|
|
4004
|
+
});
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
}
|
|
4008
|
+
return graphics;
|
|
4009
|
+
}
|
|
4010
|
+
};
|
|
4011
|
+
|
|
4012
|
+
// tests/fixtures/convertSrjToGraphicsObject.ts
|
|
4013
|
+
var convertSrjToGraphicsObject = (srj) => {
|
|
4014
|
+
const lines = [];
|
|
4015
|
+
const circles = [];
|
|
4016
|
+
const points = [];
|
|
4017
|
+
const colorMap = getColorMap(srj);
|
|
4018
|
+
if (srj.connections) {
|
|
4019
|
+
for (const connection of srj.connections) {
|
|
4020
|
+
for (const point of connection.pointsToConnect) {
|
|
4021
|
+
points.push({
|
|
4317
4022
|
x: point.x,
|
|
4318
4023
|
y: point.y,
|
|
4319
|
-
color,
|
|
4320
|
-
label: connection.name
|
|
4024
|
+
color: colorMap[connection.name],
|
|
4025
|
+
label: `${connection.name} (${point.layer})`
|
|
4321
4026
|
});
|
|
4322
|
-
}
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4027
|
+
}
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
if (srj.traces) {
|
|
4031
|
+
for (const trace of srj.traces) {
|
|
4032
|
+
for (let j = 0; j < trace.route.length - 1; j++) {
|
|
4033
|
+
const routePoint = trace.route[j];
|
|
4034
|
+
const nextRoutePoint = trace.route[j + 1];
|
|
4035
|
+
if (routePoint.route_type === "via") {
|
|
4036
|
+
circles.push({
|
|
4037
|
+
center: { x: routePoint.x, y: routePoint.y },
|
|
4038
|
+
radius: 0.3,
|
|
4039
|
+
// 0.6 via diameter
|
|
4040
|
+
fill: "blue",
|
|
4041
|
+
stroke: "none"
|
|
4042
|
+
});
|
|
4043
|
+
} else if (routePoint.route_type === "wire" && nextRoutePoint.route_type === "wire" && nextRoutePoint.layer === routePoint.layer) {
|
|
4044
|
+
lines.push({
|
|
4326
4045
|
points: [
|
|
4327
|
-
|
|
4328
|
-
|
|
4046
|
+
{ x: routePoint.x, y: routePoint.y },
|
|
4047
|
+
{ x: nextRoutePoint.x, y: nextRoutePoint.y }
|
|
4329
4048
|
],
|
|
4330
|
-
|
|
4049
|
+
strokeWidth: 0.15,
|
|
4050
|
+
strokeColor: safeTransparentize(
|
|
4051
|
+
{
|
|
4052
|
+
top: "red",
|
|
4053
|
+
bottom: "blue",
|
|
4054
|
+
inner1: "green",
|
|
4055
|
+
inner2: "yellow"
|
|
4056
|
+
}[routePoint.layer],
|
|
4057
|
+
0.5
|
|
4058
|
+
)
|
|
4059
|
+
// For some reason this is too small, likely a graphics-debug bug
|
|
4060
|
+
// strokeWidth: 0.15,
|
|
4331
4061
|
});
|
|
4332
4062
|
}
|
|
4333
4063
|
}
|
|
4334
|
-
}
|
|
4335
|
-
return graphics;
|
|
4064
|
+
}
|
|
4336
4065
|
}
|
|
4066
|
+
return {
|
|
4067
|
+
rects: srj.obstacles.map(
|
|
4068
|
+
(o) => ({
|
|
4069
|
+
center: o.center,
|
|
4070
|
+
width: o.width,
|
|
4071
|
+
height: o.height,
|
|
4072
|
+
fill: "rgba(255,0,0,0.5)"
|
|
4073
|
+
})
|
|
4074
|
+
),
|
|
4075
|
+
circles,
|
|
4076
|
+
lines,
|
|
4077
|
+
points
|
|
4078
|
+
};
|
|
4337
4079
|
};
|
|
4338
4080
|
|
|
4339
|
-
// lib/
|
|
4340
|
-
|
|
4341
|
-
|
|
4342
|
-
|
|
4081
|
+
// lib/solvers/UnravelSolver/getNodesNearNode.ts
|
|
4082
|
+
function getNodesNearNode(params) {
|
|
4083
|
+
const { nodeId, nodeIdToSegmentIds, segmentIdToNodeIds, hops } = params;
|
|
4084
|
+
if (hops === 0) return [nodeId];
|
|
4085
|
+
const segments = nodeIdToSegmentIds.get(nodeId);
|
|
4086
|
+
const nodes = /* @__PURE__ */ new Set();
|
|
4087
|
+
for (const segmentId of segments) {
|
|
4088
|
+
const adjacentNodeIds = segmentIdToNodeIds.get(segmentId);
|
|
4089
|
+
for (const adjacentNodeId of adjacentNodeIds) {
|
|
4090
|
+
const ancestors = getNodesNearNode({
|
|
4091
|
+
nodeId: adjacentNodeId,
|
|
4092
|
+
nodeIdToSegmentIds,
|
|
4093
|
+
segmentIdToNodeIds,
|
|
4094
|
+
hops: hops - 1
|
|
4095
|
+
});
|
|
4096
|
+
for (const ancestor of ancestors) {
|
|
4097
|
+
nodes.add(ancestor);
|
|
4098
|
+
}
|
|
4099
|
+
}
|
|
4343
4100
|
}
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4101
|
+
return Array.from(nodes);
|
|
4102
|
+
}
|
|
4103
|
+
|
|
4104
|
+
// lib/solvers/UnravelSolver/createPointModificationsHash.ts
|
|
4105
|
+
var createPointModificationsHash = (pointModifications) => {
|
|
4106
|
+
return Array.from(pointModifications.entries()).map(
|
|
4107
|
+
([id, { x, y, z }]) => `${id}(${x?.toFixed(3) ?? ""},${y?.toFixed(3) ?? ""},${z ?? ""})`
|
|
4108
|
+
).sort().join("&");
|
|
4347
4109
|
};
|
|
4348
4110
|
|
|
4349
|
-
// lib/
|
|
4350
|
-
var
|
|
4351
|
-
const
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
|
|
4362
|
-
|
|
4363
|
-
|
|
4364
|
-
|
|
4365
|
-
|
|
4111
|
+
// lib/solvers/UnravelSolver/hasZRangeOverlap.ts
|
|
4112
|
+
var hasZRangeOverlap = (A_z1, A_z2, B_z1, B_z2) => {
|
|
4113
|
+
const Amin = Math.min(A_z1, A_z2);
|
|
4114
|
+
const Amax = Math.max(A_z1, A_z2);
|
|
4115
|
+
const Bmin = Math.min(B_z1, B_z2);
|
|
4116
|
+
const Bmax = Math.max(B_z1, B_z2);
|
|
4117
|
+
return Amin <= Bmax && Amax >= Bmin;
|
|
4118
|
+
};
|
|
4119
|
+
|
|
4120
|
+
// lib/solvers/UnravelSolver/getIssuesInSection.ts
|
|
4121
|
+
var getIssuesInSection = (section, nodeMap, pointModifications, connMap) => {
|
|
4122
|
+
const issues = [];
|
|
4123
|
+
const points = /* @__PURE__ */ new Map();
|
|
4124
|
+
for (const nodeId of section.allNodeIds) {
|
|
4125
|
+
for (const segmentPointId of section.segmentPointsInNode.get(nodeId)) {
|
|
4126
|
+
if (!points.has(segmentPointId)) {
|
|
4127
|
+
const ogPoint = section.segmentPointMap.get(segmentPointId);
|
|
4128
|
+
const modPoint = pointModifications.get(segmentPointId);
|
|
4129
|
+
points.set(segmentPointId, {
|
|
4130
|
+
x: modPoint?.x ?? ogPoint.x,
|
|
4131
|
+
y: modPoint?.y ?? ogPoint.y,
|
|
4132
|
+
z: modPoint?.z ?? ogPoint.z
|
|
4366
4133
|
});
|
|
4367
4134
|
}
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
|
|
4371
|
-
|
|
4372
|
-
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
|
|
4135
|
+
}
|
|
4136
|
+
}
|
|
4137
|
+
for (const nodeId of section.allNodeIds) {
|
|
4138
|
+
const node = nodeMap.get(nodeId);
|
|
4139
|
+
if (!node) continue;
|
|
4140
|
+
const nodeSegmentPairs = section.segmentPairsInNode.get(nodeId);
|
|
4141
|
+
for (const pair of nodeSegmentPairs) {
|
|
4142
|
+
const A = points.get(pair[0]);
|
|
4143
|
+
const B = points.get(pair[1]);
|
|
4144
|
+
if (A.z !== B.z) {
|
|
4145
|
+
issues.push({
|
|
4146
|
+
type: "transition_via",
|
|
4147
|
+
segmentPoints: pair,
|
|
4148
|
+
capacityMeshNodeId: nodeId,
|
|
4149
|
+
probabilityOfFailure: 0
|
|
4380
4150
|
});
|
|
4381
4151
|
}
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
|
|
4152
|
+
}
|
|
4153
|
+
for (let i = 0; i < nodeSegmentPairs.length; i++) {
|
|
4154
|
+
for (let j = i + 1; j < nodeSegmentPairs.length; j++) {
|
|
4155
|
+
if (connMap?.areIdsConnected(
|
|
4156
|
+
nodeSegmentPairs[i][0],
|
|
4157
|
+
nodeSegmentPairs[i][1]
|
|
4158
|
+
)) {
|
|
4159
|
+
continue;
|
|
4160
|
+
}
|
|
4161
|
+
const pair1 = nodeSegmentPairs[i];
|
|
4162
|
+
const pair2 = nodeSegmentPairs[j];
|
|
4163
|
+
const A = points.get(pair1[0]);
|
|
4164
|
+
const B = points.get(pair1[1]);
|
|
4165
|
+
const C = points.get(pair2[0]);
|
|
4166
|
+
const D = points.get(pair2[1]);
|
|
4167
|
+
if (!hasZRangeOverlap(A.z, B.z, C.z, D.z)) continue;
|
|
4168
|
+
const areCrossing = doSegmentsIntersect(A, B, C, D);
|
|
4169
|
+
const isSameLayer = A.z === B.z && C.z === D.z && A.z === C.z;
|
|
4170
|
+
if (areCrossing) {
|
|
4171
|
+
if (isSameLayer) {
|
|
4172
|
+
issues.push({
|
|
4173
|
+
type: "same_layer_crossing",
|
|
4174
|
+
segmentPoints: [pair1, pair2],
|
|
4175
|
+
capacityMeshNodeId: nodeId,
|
|
4176
|
+
crossingLine1: pair1,
|
|
4177
|
+
crossingLine2: pair2,
|
|
4178
|
+
probabilityOfFailure: 0
|
|
4179
|
+
});
|
|
4180
|
+
} else if (A.z === B.z && C.z !== D.z) {
|
|
4181
|
+
issues.push({
|
|
4182
|
+
type: "single_transition_crossing",
|
|
4183
|
+
segmentPoints: [pair1, pair2],
|
|
4184
|
+
capacityMeshNodeId: nodeId,
|
|
4185
|
+
sameLayerCrossingLine: pair1,
|
|
4186
|
+
transitionCrossingLine: pair2,
|
|
4187
|
+
probabilityOfFailure: 0
|
|
4188
|
+
});
|
|
4189
|
+
} else if (A.z !== B.z && C.z === D.z) {
|
|
4190
|
+
issues.push({
|
|
4191
|
+
type: "single_transition_crossing",
|
|
4192
|
+
segmentPoints: [pair1, pair2],
|
|
4193
|
+
capacityMeshNodeId: nodeId,
|
|
4194
|
+
sameLayerCrossingLine: pair2,
|
|
4195
|
+
transitionCrossingLine: pair1,
|
|
4196
|
+
probabilityOfFailure: 0
|
|
4197
|
+
});
|
|
4198
|
+
} else if (A.z !== B.z && C.z !== D.z) {
|
|
4199
|
+
issues.push({
|
|
4200
|
+
type: "double_transition_crossing",
|
|
4201
|
+
segmentPoints: [pair1, pair2],
|
|
4202
|
+
capacityMeshNodeId: nodeId,
|
|
4203
|
+
crossingLine1: pair1,
|
|
4204
|
+
crossingLine2: pair2,
|
|
4205
|
+
probabilityOfFailure: 0
|
|
4206
|
+
});
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
}
|
|
4386
4210
|
}
|
|
4387
4211
|
}
|
|
4388
|
-
|
|
4389
|
-
|
|
4390
|
-
|
|
4391
|
-
|
|
4392
|
-
|
|
4393
|
-
|
|
4394
|
-
|
|
4395
|
-
|
|
4212
|
+
return issues;
|
|
4213
|
+
};
|
|
4214
|
+
|
|
4215
|
+
// lib/solvers/UnravelSolver/getLogProbability.ts
|
|
4216
|
+
var getLogProbability = (probability) => {
|
|
4217
|
+
const K = -2.3;
|
|
4218
|
+
return 1 - Math.exp(probability * K);
|
|
4219
|
+
};
|
|
4220
|
+
|
|
4221
|
+
// lib/solvers/UnravelSolver/applyOperationToPointModifications.ts
|
|
4222
|
+
var applyOperationToPointModifications = (pointModifications, operation, getPointInCandidate) => {
|
|
4223
|
+
if (operation.type === "change_layer") {
|
|
4224
|
+
for (const segmentPointId of operation.segmentPointIds) {
|
|
4225
|
+
const existingMods = pointModifications.get(segmentPointId) || {};
|
|
4226
|
+
pointModifications.set(segmentPointId, {
|
|
4227
|
+
...existingMods,
|
|
4228
|
+
z: operation.newZ
|
|
4229
|
+
});
|
|
4230
|
+
}
|
|
4231
|
+
} else if (operation.type === "swap_position_on_segment") {
|
|
4232
|
+
const [ASpId, BSpId] = operation.segmentPointIds;
|
|
4233
|
+
const A = getPointInCandidate(ASpId);
|
|
4234
|
+
const B = getPointInCandidate(BSpId);
|
|
4235
|
+
const existingModsA = pointModifications.get(ASpId) || {};
|
|
4236
|
+
const existingModsB = pointModifications.get(BSpId) || {};
|
|
4237
|
+
pointModifications.set(ASpId, {
|
|
4238
|
+
...existingModsA,
|
|
4239
|
+
x: B.x,
|
|
4240
|
+
y: B.y
|
|
4241
|
+
});
|
|
4242
|
+
pointModifications.set(BSpId, {
|
|
4243
|
+
...existingModsB,
|
|
4244
|
+
x: A.x,
|
|
4245
|
+
y: A.y
|
|
4396
4246
|
});
|
|
4247
|
+
} else if (operation.type === "combined") {
|
|
4248
|
+
for (const subOperation of operation.operations) {
|
|
4249
|
+
applyOperationToPointModifications(
|
|
4250
|
+
pointModifications,
|
|
4251
|
+
subOperation,
|
|
4252
|
+
getPointInCandidate
|
|
4253
|
+
);
|
|
4254
|
+
}
|
|
4397
4255
|
}
|
|
4398
|
-
return result;
|
|
4399
4256
|
};
|
|
4400
4257
|
|
|
4401
|
-
// lib/
|
|
4402
|
-
var
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4258
|
+
// lib/solvers/UnravelSolver/createSegmentPointMap.ts
|
|
4259
|
+
var createSegmentPointMap = (dedupedSegments, segmentIdToNodeIds) => {
|
|
4260
|
+
const segmentPoints = [];
|
|
4261
|
+
let highestSegmentPointId = 0;
|
|
4262
|
+
for (const segment of dedupedSegments) {
|
|
4263
|
+
for (const point of segment.assignedPoints) {
|
|
4264
|
+
segmentPoints.push({
|
|
4265
|
+
segmentPointId: `SP${highestSegmentPointId++}`,
|
|
4266
|
+
segmentId: segment.nodePortSegmentId,
|
|
4267
|
+
capacityMeshNodeIds: segmentIdToNodeIds.get(
|
|
4268
|
+
segment.nodePortSegmentId
|
|
4269
|
+
),
|
|
4270
|
+
connectionName: point.connectionName,
|
|
4271
|
+
x: point.point.x,
|
|
4272
|
+
y: point.point.y,
|
|
4273
|
+
z: point.point.z,
|
|
4274
|
+
directlyConnectedSegmentPointIds: []
|
|
4275
|
+
});
|
|
4276
|
+
}
|
|
4277
|
+
}
|
|
4278
|
+
const segmentPointMap = /* @__PURE__ */ new Map();
|
|
4279
|
+
for (const segmentPoint of segmentPoints) {
|
|
4280
|
+
segmentPointMap.set(segmentPoint.segmentPointId, segmentPoint);
|
|
4281
|
+
}
|
|
4282
|
+
return segmentPointMap;
|
|
4406
4283
|
};
|
|
4407
4284
|
|
|
4408
|
-
// lib/solvers/
|
|
4409
|
-
var
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4285
|
+
// lib/solvers/UnravelSolver/UnravelSectionSolver.ts
|
|
4286
|
+
var UnravelSectionSolver = class extends BaseSolver {
|
|
4287
|
+
nodeMap;
|
|
4288
|
+
dedupedSegments;
|
|
4289
|
+
MUTABLE_HOPS = 1;
|
|
4290
|
+
unravelSection;
|
|
4291
|
+
candidates = [];
|
|
4292
|
+
lastProcessedCandidate = null;
|
|
4293
|
+
bestCandidate = null;
|
|
4294
|
+
originalCandidate;
|
|
4295
|
+
rootNodeId;
|
|
4296
|
+
nodeIdToSegmentIds;
|
|
4297
|
+
segmentIdToNodeIds;
|
|
4298
|
+
colorMap;
|
|
4299
|
+
tunedNodeCapacityMap;
|
|
4300
|
+
MAX_CANDIDATES = 500;
|
|
4301
|
+
selectedCandidateIndex = null;
|
|
4302
|
+
queuedOrExploredCandidatePointModificationHashes = /* @__PURE__ */ new Set();
|
|
4303
|
+
constructor(params) {
|
|
4415
4304
|
super();
|
|
4416
|
-
this.
|
|
4417
|
-
this.
|
|
4418
|
-
|
|
4419
|
-
|
|
4420
|
-
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4305
|
+
this.MUTABLE_HOPS = params.MUTABLE_HOPS ?? this.MUTABLE_HOPS;
|
|
4306
|
+
this.nodeMap = params.nodeMap;
|
|
4307
|
+
this.dedupedSegments = params.dedupedSegments;
|
|
4308
|
+
this.nodeIdToSegmentIds = params.nodeIdToSegmentIds;
|
|
4309
|
+
this.segmentIdToNodeIds = params.segmentIdToNodeIds;
|
|
4310
|
+
this.rootNodeId = params.rootNodeId;
|
|
4311
|
+
this.colorMap = params.colorMap ?? {};
|
|
4312
|
+
this.unravelSection = this.createUnravelSection(params.segmentPointMap);
|
|
4313
|
+
this.tunedNodeCapacityMap = /* @__PURE__ */ new Map();
|
|
4314
|
+
for (const nodeId of this.unravelSection.allNodeIds) {
|
|
4315
|
+
this.tunedNodeCapacityMap.set(
|
|
4316
|
+
nodeId,
|
|
4317
|
+
getTunedTotalCapacity1(this.nodeMap.get(nodeId))
|
|
4318
|
+
);
|
|
4319
|
+
}
|
|
4320
|
+
this.originalCandidate = this.createInitialCandidate();
|
|
4321
|
+
this.candidates = [this.originalCandidate];
|
|
4322
|
+
}
|
|
4323
|
+
createUnravelSection(segmentPointMap) {
|
|
4324
|
+
const mutableNodeIds = getNodesNearNode({
|
|
4325
|
+
nodeId: this.rootNodeId,
|
|
4326
|
+
nodeIdToSegmentIds: this.nodeIdToSegmentIds,
|
|
4327
|
+
segmentIdToNodeIds: this.segmentIdToNodeIds,
|
|
4328
|
+
hops: this.MUTABLE_HOPS
|
|
4329
|
+
});
|
|
4330
|
+
const allNodeIds = getNodesNearNode({
|
|
4331
|
+
nodeId: this.rootNodeId,
|
|
4332
|
+
nodeIdToSegmentIds: this.nodeIdToSegmentIds,
|
|
4333
|
+
segmentIdToNodeIds: this.segmentIdToNodeIds,
|
|
4334
|
+
hops: this.MUTABLE_HOPS + 1
|
|
4335
|
+
});
|
|
4336
|
+
const immutableNodeIds = Array.from(
|
|
4337
|
+
new Set(allNodeIds).difference(new Set(mutableNodeIds))
|
|
4338
|
+
);
|
|
4339
|
+
if (!segmentPointMap) {
|
|
4340
|
+
segmentPointMap = createSegmentPointMap(
|
|
4341
|
+
this.dedupedSegments,
|
|
4342
|
+
this.segmentIdToNodeIds
|
|
4343
|
+
);
|
|
4344
|
+
}
|
|
4345
|
+
const segmentPoints = Array.from(segmentPointMap.values());
|
|
4346
|
+
const segmentPointsInNode = /* @__PURE__ */ new Map();
|
|
4347
|
+
for (const segmentPoint of segmentPoints) {
|
|
4348
|
+
for (const nodeId of segmentPoint.capacityMeshNodeIds) {
|
|
4349
|
+
segmentPointsInNode.set(nodeId, [
|
|
4350
|
+
...segmentPointsInNode.get(nodeId) ?? [],
|
|
4351
|
+
segmentPoint.segmentPointId
|
|
4352
|
+
]);
|
|
4353
|
+
}
|
|
4354
|
+
}
|
|
4355
|
+
const segmentPointsInSegment = /* @__PURE__ */ new Map();
|
|
4356
|
+
for (const segmentPoint of segmentPoints) {
|
|
4357
|
+
segmentPointsInSegment.set(segmentPoint.segmentId, [
|
|
4358
|
+
...segmentPointsInSegment.get(segmentPoint.segmentId) ?? [],
|
|
4359
|
+
segmentPoint.segmentPointId
|
|
4360
|
+
]);
|
|
4361
|
+
}
|
|
4362
|
+
for (let i = 0; i < segmentPoints.length; i++) {
|
|
4363
|
+
const A = segmentPoints[i];
|
|
4364
|
+
for (let j = i + 1; j < segmentPoints.length; j++) {
|
|
4365
|
+
const B = segmentPoints[j];
|
|
4366
|
+
if (B.segmentPointId === A.segmentPointId) continue;
|
|
4367
|
+
if (B.segmentId === A.segmentId) continue;
|
|
4368
|
+
if (B.connectionName !== A.connectionName) continue;
|
|
4369
|
+
if (A.capacityMeshNodeIds.some(
|
|
4370
|
+
(nId) => B.capacityMeshNodeIds.includes(nId)
|
|
4371
|
+
)) {
|
|
4372
|
+
A.directlyConnectedSegmentPointIds.push(B.segmentPointId);
|
|
4373
|
+
B.directlyConnectedSegmentPointIds.push(A.segmentPointId);
|
|
4424
4374
|
}
|
|
4425
|
-
|
|
4426
|
-
|
|
4427
|
-
|
|
4428
|
-
|
|
4375
|
+
}
|
|
4376
|
+
}
|
|
4377
|
+
const segmentPairsInNode = /* @__PURE__ */ new Map();
|
|
4378
|
+
for (const nodeId of allNodeIds) {
|
|
4379
|
+
segmentPairsInNode.set(nodeId, []);
|
|
4380
|
+
}
|
|
4381
|
+
for (const A of segmentPoints) {
|
|
4382
|
+
for (const nodeId of A.capacityMeshNodeIds) {
|
|
4383
|
+
const otherSegmentPoints = segmentPointsInNode.get(nodeId).map((spId) => segmentPointMap.get(spId));
|
|
4384
|
+
const segmentPairs = segmentPairsInNode.get(nodeId);
|
|
4385
|
+
if (!segmentPairs) continue;
|
|
4386
|
+
for (const BId of A.directlyConnectedSegmentPointIds) {
|
|
4387
|
+
const B = segmentPointMap.get(BId);
|
|
4388
|
+
if (B.segmentPointId === A.segmentPointId) continue;
|
|
4389
|
+
if (!B.capacityMeshNodeIds.some((nId) => nId === nodeId)) continue;
|
|
4390
|
+
if (!segmentPairs.some(
|
|
4391
|
+
([a, b]) => a === A.segmentPointId && b === B.segmentPointId || a === B.segmentPointId && b === A.segmentPointId
|
|
4392
|
+
)) {
|
|
4393
|
+
segmentPairs.push([A.segmentPointId, B.segmentPointId]);
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
const mutableSegmentIds = /* @__PURE__ */ new Set();
|
|
4399
|
+
for (const nodeId of mutableNodeIds) {
|
|
4400
|
+
for (const segmentId of this.nodeIdToSegmentIds.get(nodeId)) {
|
|
4401
|
+
const allNodeIdsWithSegment = this.segmentIdToNodeIds.get(segmentId);
|
|
4402
|
+
if (allNodeIdsWithSegment.every(
|
|
4403
|
+
(nodeId2) => !this.nodeMap.get(nodeId2)._containsTarget
|
|
4404
|
+
)) {
|
|
4405
|
+
mutableSegmentIds.add(segmentId);
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
return {
|
|
4410
|
+
allNodeIds,
|
|
4411
|
+
mutableNodeIds,
|
|
4412
|
+
immutableNodeIds,
|
|
4413
|
+
mutableSegmentIds,
|
|
4414
|
+
segmentPairsInNode,
|
|
4415
|
+
segmentPointMap,
|
|
4416
|
+
segmentPointsInNode,
|
|
4417
|
+
segmentPointsInSegment
|
|
4418
|
+
};
|
|
4419
|
+
}
|
|
4420
|
+
createInitialCandidate() {
|
|
4421
|
+
const pointModifications = /* @__PURE__ */ new Map();
|
|
4422
|
+
const issues = getIssuesInSection(
|
|
4423
|
+
this.unravelSection,
|
|
4424
|
+
this.nodeMap,
|
|
4425
|
+
pointModifications
|
|
4426
|
+
);
|
|
4427
|
+
const g = this.computeG({
|
|
4428
|
+
issues,
|
|
4429
|
+
originalCandidate: {},
|
|
4430
|
+
operationsPerformed: 0,
|
|
4431
|
+
operation: {}
|
|
4432
|
+
});
|
|
4433
|
+
return {
|
|
4434
|
+
pointModifications,
|
|
4435
|
+
issues,
|
|
4436
|
+
g,
|
|
4437
|
+
h: 0,
|
|
4438
|
+
f: g,
|
|
4439
|
+
operationsPerformed: 0,
|
|
4440
|
+
candidateHash: createPointModificationsHash(pointModifications)
|
|
4441
|
+
// candidateFullHash: createFullPointModificationsHash(
|
|
4442
|
+
// this.unravelSection.segmentPointMap,
|
|
4443
|
+
// pointModifications,
|
|
4444
|
+
// ),
|
|
4445
|
+
};
|
|
4446
|
+
}
|
|
4447
|
+
get nextCandidate() {
|
|
4448
|
+
return this.candidates[0] ?? null;
|
|
4449
|
+
}
|
|
4450
|
+
getPointInCandidate(candidate, segmentPointId) {
|
|
4451
|
+
const originalPoint = this.unravelSection.segmentPointMap.get(segmentPointId);
|
|
4452
|
+
const modifications = candidate.pointModifications.get(segmentPointId);
|
|
4453
|
+
return {
|
|
4454
|
+
x: modifications?.x ?? originalPoint.x,
|
|
4455
|
+
y: modifications?.y ?? originalPoint.y,
|
|
4456
|
+
z: modifications?.z ?? originalPoint.z,
|
|
4457
|
+
segmentId: originalPoint.segmentId
|
|
4458
|
+
};
|
|
4459
|
+
}
|
|
4460
|
+
getOperationsForIssue(candidate, issue) {
|
|
4461
|
+
const operations = [];
|
|
4462
|
+
if (issue.type === "transition_via") {
|
|
4463
|
+
const [APointId, BPointId] = issue.segmentPoints;
|
|
4464
|
+
const pointA = this.getPointInCandidate(candidate, APointId);
|
|
4465
|
+
const pointB = this.getPointInCandidate(candidate, BPointId);
|
|
4466
|
+
if (this.unravelSection.mutableSegmentIds.has(pointA.segmentId)) {
|
|
4467
|
+
operations.push({
|
|
4468
|
+
type: "change_layer",
|
|
4469
|
+
newZ: pointB.z,
|
|
4470
|
+
segmentPointIds: [APointId]
|
|
4471
|
+
});
|
|
4472
|
+
}
|
|
4473
|
+
if (this.unravelSection.mutableSegmentIds.has(pointB.segmentId)) {
|
|
4474
|
+
operations.push({
|
|
4475
|
+
type: "change_layer",
|
|
4476
|
+
newZ: pointA.z,
|
|
4477
|
+
segmentPointIds: [BPointId]
|
|
4478
|
+
});
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
if (issue.type === "same_layer_crossing") {
|
|
4482
|
+
const [APointId, BPointId] = issue.crossingLine1;
|
|
4483
|
+
const [CPointId, DPointId] = issue.crossingLine2;
|
|
4484
|
+
const sharedSegments = [];
|
|
4485
|
+
const A = this.unravelSection.segmentPointMap.get(APointId);
|
|
4486
|
+
const B = this.unravelSection.segmentPointMap.get(BPointId);
|
|
4487
|
+
const C = this.unravelSection.segmentPointMap.get(CPointId);
|
|
4488
|
+
const D = this.unravelSection.segmentPointMap.get(DPointId);
|
|
4489
|
+
if (A.segmentId === C.segmentId) {
|
|
4490
|
+
sharedSegments.push([APointId, CPointId]);
|
|
4491
|
+
}
|
|
4492
|
+
if (A.segmentId === D.segmentId) {
|
|
4493
|
+
sharedSegments.push([APointId, DPointId]);
|
|
4494
|
+
}
|
|
4495
|
+
if (B.segmentId === C.segmentId) {
|
|
4496
|
+
sharedSegments.push([BPointId, CPointId]);
|
|
4497
|
+
}
|
|
4498
|
+
if (B.segmentId === D.segmentId) {
|
|
4499
|
+
sharedSegments.push([BPointId, DPointId]);
|
|
4500
|
+
}
|
|
4501
|
+
for (const [EPointId, FPointId] of sharedSegments) {
|
|
4502
|
+
operations.push({
|
|
4503
|
+
type: "swap_position_on_segment",
|
|
4504
|
+
segmentPointIds: [EPointId, FPointId]
|
|
4505
|
+
});
|
|
4506
|
+
}
|
|
4507
|
+
const Amutable = this.unravelSection.mutableSegmentIds.has(A.segmentId);
|
|
4508
|
+
const Bmutable = this.unravelSection.mutableSegmentIds.has(B.segmentId);
|
|
4509
|
+
const Cmutable = this.unravelSection.mutableSegmentIds.has(C.segmentId);
|
|
4510
|
+
const Dmutable = this.unravelSection.mutableSegmentIds.has(D.segmentId);
|
|
4511
|
+
if (Amutable && Bmutable) {
|
|
4512
|
+
operations.push({
|
|
4513
|
+
type: "change_layer",
|
|
4514
|
+
newZ: A.z === 0 ? 1 : 0,
|
|
4515
|
+
segmentPointIds: [APointId, BPointId]
|
|
4516
|
+
});
|
|
4517
|
+
}
|
|
4518
|
+
if (Cmutable && Dmutable) {
|
|
4519
|
+
operations.push({
|
|
4520
|
+
type: "change_layer",
|
|
4521
|
+
newZ: C.z === 0 ? 1 : 0,
|
|
4522
|
+
segmentPointIds: [CPointId, DPointId]
|
|
4523
|
+
});
|
|
4524
|
+
}
|
|
4525
|
+
if (Amutable) {
|
|
4526
|
+
operations.push({
|
|
4527
|
+
type: "change_layer",
|
|
4528
|
+
newZ: A.z === 0 ? 1 : 0,
|
|
4529
|
+
segmentPointIds: [APointId]
|
|
4530
|
+
});
|
|
4531
|
+
}
|
|
4532
|
+
if (Bmutable) {
|
|
4533
|
+
operations.push({
|
|
4534
|
+
type: "change_layer",
|
|
4535
|
+
newZ: B.z === 0 ? 1 : 0,
|
|
4536
|
+
segmentPointIds: [BPointId]
|
|
4537
|
+
});
|
|
4538
|
+
}
|
|
4539
|
+
if (Cmutable) {
|
|
4540
|
+
operations.push({
|
|
4541
|
+
type: "change_layer",
|
|
4542
|
+
newZ: C.z === 0 ? 1 : 0,
|
|
4543
|
+
segmentPointIds: [CPointId]
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
if (Dmutable) {
|
|
4547
|
+
operations.push({
|
|
4548
|
+
type: "change_layer",
|
|
4549
|
+
newZ: D.z === 0 ? 1 : 0,
|
|
4550
|
+
segmentPointIds: [DPointId]
|
|
4551
|
+
});
|
|
4552
|
+
}
|
|
4553
|
+
}
|
|
4554
|
+
return operations;
|
|
4555
|
+
}
|
|
4556
|
+
computeG(params) {
|
|
4557
|
+
const { issues, originalCandidate, operationsPerformed, operation } = params;
|
|
4558
|
+
const nodeProblemCounts = /* @__PURE__ */ new Map();
|
|
4559
|
+
for (const issue of issues) {
|
|
4560
|
+
if (!nodeProblemCounts.has(issue.capacityMeshNodeId)) {
|
|
4561
|
+
nodeProblemCounts.set(issue.capacityMeshNodeId, {
|
|
4562
|
+
numTransitionCrossings: 0,
|
|
4563
|
+
numSameLayerCrossings: 0,
|
|
4564
|
+
numEntryExitLayerChanges: 0
|
|
4565
|
+
});
|
|
4566
|
+
}
|
|
4567
|
+
const nodeProblemCount = nodeProblemCounts.get(issue.capacityMeshNodeId);
|
|
4568
|
+
if (issue.type === "transition_via") {
|
|
4569
|
+
nodeProblemCount.numTransitionCrossings++;
|
|
4570
|
+
} else if (issue.type === "same_layer_crossing") {
|
|
4571
|
+
nodeProblemCount.numSameLayerCrossings++;
|
|
4572
|
+
} else if (issue.type === "double_transition_crossing" || issue.type === "single_transition_crossing") {
|
|
4573
|
+
nodeProblemCount.numEntryExitLayerChanges++;
|
|
4574
|
+
} else if (issue.type === "same_layer_trace_imbalance_with_low_capacity") {
|
|
4575
|
+
}
|
|
4576
|
+
}
|
|
4577
|
+
let cost = 0;
|
|
4578
|
+
for (const [
|
|
4579
|
+
nodeId,
|
|
4580
|
+
{
|
|
4581
|
+
numEntryExitLayerChanges,
|
|
4582
|
+
numSameLayerCrossings,
|
|
4583
|
+
numTransitionCrossings
|
|
4584
|
+
}
|
|
4585
|
+
] of nodeProblemCounts) {
|
|
4586
|
+
const estNumVias = numSameLayerCrossings * 0.82 + numEntryExitLayerChanges * 0.41 + numTransitionCrossings * 0.2;
|
|
4587
|
+
const estUsedCapacity = (estNumVias / 2) ** 1.1;
|
|
4588
|
+
const totalCapacity = this.tunedNodeCapacityMap.get(nodeId);
|
|
4589
|
+
const estPf = estUsedCapacity / totalCapacity;
|
|
4590
|
+
cost += getLogProbability(estPf);
|
|
4591
|
+
}
|
|
4592
|
+
return cost;
|
|
4593
|
+
}
|
|
4594
|
+
getNeighborByApplyingOperation(currentCandidate, operation) {
|
|
4595
|
+
const pointModifications = new Map(currentCandidate.pointModifications);
|
|
4596
|
+
applyOperationToPointModifications(
|
|
4597
|
+
pointModifications,
|
|
4598
|
+
operation,
|
|
4599
|
+
(segmentPointId) => this.getPointInCandidate(currentCandidate, segmentPointId)
|
|
4600
|
+
);
|
|
4601
|
+
const issues = getIssuesInSection(
|
|
4602
|
+
this.unravelSection,
|
|
4603
|
+
this.nodeMap,
|
|
4604
|
+
pointModifications
|
|
4605
|
+
);
|
|
4606
|
+
const operationsPerformed = currentCandidate.operationsPerformed + 1;
|
|
4607
|
+
const g = this.computeG({
|
|
4608
|
+
issues,
|
|
4609
|
+
originalCandidate: currentCandidate,
|
|
4610
|
+
operationsPerformed,
|
|
4611
|
+
operation
|
|
4612
|
+
});
|
|
4613
|
+
return {
|
|
4614
|
+
issues,
|
|
4615
|
+
g,
|
|
4616
|
+
h: 0,
|
|
4617
|
+
f: g,
|
|
4618
|
+
pointModifications,
|
|
4619
|
+
candidateHash: createPointModificationsHash(pointModifications),
|
|
4620
|
+
// TODO PERFORMANCE allow disabling this
|
|
4621
|
+
// candidateFullHash: createFullPointModificationsHash(
|
|
4622
|
+
// this.unravelSection.segmentPointMap,
|
|
4623
|
+
// pointModifications,
|
|
4624
|
+
// ),
|
|
4625
|
+
operationsPerformed
|
|
4429
4626
|
};
|
|
4430
|
-
|
|
4431
|
-
|
|
4627
|
+
}
|
|
4628
|
+
getNeighborOperationsForCandidate(candidate) {
|
|
4629
|
+
return candidate.issues.flatMap(
|
|
4630
|
+
(issue) => this.getOperationsForIssue(candidate, issue)
|
|
4631
|
+
);
|
|
4632
|
+
}
|
|
4633
|
+
getNeighbors(candidate) {
|
|
4634
|
+
const neighbors = [];
|
|
4635
|
+
const operations = this.getNeighborOperationsForCandidate(candidate);
|
|
4636
|
+
for (const operation of operations) {
|
|
4637
|
+
const neighbor = this.getNeighborByApplyingOperation(candidate, operation);
|
|
4638
|
+
neighbors.push(neighbor);
|
|
4639
|
+
}
|
|
4640
|
+
return neighbors;
|
|
4432
4641
|
}
|
|
4433
4642
|
_step() {
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
x: this.end.x,
|
|
4437
|
-
y: this.end.y,
|
|
4438
|
-
z: this.end.z
|
|
4439
|
-
});
|
|
4643
|
+
const candidate = this.candidates.shift();
|
|
4644
|
+
if (!candidate) {
|
|
4440
4645
|
this.solved = true;
|
|
4441
4646
|
return;
|
|
4442
4647
|
}
|
|
4443
|
-
|
|
4444
|
-
|
|
4445
|
-
|
|
4446
|
-
let closestDistance = Infinity;
|
|
4447
|
-
for (let i = 0; i < this.remainingHdRoutes.length; i++) {
|
|
4448
|
-
const hdRoute = this.remainingHdRoutes[i];
|
|
4449
|
-
const lastPointInCandidate = hdRoute.route[hdRoute.route.length - 1];
|
|
4450
|
-
const firstPointInCandidate = hdRoute.route[0];
|
|
4451
|
-
const distToFirst = distance(lastMergedPoint, firstPointInCandidate);
|
|
4452
|
-
const distToLast = distance(lastMergedPoint, lastPointInCandidate);
|
|
4453
|
-
if (distToFirst < closestDistance) {
|
|
4454
|
-
closestDistance = distToFirst;
|
|
4455
|
-
closestRouteIndex = i;
|
|
4456
|
-
matchedOn = "first";
|
|
4457
|
-
}
|
|
4458
|
-
if (distToLast < closestDistance) {
|
|
4459
|
-
closestDistance = distToLast;
|
|
4460
|
-
closestRouteIndex = i;
|
|
4461
|
-
matchedOn = "last";
|
|
4462
|
-
}
|
|
4463
|
-
}
|
|
4464
|
-
const hdRouteToMerge = this.remainingHdRoutes[closestRouteIndex];
|
|
4465
|
-
this.remainingHdRoutes.splice(closestRouteIndex, 1);
|
|
4466
|
-
if (matchedOn === "first") {
|
|
4467
|
-
this.mergedHdRoute.route.push(...hdRouteToMerge.route);
|
|
4468
|
-
} else {
|
|
4469
|
-
this.mergedHdRoute.route.push(...[...hdRouteToMerge.route].reverse());
|
|
4648
|
+
this.lastProcessedCandidate = candidate;
|
|
4649
|
+
if (candidate.f < (this.bestCandidate?.f ?? Infinity)) {
|
|
4650
|
+
this.bestCandidate = candidate;
|
|
4470
4651
|
}
|
|
4471
|
-
this.
|
|
4652
|
+
this.getNeighbors(candidate).forEach((neighbor) => {
|
|
4653
|
+
const isPartialHashExplored = this.queuedOrExploredCandidatePointModificationHashes.has(
|
|
4654
|
+
neighbor.candidateHash
|
|
4655
|
+
);
|
|
4656
|
+
if (isPartialHashExplored) return;
|
|
4657
|
+
this.queuedOrExploredCandidatePointModificationHashes.add(
|
|
4658
|
+
neighbor.candidateHash
|
|
4659
|
+
);
|
|
4660
|
+
this.candidates.push(neighbor);
|
|
4661
|
+
});
|
|
4662
|
+
this.candidates.sort((a, b) => a.f - b.f);
|
|
4663
|
+
this.candidates.length = Math.min(
|
|
4664
|
+
this.candidates.length,
|
|
4665
|
+
this.MAX_CANDIDATES
|
|
4666
|
+
);
|
|
4472
4667
|
}
|
|
4473
4668
|
visualize() {
|
|
4474
4669
|
const graphics = {
|
|
4475
4670
|
points: [],
|
|
4476
4671
|
lines: [],
|
|
4672
|
+
rects: [],
|
|
4477
4673
|
circles: [],
|
|
4478
|
-
|
|
4674
|
+
coordinateSystem: "cartesian",
|
|
4675
|
+
title: "Unravel Section Solver"
|
|
4479
4676
|
};
|
|
4480
|
-
|
|
4481
|
-
|
|
4482
|
-
|
|
4483
|
-
|
|
4484
|
-
|
|
4485
|
-
|
|
4486
|
-
}
|
|
4487
|
-
|
|
4488
|
-
x: this.end.x,
|
|
4489
|
-
y: this.end.y,
|
|
4490
|
-
color: "red",
|
|
4491
|
-
label: "End"
|
|
4677
|
+
let candidate = null;
|
|
4678
|
+
if (this.selectedCandidateIndex !== null) {
|
|
4679
|
+
if (this.selectedCandidateIndex === "best") {
|
|
4680
|
+
candidate = this.bestCandidate;
|
|
4681
|
+
} else if (this.selectedCandidateIndex === "original") {
|
|
4682
|
+
candidate = this.originalCandidate;
|
|
4683
|
+
} else {
|
|
4684
|
+
candidate = this.candidates[this.selectedCandidateIndex];
|
|
4492
4685
|
}
|
|
4493
|
-
|
|
4494
|
-
|
|
4495
|
-
|
|
4496
|
-
|
|
4497
|
-
|
|
4498
|
-
|
|
4499
|
-
|
|
4500
|
-
|
|
4686
|
+
} else {
|
|
4687
|
+
candidate = this.lastProcessedCandidate || this.candidates[0];
|
|
4688
|
+
}
|
|
4689
|
+
if (!candidate) return graphics;
|
|
4690
|
+
const modifiedSegmentPoints = /* @__PURE__ */ new Map();
|
|
4691
|
+
for (const [segmentPointId, segmentPoint] of this.unravelSection.segmentPointMap) {
|
|
4692
|
+
const modifiedPoint = { ...segmentPoint };
|
|
4693
|
+
const modification = candidate.pointModifications.get(segmentPointId);
|
|
4694
|
+
if (modification) {
|
|
4695
|
+
if (modification.x !== void 0) modifiedPoint.x = modification.x;
|
|
4696
|
+
if (modification.y !== void 0) modifiedPoint.y = modification.y;
|
|
4697
|
+
if (modification.z !== void 0) modifiedPoint.z = modification.z;
|
|
4698
|
+
}
|
|
4699
|
+
modifiedSegmentPoints.set(segmentPointId, modifiedPoint);
|
|
4700
|
+
}
|
|
4701
|
+
for (const [segmentPointId, segmentPoint] of modifiedSegmentPoints) {
|
|
4702
|
+
graphics.points.push({
|
|
4703
|
+
x: segmentPoint.x,
|
|
4704
|
+
y: segmentPoint.y,
|
|
4705
|
+
label: `${segmentPointId}
|
|
4706
|
+
Segment: ${segmentPoint.segmentId} ${this.unravelSection.mutableSegmentIds.has(segmentPoint.segmentId) ? "MUTABLE" : "IMMUTABLE"}
|
|
4707
|
+
Layer: ${segmentPoint.z}`,
|
|
4708
|
+
color: this.colorMap[segmentPoint.connectionName] || "#000"
|
|
4501
4709
|
});
|
|
4502
|
-
|
|
4503
|
-
|
|
4504
|
-
|
|
4505
|
-
|
|
4506
|
-
|
|
4710
|
+
}
|
|
4711
|
+
for (const nodeId of this.unravelSection.allNodeIds) {
|
|
4712
|
+
const node = this.nodeMap.get(nodeId);
|
|
4713
|
+
const isMutable = this.unravelSection.mutableNodeIds.includes(nodeId);
|
|
4714
|
+
graphics.rects.push({
|
|
4715
|
+
center: node.center,
|
|
4716
|
+
label: `${nodeId}
|
|
4717
|
+
${node.width.toFixed(2)}x${node.height.toFixed(2)}
|
|
4718
|
+
${isMutable ? "MUTABLE" : "IMMUTABLE"}`,
|
|
4719
|
+
color: isMutable ? "green" : "red",
|
|
4720
|
+
width: node.width / 8,
|
|
4721
|
+
height: node.height / 8
|
|
4722
|
+
});
|
|
4723
|
+
}
|
|
4724
|
+
for (const [segmentId, segmentPointIds] of this.unravelSection.segmentPointsInSegment) {
|
|
4725
|
+
if (segmentPointIds.length <= 1) continue;
|
|
4726
|
+
const points = segmentPointIds.map(
|
|
4727
|
+
(spId) => modifiedSegmentPoints.get(spId)
|
|
4728
|
+
);
|
|
4729
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
4730
|
+
graphics.lines.push({
|
|
4731
|
+
points: [
|
|
4732
|
+
{ x: points[i].x, y: points[i].y },
|
|
4733
|
+
{ x: points[i + 1].x, y: points[i + 1].y }
|
|
4734
|
+
],
|
|
4735
|
+
strokeColor: this.colorMap[segmentId] || "#000"
|
|
4507
4736
|
});
|
|
4508
4737
|
}
|
|
4509
|
-
|
|
4510
|
-
|
|
4511
|
-
|
|
4512
|
-
|
|
4513
|
-
|
|
4514
|
-
|
|
4738
|
+
}
|
|
4739
|
+
for (const [segmentPointId, segmentPoint] of modifiedSegmentPoints) {
|
|
4740
|
+
for (const connectedPointId of segmentPoint.directlyConnectedSegmentPointIds) {
|
|
4741
|
+
if (segmentPointId < connectedPointId) {
|
|
4742
|
+
const connectedPoint = modifiedSegmentPoints.get(connectedPointId);
|
|
4743
|
+
const sameLayer = segmentPoint.z === connectedPoint.z;
|
|
4744
|
+
const commonLayer = segmentPoint.z;
|
|
4745
|
+
let strokeDash;
|
|
4746
|
+
if (sameLayer) {
|
|
4747
|
+
strokeDash = commonLayer === 0 ? void 0 : "10 5";
|
|
4748
|
+
} else {
|
|
4749
|
+
strokeDash = "3 3 10";
|
|
4750
|
+
}
|
|
4751
|
+
graphics.lines.push({
|
|
4752
|
+
points: [
|
|
4753
|
+
{ x: segmentPoint.x, y: segmentPoint.y },
|
|
4754
|
+
{ x: connectedPoint.x, y: connectedPoint.y }
|
|
4755
|
+
],
|
|
4756
|
+
strokeDash,
|
|
4757
|
+
strokeColor: this.colorMap[segmentPoint.connectionName] || "#000"
|
|
4758
|
+
});
|
|
4759
|
+
}
|
|
4515
4760
|
}
|
|
4516
4761
|
}
|
|
4517
|
-
const
|
|
4518
|
-
|
|
4519
|
-
|
|
4520
|
-
|
|
4521
|
-
|
|
4522
|
-
|
|
4523
|
-
|
|
4524
|
-
|
|
4525
|
-
|
|
4526
|
-
|
|
4762
|
+
for (const issue of candidate.issues) {
|
|
4763
|
+
const node = this.nodeMap.get(issue.capacityMeshNodeId);
|
|
4764
|
+
if (issue.type === "transition_via") {
|
|
4765
|
+
for (const segmentPointId of issue.segmentPoints) {
|
|
4766
|
+
const segmentPoint = modifiedSegmentPoints.get(segmentPointId);
|
|
4767
|
+
graphics.circles.push({
|
|
4768
|
+
center: { x: segmentPoint.x, y: segmentPoint.y },
|
|
4769
|
+
radius: node.width / 16,
|
|
4770
|
+
stroke: "#ff0000",
|
|
4771
|
+
fill: "rgba(255, 0, 0, 0.2)",
|
|
4772
|
+
label: `Via Issue
|
|
4773
|
+
${segmentPointId}
|
|
4774
|
+
Layer: ${segmentPoint.z}`
|
|
4775
|
+
});
|
|
4776
|
+
}
|
|
4777
|
+
} else if (issue.type === "same_layer_crossing") {
|
|
4778
|
+
for (const [sp1Id, sp2Id] of [
|
|
4779
|
+
issue.crossingLine1,
|
|
4780
|
+
issue.crossingLine2
|
|
4781
|
+
]) {
|
|
4782
|
+
const sp1 = modifiedSegmentPoints.get(sp1Id);
|
|
4783
|
+
const sp2 = modifiedSegmentPoints.get(sp2Id);
|
|
4784
|
+
graphics.lines.push({
|
|
4785
|
+
points: [
|
|
4786
|
+
{ x: sp1.x, y: sp1.y },
|
|
4787
|
+
{ x: sp2.x, y: sp2.y }
|
|
4788
|
+
],
|
|
4789
|
+
strokeColor: "rgba(255,0,0,0.2)",
|
|
4790
|
+
strokeWidth: node.width / 32
|
|
4791
|
+
});
|
|
4792
|
+
}
|
|
4527
4793
|
}
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4794
|
+
}
|
|
4795
|
+
for (const [segmentPointId, modification] of candidate.pointModifications) {
|
|
4796
|
+
const modifiedPoint = modifiedSegmentPoints.get(segmentPointId);
|
|
4797
|
+
const originalPoint = this.unravelSection.segmentPointMap.get(segmentPointId);
|
|
4798
|
+
graphics.circles.push({
|
|
4799
|
+
center: { x: modifiedPoint.x, y: modifiedPoint.y },
|
|
4800
|
+
radius: 0.05,
|
|
4801
|
+
stroke: "#0000ff",
|
|
4802
|
+
fill: "rgba(0, 0, 255, 0.2)",
|
|
4803
|
+
label: `${segmentPointId}
|
|
4804
|
+
Original: (${originalPoint.x.toFixed(2)}, ${originalPoint.y.toFixed(2)}, ${originalPoint.z})
|
|
4805
|
+
New: (${modifiedPoint.x.toFixed(2)}, ${modifiedPoint.y.toFixed(2)}, ${modifiedPoint.z})`
|
|
4806
|
+
});
|
|
4807
|
+
}
|
|
4808
|
+
return graphics;
|
|
4809
|
+
}
|
|
4810
|
+
};
|
|
4811
|
+
|
|
4812
|
+
// lib/solvers/UnravelSolver/getDedupedSegments.ts
|
|
4813
|
+
var getDedupedSegments = (assignedSegments) => {
|
|
4814
|
+
const dedupedSegments = [];
|
|
4815
|
+
const dedupedSegPointMap = /* @__PURE__ */ new Map();
|
|
4816
|
+
let highestSegmentId = -1;
|
|
4817
|
+
for (const seg of assignedSegments) {
|
|
4818
|
+
const segKey = `${seg.start.x}-${seg.start.y}-${seg.end.x}-${seg.end.y}`;
|
|
4819
|
+
const existingSeg = dedupedSegPointMap.get(segKey);
|
|
4820
|
+
if (!existingSeg) {
|
|
4821
|
+
highestSegmentId++;
|
|
4822
|
+
seg.nodePortSegmentId = `SEG${highestSegmentId}`;
|
|
4823
|
+
dedupedSegPointMap.set(segKey, seg);
|
|
4824
|
+
dedupedSegments.push(seg);
|
|
4825
|
+
continue;
|
|
4826
|
+
}
|
|
4827
|
+
seg.nodePortSegmentId = existingSeg.nodePortSegmentId;
|
|
4828
|
+
}
|
|
4829
|
+
return dedupedSegments;
|
|
4830
|
+
};
|
|
4831
|
+
|
|
4832
|
+
// lib/utils/getIntraNodeCrossingsFromSegments.ts
|
|
4833
|
+
var getIntraNodeCrossingsFromSegments = (segments) => {
|
|
4834
|
+
let numSameLayerCrossings = 0;
|
|
4835
|
+
const pointPairs = [];
|
|
4836
|
+
const transitionPairPoints = [];
|
|
4837
|
+
let numEntryExitLayerChanges = 0;
|
|
4838
|
+
const portPoints = segments.flatMap((seg) => seg.assignedPoints);
|
|
4839
|
+
for (const { connectionName: aConnName, point: A } of portPoints) {
|
|
4840
|
+
if (pointPairs.some((p) => p.connectionName === aConnName)) {
|
|
4841
|
+
continue;
|
|
4842
|
+
}
|
|
4843
|
+
if (transitionPairPoints.some((p) => p.connectionName === aConnName)) {
|
|
4844
|
+
continue;
|
|
4845
|
+
}
|
|
4846
|
+
const pointPair = {
|
|
4847
|
+
connectionName: aConnName,
|
|
4848
|
+
z: A.z,
|
|
4849
|
+
points: [A]
|
|
4850
|
+
};
|
|
4851
|
+
for (const { connectionName: bConnName, point: B } of portPoints) {
|
|
4852
|
+
if (aConnName !== bConnName) continue;
|
|
4853
|
+
if (A === B) continue;
|
|
4854
|
+
pointPair.points.push(B);
|
|
4855
|
+
if (pointPair.points.some((p) => p.z !== pointPair.z)) {
|
|
4856
|
+
numEntryExitLayerChanges++;
|
|
4857
|
+
transitionPairPoints.push(pointPair);
|
|
4858
|
+
break;
|
|
4859
|
+
} else {
|
|
4860
|
+
pointPairs.push(pointPair);
|
|
4861
|
+
break;
|
|
4536
4862
|
}
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
for (let i = 0; i < pointPairs.length; i++) {
|
|
4866
|
+
for (let j = i + 1; j < pointPairs.length; j++) {
|
|
4867
|
+
const pair1 = pointPairs[i];
|
|
4868
|
+
const pair2 = pointPairs[j];
|
|
4869
|
+
if (pair1.z === pair2.z && doSegmentsIntersect(
|
|
4870
|
+
pair1.points[0],
|
|
4871
|
+
pair1.points[1],
|
|
4872
|
+
pair2.points[0],
|
|
4873
|
+
pair2.points[1]
|
|
4874
|
+
)) {
|
|
4875
|
+
numSameLayerCrossings++;
|
|
4876
|
+
}
|
|
4877
|
+
}
|
|
4878
|
+
}
|
|
4879
|
+
let numTransitionCrossings = 0;
|
|
4880
|
+
for (let i = 0; i < transitionPairPoints.length; i++) {
|
|
4881
|
+
for (let j = i + 1; j < transitionPairPoints.length; j++) {
|
|
4882
|
+
const pair1 = transitionPairPoints[i];
|
|
4883
|
+
const pair2 = transitionPairPoints[j];
|
|
4884
|
+
if (doSegmentsIntersect(
|
|
4885
|
+
pair1.points[0],
|
|
4886
|
+
pair1.points[1],
|
|
4887
|
+
pair2.points[0],
|
|
4888
|
+
pair2.points[1]
|
|
4889
|
+
)) {
|
|
4890
|
+
numTransitionCrossings++;
|
|
4891
|
+
}
|
|
4892
|
+
}
|
|
4893
|
+
}
|
|
4894
|
+
for (let i = 0; i < transitionPairPoints.length; i++) {
|
|
4895
|
+
for (let j = 0; j < pointPairs.length; j++) {
|
|
4896
|
+
const pair1 = transitionPairPoints[i];
|
|
4897
|
+
const pair2 = pointPairs[j];
|
|
4898
|
+
if (doSegmentsIntersect(
|
|
4899
|
+
pair1.points[0],
|
|
4900
|
+
pair1.points[1],
|
|
4901
|
+
pair2.points[0],
|
|
4902
|
+
pair2.points[1]
|
|
4903
|
+
)) {
|
|
4904
|
+
numTransitionCrossings++;
|
|
4543
4905
|
}
|
|
4544
4906
|
}
|
|
4545
|
-
return graphics;
|
|
4546
4907
|
}
|
|
4547
|
-
|
|
4548
|
-
|
|
4549
|
-
|
|
4550
|
-
|
|
4551
|
-
|
|
4552
|
-
|
|
4553
|
-
|
|
4554
|
-
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4908
|
+
return {
|
|
4909
|
+
numSameLayerCrossings,
|
|
4910
|
+
numEntryExitLayerChanges,
|
|
4911
|
+
numTransitionCrossings
|
|
4912
|
+
};
|
|
4913
|
+
};
|
|
4914
|
+
|
|
4915
|
+
// lib/solvers/UnravelSolver/calculateCrossingProbabilityOfFailure.ts
|
|
4916
|
+
var calculateNodeProbabilityOfFailure = (node, numSameLayerCrossings, numEntryExitLayerChanges, numTransitionCrossings) => {
|
|
4917
|
+
if (node?._containsTarget) return 0;
|
|
4918
|
+
const totalCapacity = getTunedTotalCapacity1(node);
|
|
4919
|
+
const estNumVias = numSameLayerCrossings * 0.82 + numEntryExitLayerChanges * 0.41 + numTransitionCrossings * 0.2;
|
|
4920
|
+
const estUsedCapacity = (estNumVias / 2) ** 1.1;
|
|
4921
|
+
const approxProb = estUsedCapacity / totalCapacity;
|
|
4922
|
+
return approxProb;
|
|
4923
|
+
};
|
|
4924
|
+
|
|
4925
|
+
// lib/solvers/UnravelSolver/UnravelMultiSectionSolver.ts
|
|
4926
|
+
var UnravelMultiSectionSolver = class extends BaseSolver {
|
|
4927
|
+
nodeMap;
|
|
4928
|
+
dedupedSegments;
|
|
4929
|
+
nodeIdToSegmentIds;
|
|
4930
|
+
segmentIdToNodeIds;
|
|
4931
|
+
colorMap;
|
|
4932
|
+
tunedNodeCapacityMap;
|
|
4933
|
+
MAX_NODE_ATTEMPTS = 2;
|
|
4934
|
+
MUTABLE_HOPS = 1;
|
|
4935
|
+
ACCEPTABLE_PF = 0.05;
|
|
4936
|
+
/**
|
|
4937
|
+
* Probability of failure for each node
|
|
4938
|
+
*/
|
|
4939
|
+
nodePfMap;
|
|
4940
|
+
attemptsToFixNode;
|
|
4941
|
+
activeSolver = null;
|
|
4942
|
+
segmentPointMap;
|
|
4943
|
+
constructor({
|
|
4944
|
+
assignedSegments,
|
|
4945
|
+
colorMap,
|
|
4946
|
+
nodes
|
|
4947
|
+
}) {
|
|
4948
|
+
super();
|
|
4949
|
+
this.MAX_ITERATIONS = 1e5;
|
|
4950
|
+
this.dedupedSegments = getDedupedSegments(assignedSegments);
|
|
4951
|
+
this.nodeMap = /* @__PURE__ */ new Map();
|
|
4952
|
+
for (const node of nodes) {
|
|
4953
|
+
this.nodeMap.set(node.capacityMeshNodeId, node);
|
|
4954
|
+
}
|
|
4955
|
+
this.nodeIdToSegmentIds = /* @__PURE__ */ new Map();
|
|
4956
|
+
this.segmentIdToNodeIds = /* @__PURE__ */ new Map();
|
|
4957
|
+
this.attemptsToFixNode = /* @__PURE__ */ new Map();
|
|
4958
|
+
for (const segment of assignedSegments) {
|
|
4959
|
+
this.segmentIdToNodeIds.set(segment.nodePortSegmentId, [
|
|
4960
|
+
...this.segmentIdToNodeIds.get(segment.nodePortSegmentId) ?? [],
|
|
4961
|
+
segment.capacityMeshNodeId
|
|
4962
|
+
]);
|
|
4963
|
+
this.nodeIdToSegmentIds.set(segment.capacityMeshNodeId, [
|
|
4964
|
+
...this.nodeIdToSegmentIds.get(segment.capacityMeshNodeId) ?? [],
|
|
4965
|
+
segment.nodePortSegmentId
|
|
4966
|
+
]);
|
|
4967
|
+
}
|
|
4968
|
+
this.colorMap = colorMap ?? {};
|
|
4969
|
+
this.tunedNodeCapacityMap = /* @__PURE__ */ new Map();
|
|
4970
|
+
for (const [nodeId, node] of this.nodeMap) {
|
|
4971
|
+
this.tunedNodeCapacityMap.set(nodeId, getTunedTotalCapacity1(node));
|
|
4972
|
+
}
|
|
4973
|
+
this.segmentPointMap = createSegmentPointMap(
|
|
4974
|
+
this.dedupedSegments,
|
|
4975
|
+
this.segmentIdToNodeIds
|
|
4976
|
+
);
|
|
4977
|
+
this.nodePfMap = this.computeInitialPfMap();
|
|
4978
|
+
}
|
|
4979
|
+
computeInitialPfMap() {
|
|
4980
|
+
const pfMap = /* @__PURE__ */ new Map();
|
|
4981
|
+
for (const [nodeId, node] of this.nodeMap.entries()) {
|
|
4982
|
+
pfMap.set(nodeId, this.computeNodePf(node));
|
|
4983
|
+
}
|
|
4984
|
+
return pfMap;
|
|
4985
|
+
}
|
|
4986
|
+
computeNodePf(node) {
|
|
4987
|
+
const {
|
|
4988
|
+
numSameLayerCrossings,
|
|
4989
|
+
numEntryExitLayerChanges,
|
|
4990
|
+
numTransitionCrossings
|
|
4991
|
+
} = getIntraNodeCrossingsFromSegments(
|
|
4992
|
+
this.dedupedSegments.filter(
|
|
4993
|
+
(seg) => this.segmentIdToNodeIds.get(seg.nodePortSegmentId).includes(node.capacityMeshNodeId)
|
|
4994
|
+
)
|
|
4995
|
+
);
|
|
4996
|
+
const probabilityOfFailure = calculateNodeProbabilityOfFailure(
|
|
4997
|
+
node,
|
|
4998
|
+
numSameLayerCrossings,
|
|
4999
|
+
numEntryExitLayerChanges,
|
|
5000
|
+
numTransitionCrossings
|
|
5001
|
+
);
|
|
5002
|
+
return probabilityOfFailure;
|
|
4568
5003
|
}
|
|
4569
5004
|
_step() {
|
|
4570
|
-
if (this.
|
|
4571
|
-
this.activeSolver.step();
|
|
4572
|
-
if (this.activeSolver.solved) {
|
|
4573
|
-
this.mergedHdRoutes.push(this.activeSolver.mergedHdRoute);
|
|
4574
|
-
this.activeSolver = null;
|
|
4575
|
-
} else if (this.activeSolver.failed) {
|
|
4576
|
-
this.failed = true;
|
|
4577
|
-
this.error = this.activeSolver.error;
|
|
4578
|
-
}
|
|
4579
|
-
return;
|
|
4580
|
-
}
|
|
4581
|
-
const unsolvedRoute = this.unsolvedRoutes.pop();
|
|
4582
|
-
if (!unsolvedRoute) {
|
|
5005
|
+
if (this.iterations >= this.MAX_ITERATIONS - 1) {
|
|
4583
5006
|
this.solved = true;
|
|
4584
5007
|
return;
|
|
4585
5008
|
}
|
|
4586
|
-
if (
|
|
4587
|
-
|
|
4588
|
-
|
|
5009
|
+
if (!this.activeSolver) {
|
|
5010
|
+
let highestPfNodeId = null;
|
|
5011
|
+
let highestPf = 0;
|
|
5012
|
+
for (const [nodeId, pf] of this.nodePfMap.entries()) {
|
|
5013
|
+
const pfReduced = pf * (1 - (this.attemptsToFixNode.get(nodeId) ?? 0) / this.MAX_NODE_ATTEMPTS);
|
|
5014
|
+
if (pfReduced > highestPf) {
|
|
5015
|
+
highestPf = pf;
|
|
5016
|
+
highestPfNodeId = nodeId;
|
|
5017
|
+
}
|
|
5018
|
+
}
|
|
5019
|
+
if (!highestPfNodeId || highestPf < this.ACCEPTABLE_PF) {
|
|
5020
|
+
this.solved = true;
|
|
5021
|
+
return;
|
|
5022
|
+
}
|
|
5023
|
+
this.attemptsToFixNode.set(
|
|
5024
|
+
highestPfNodeId,
|
|
5025
|
+
(this.attemptsToFixNode.get(highestPfNodeId) ?? 0) + 1
|
|
5026
|
+
);
|
|
5027
|
+
this.activeSolver = new UnravelSectionSolver({
|
|
5028
|
+
dedupedSegments: this.dedupedSegments,
|
|
5029
|
+
nodeMap: this.nodeMap,
|
|
5030
|
+
nodeIdToSegmentIds: this.nodeIdToSegmentIds,
|
|
5031
|
+
segmentIdToNodeIds: this.segmentIdToNodeIds,
|
|
5032
|
+
colorMap: this.colorMap,
|
|
5033
|
+
rootNodeId: highestPfNodeId,
|
|
5034
|
+
MUTABLE_HOPS: this.MUTABLE_HOPS,
|
|
5035
|
+
segmentPointMap: this.segmentPointMap
|
|
5036
|
+
});
|
|
5037
|
+
}
|
|
5038
|
+
this.activeSolver.step();
|
|
5039
|
+
const { bestCandidate, originalCandidate, lastProcessedCandidate } = this.activeSolver;
|
|
5040
|
+
const giveUpFactor = 1 + 4 * (1 - Math.min(1, this.activeSolver.iterations / 40));
|
|
5041
|
+
const shouldEarlyStop = lastProcessedCandidate && lastProcessedCandidate.g > bestCandidate.g * giveUpFactor;
|
|
5042
|
+
if (this.activeSolver.solved || shouldEarlyStop) {
|
|
5043
|
+
const foundBetterSolution = bestCandidate && bestCandidate.g < originalCandidate.g;
|
|
5044
|
+
if (foundBetterSolution) {
|
|
5045
|
+
for (const [
|
|
5046
|
+
segmentPointId,
|
|
5047
|
+
pointModification
|
|
5048
|
+
] of bestCandidate.pointModifications.entries()) {
|
|
5049
|
+
const segmentPoint = this.segmentPointMap.get(segmentPointId);
|
|
5050
|
+
segmentPoint.x = pointModification.x ?? segmentPoint.x;
|
|
5051
|
+
segmentPoint.y = pointModification.y ?? segmentPoint.y;
|
|
5052
|
+
segmentPoint.z = pointModification.z ?? segmentPoint.z;
|
|
5053
|
+
}
|
|
5054
|
+
}
|
|
5055
|
+
for (const nodeId of this.activeSolver.unravelSection.allNodeIds) {
|
|
5056
|
+
this.nodePfMap.set(
|
|
5057
|
+
nodeId,
|
|
5058
|
+
this.computeNodePf(this.nodeMap.get(nodeId))
|
|
5059
|
+
);
|
|
5060
|
+
}
|
|
5061
|
+
this.activeSolver = null;
|
|
4589
5062
|
}
|
|
4590
|
-
this.activeSolver = new SingleHighDensityRouteStitchSolver({
|
|
4591
|
-
hdRoutes: unsolvedRoute.hdRoutes,
|
|
4592
|
-
start: unsolvedRoute.start,
|
|
4593
|
-
end: unsolvedRoute.end
|
|
4594
|
-
});
|
|
4595
5063
|
}
|
|
4596
5064
|
visualize() {
|
|
5065
|
+
if (this.activeSolver) {
|
|
5066
|
+
return this.activeSolver.visualize();
|
|
5067
|
+
}
|
|
4597
5068
|
const graphics = {
|
|
4598
|
-
points: [],
|
|
4599
5069
|
lines: [],
|
|
5070
|
+
points: [],
|
|
5071
|
+
rects: [],
|
|
4600
5072
|
circles: [],
|
|
4601
|
-
|
|
5073
|
+
coordinateSystem: "cartesian",
|
|
5074
|
+
title: "Unravel Multi Section Solver"
|
|
4602
5075
|
};
|
|
4603
|
-
|
|
4604
|
-
const
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
4612
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
5076
|
+
for (const [nodeId, node] of this.nodeMap.entries()) {
|
|
5077
|
+
const probabilityOfFailure = this.nodePfMap.get(nodeId) || 0;
|
|
5078
|
+
const pf = Math.min(probabilityOfFailure, 1);
|
|
5079
|
+
const red = Math.floor(255 * pf);
|
|
5080
|
+
const green = Math.floor(255 * (1 - pf));
|
|
5081
|
+
const color = `rgb(${red}, ${green}, 0)`;
|
|
5082
|
+
graphics.rects.push({
|
|
5083
|
+
center: node.center,
|
|
5084
|
+
label: `${nodeId}
|
|
5085
|
+
${node.width.toFixed(2)}x${node.height.toFixed(2)}
|
|
5086
|
+
Pf: ${probabilityOfFailure.toFixed(3)}`,
|
|
5087
|
+
color,
|
|
5088
|
+
width: node.width / 8,
|
|
5089
|
+
height: node.height / 8
|
|
5090
|
+
});
|
|
4617
5091
|
}
|
|
4618
|
-
for (const
|
|
4619
|
-
|
|
4620
|
-
|
|
4621
|
-
|
|
4622
|
-
|
|
4623
|
-
|
|
4624
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
|
|
4632
|
-
x: point.x,
|
|
4633
|
-
y: point.y,
|
|
4634
|
-
color: solvedColor
|
|
4635
|
-
});
|
|
5092
|
+
for (const segmentPoint of this.segmentPointMap.values()) {
|
|
5093
|
+
graphics.points.push({
|
|
5094
|
+
x: segmentPoint.x,
|
|
5095
|
+
y: segmentPoint.y,
|
|
5096
|
+
label: `${segmentPoint.segmentPointId}
|
|
5097
|
+
Segment: ${segmentPoint.segmentId}
|
|
5098
|
+
Layer: ${segmentPoint.z}`,
|
|
5099
|
+
color: this.colorMap[segmentPoint.connectionName] || "#000"
|
|
5100
|
+
});
|
|
5101
|
+
}
|
|
5102
|
+
const pointsBySegment = /* @__PURE__ */ new Map();
|
|
5103
|
+
for (const point of this.segmentPointMap.values()) {
|
|
5104
|
+
if (!pointsBySegment.has(point.segmentId)) {
|
|
5105
|
+
pointsBySegment.set(point.segmentId, []);
|
|
4636
5106
|
}
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
5107
|
+
pointsBySegment.get(point.segmentId).push(point);
|
|
5108
|
+
}
|
|
5109
|
+
for (const [segmentId, points] of pointsBySegment.entries()) {
|
|
5110
|
+
if (points.length < 2) continue;
|
|
5111
|
+
const sortedPoints = [...points].sort(
|
|
5112
|
+
(a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y
|
|
5113
|
+
);
|
|
5114
|
+
for (let i = 0; i < sortedPoints.length - 1; i++) {
|
|
5115
|
+
graphics.lines.push({
|
|
5116
|
+
points: [
|
|
5117
|
+
{ x: sortedPoints[i].x, y: sortedPoints[i].y },
|
|
5118
|
+
{ x: sortedPoints[i + 1].x, y: sortedPoints[i + 1].y }
|
|
5119
|
+
],
|
|
5120
|
+
strokeColor: this.colorMap[segmentId] || "#000"
|
|
4642
5121
|
});
|
|
4643
5122
|
}
|
|
4644
5123
|
}
|
|
4645
|
-
const
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
{
|
|
4652
|
-
|
|
4653
|
-
y: unsolvedRoute.start.y,
|
|
4654
|
-
color: colorList[i],
|
|
4655
|
-
label: `${unsolvedRoute.connectionName} Start`
|
|
4656
|
-
},
|
|
4657
|
-
{
|
|
4658
|
-
x: unsolvedRoute.end.x,
|
|
4659
|
-
y: unsolvedRoute.end.y,
|
|
4660
|
-
color: colorList[i],
|
|
4661
|
-
label: `${unsolvedRoute.connectionName} End`
|
|
4662
|
-
}
|
|
4663
|
-
);
|
|
4664
|
-
graphics.lines?.push({
|
|
4665
|
-
points: [
|
|
4666
|
-
{ x: unsolvedRoute.start.x, y: unsolvedRoute.start.y },
|
|
4667
|
-
{ x: unsolvedRoute.end.x, y: unsolvedRoute.end.y }
|
|
4668
|
-
],
|
|
4669
|
-
strokeColor: colorList[i],
|
|
4670
|
-
strokeDash: "2 2"
|
|
4671
|
-
});
|
|
4672
|
-
for (const hdRoute of unsolvedRoute.hdRoutes) {
|
|
4673
|
-
if (hdRoute.route.length > 1) {
|
|
4674
|
-
graphics.lines?.push({
|
|
4675
|
-
points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
|
|
4676
|
-
strokeColor: safeTransparentize(colorList[i], 0.5),
|
|
4677
|
-
strokeDash: "10 5"
|
|
4678
|
-
});
|
|
5124
|
+
const processedConnections = /* @__PURE__ */ new Set();
|
|
5125
|
+
const allPoints = Array.from(this.segmentPointMap.values());
|
|
5126
|
+
for (let i = 0; i < allPoints.length; i++) {
|
|
5127
|
+
const point1 = allPoints[i];
|
|
5128
|
+
for (let j = i + 1; j < allPoints.length; j++) {
|
|
5129
|
+
const point2 = allPoints[j];
|
|
5130
|
+
if (point1.connectionName !== point2.connectionName || point1.segmentId === point2.segmentId) {
|
|
5131
|
+
continue;
|
|
4679
5132
|
}
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
5133
|
+
const hasSharedNode = point1.capacityMeshNodeIds.some(
|
|
5134
|
+
(nodeId) => point2.capacityMeshNodeIds.includes(nodeId)
|
|
5135
|
+
);
|
|
5136
|
+
if (hasSharedNode) {
|
|
5137
|
+
const connectionKey = `${point1.segmentPointId}-${point2.segmentPointId}`;
|
|
5138
|
+
if (processedConnections.has(connectionKey)) continue;
|
|
5139
|
+
processedConnections.add(connectionKey);
|
|
5140
|
+
const sameLayer = point1.z === point2.z;
|
|
5141
|
+
const layer = point1.z;
|
|
5142
|
+
let strokeDash;
|
|
5143
|
+
if (sameLayer) {
|
|
5144
|
+
strokeDash = layer === 0 ? void 0 : "10 5";
|
|
5145
|
+
} else {
|
|
5146
|
+
strokeDash = "3 3 10";
|
|
5147
|
+
}
|
|
5148
|
+
graphics.lines.push({
|
|
5149
|
+
points: [
|
|
5150
|
+
{ x: point1.x, y: point1.y },
|
|
5151
|
+
{ x: point2.x, y: point2.y }
|
|
5152
|
+
],
|
|
5153
|
+
strokeDash,
|
|
5154
|
+
strokeColor: this.colorMap[point1.connectionName] || "#666"
|
|
4685
5155
|
});
|
|
4686
5156
|
}
|
|
4687
5157
|
}
|
|
4688
5158
|
}
|
|
4689
5159
|
return graphics;
|
|
4690
5160
|
}
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
const circles = [];
|
|
4697
|
-
const points = [];
|
|
4698
|
-
const colorMap = getColorMap(srj);
|
|
4699
|
-
if (srj.connections) {
|
|
4700
|
-
for (const connection of srj.connections) {
|
|
4701
|
-
for (const point of connection.pointsToConnect) {
|
|
4702
|
-
points.push({
|
|
4703
|
-
x: point.x,
|
|
4704
|
-
y: point.y,
|
|
4705
|
-
color: colorMap[connection.name],
|
|
4706
|
-
label: `${connection.name} (${point.layer})`
|
|
4707
|
-
});
|
|
4708
|
-
}
|
|
5161
|
+
getNodesWithPortPoints() {
|
|
5162
|
+
if (!this.solved) {
|
|
5163
|
+
throw new Error(
|
|
5164
|
+
"CapacitySegmentToPointSolver not solved, can't give port points yet"
|
|
5165
|
+
);
|
|
4709
5166
|
}
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
for (
|
|
4714
|
-
const
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
stroke: "none"
|
|
5167
|
+
const nodeWithPortPointsMap = /* @__PURE__ */ new Map();
|
|
5168
|
+
for (const segment of this.dedupedSegments) {
|
|
5169
|
+
const segId = segment.nodePortSegmentId;
|
|
5170
|
+
for (const nodeId of this.segmentIdToNodeIds.get(segId)) {
|
|
5171
|
+
const node = this.nodeMap.get(nodeId);
|
|
5172
|
+
if (!nodeWithPortPointsMap.has(nodeId)) {
|
|
5173
|
+
nodeWithPortPointsMap.set(nodeId, {
|
|
5174
|
+
capacityMeshNodeId: nodeId,
|
|
5175
|
+
portPoints: [],
|
|
5176
|
+
center: node.center,
|
|
5177
|
+
width: node.width,
|
|
5178
|
+
height: node.height
|
|
4723
5179
|
});
|
|
4724
|
-
}
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
inner2: "yellow"
|
|
4737
|
-
}[routePoint.layer],
|
|
4738
|
-
0.5
|
|
4739
|
-
)
|
|
4740
|
-
// For some reason this is too small, likely a graphics-debug bug
|
|
4741
|
-
// strokeWidth: 0.15,
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
}
|
|
5183
|
+
for (const segmentPoint of this.segmentPointMap.values()) {
|
|
5184
|
+
for (const nodeId of segmentPoint.capacityMeshNodeIds) {
|
|
5185
|
+
const nodeWithPortPoints = nodeWithPortPointsMap.get(nodeId);
|
|
5186
|
+
if (nodeWithPortPoints) {
|
|
5187
|
+
nodeWithPortPoints.portPoints.push({
|
|
5188
|
+
x: segmentPoint.x,
|
|
5189
|
+
y: segmentPoint.y,
|
|
5190
|
+
z: segmentPoint.z,
|
|
5191
|
+
connectionName: segmentPoint.connectionName
|
|
4742
5192
|
});
|
|
4743
5193
|
}
|
|
4744
5194
|
}
|
|
4745
5195
|
}
|
|
5196
|
+
return Array.from(nodeWithPortPointsMap.values());
|
|
4746
5197
|
}
|
|
4747
|
-
return {
|
|
4748
|
-
rects: srj.obstacles.map(
|
|
4749
|
-
(o) => ({
|
|
4750
|
-
center: o.center,
|
|
4751
|
-
width: o.width,
|
|
4752
|
-
height: o.height,
|
|
4753
|
-
fill: "rgba(255,0,0,0.5)"
|
|
4754
|
-
})
|
|
4755
|
-
),
|
|
4756
|
-
circles,
|
|
4757
|
-
lines,
|
|
4758
|
-
points
|
|
4759
|
-
};
|
|
4760
5198
|
};
|
|
4761
5199
|
|
|
4762
5200
|
// lib/solvers/CapacityMeshSolver/CapacityMeshSolver.ts
|
|
@@ -4798,6 +5236,7 @@ var CapacityMeshSolver = class extends BaseSolver {
|
|
|
4798
5236
|
edgeToPortSegmentSolver;
|
|
4799
5237
|
colorMap;
|
|
4800
5238
|
segmentToPointSolver;
|
|
5239
|
+
unravelMultiSectionSolver;
|
|
4801
5240
|
segmentToPointOptimizer;
|
|
4802
5241
|
highDensityRouteSolver;
|
|
4803
5242
|
highDensityStitchSolver;
|
|
@@ -4880,9 +5319,20 @@ var CapacityMeshSolver = class extends BaseSolver {
|
|
|
4880
5319
|
];
|
|
4881
5320
|
}
|
|
4882
5321
|
),
|
|
5322
|
+
// definePipelineStep(
|
|
5323
|
+
// "segmentToPointOptimizer",
|
|
5324
|
+
// CapacitySegmentPointOptimizer,
|
|
5325
|
+
// (cms) => [
|
|
5326
|
+
// {
|
|
5327
|
+
// assignedSegments: cms.segmentToPointSolver?.solvedSegments || [],
|
|
5328
|
+
// colorMap: cms.colorMap,
|
|
5329
|
+
// nodes: cms.nodeTargetMerger?.newNodes || [],
|
|
5330
|
+
// },
|
|
5331
|
+
// ],
|
|
5332
|
+
// ),
|
|
4883
5333
|
definePipelineStep(
|
|
4884
|
-
"
|
|
4885
|
-
|
|
5334
|
+
"unravelMultiSectionSolver",
|
|
5335
|
+
UnravelMultiSectionSolver,
|
|
4886
5336
|
(cms) => [
|
|
4887
5337
|
{
|
|
4888
5338
|
assignedSegments: cms.segmentToPointSolver?.solvedSegments || [],
|
|
@@ -4893,7 +5343,7 @@ var CapacityMeshSolver = class extends BaseSolver {
|
|
|
4893
5343
|
),
|
|
4894
5344
|
definePipelineStep("highDensityRouteSolver", HighDensitySolver, (cms) => [
|
|
4895
5345
|
{
|
|
4896
|
-
nodePortPoints: cms.segmentToPointOptimizer?.getNodesWithPortPoints()
|
|
5346
|
+
nodePortPoints: cms.unravelMultiSectionSolver?.getNodesWithPortPoints() ?? cms.segmentToPointOptimizer?.getNodesWithPortPoints() ?? [],
|
|
4897
5347
|
colorMap: cms.colorMap,
|
|
4898
5348
|
connMap: cms.connMap
|
|
4899
5349
|
}
|
|
@@ -4951,7 +5401,7 @@ var CapacityMeshSolver = class extends BaseSolver {
|
|
|
4951
5401
|
const pathingViz = this.pathingSolver?.visualize();
|
|
4952
5402
|
const edgeToPortSegmentViz = this.edgeToPortSegmentSolver?.visualize();
|
|
4953
5403
|
const segmentToPointViz = this.segmentToPointSolver?.visualize();
|
|
4954
|
-
const segmentOptimizationViz = this.segmentToPointOptimizer?.visualize();
|
|
5404
|
+
const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
|
|
4955
5405
|
const highDensityViz = this.highDensityRouteSolver?.visualize();
|
|
4956
5406
|
const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
|
|
4957
5407
|
const problemViz = {
|