@tscircuit/capacity-autorouter 0.0.18 → 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.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/utils/getIntraNodeCrossingsFromSegments.ts
3351
- var getIntraNodeCrossingsFromSegments = (segments) => {
3352
- let numSameLayerCrossings = 0;
3353
- const pointPairs = [];
3354
- const transitionPairPoints = [];
3355
- let numEntryExitLayerChanges = 0;
3356
- const portPoints = segments.flatMap((seg) => seg.assignedPoints);
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
- for (let i = 0; i < pointPairs.length; i++) {
3382
- for (let j = i + 1; j < pointPairs.length; j++) {
3383
- const pair1 = pointPairs[i];
3384
- const pair2 = pointPairs[j];
3385
- if (pair1.z === pair2.z && doSegmentsIntersect(
3386
- pair1.points[0],
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
- let numTransitionCrossings = 0;
3396
- for (let i = 0; i < transitionPairPoints.length; i++) {
3397
- for (let j = i + 1; j < transitionPairPoints.length; j++) {
3398
- const pair1 = transitionPairPoints[i];
3399
- const pair2 = transitionPairPoints[j];
3400
- if (doSegmentsIntersect(
3401
- pair1.points[0],
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
- for (let i = 0; i < transitionPairPoints.length; i++) {
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
- return {
3425
- numSameLayerCrossings,
3426
- numEntryExitLayerChanges,
3427
- numTransitionCrossings
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
- this.colorMap = colorMap ?? {};
3505
- this.nodeMap = /* @__PURE__ */ new Map();
3506
- for (const node of nodes) {
3507
- this.nodeMap.set(node.capacityMeshNodeId, node);
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
- this.numNodes = this.segmentIdToNodeIds.size;
3510
- const { cost, nodeCosts, probabilityOfFailure } = this.computeCurrentCost();
3511
- this.currentCost = cost;
3512
- this.currentNodeCosts = nodeCosts;
3513
- this.probabilityOfFailure = probabilityOfFailure;
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
- this.mutableSegments = this.getMutableSegments();
3524
- }
3525
- random() {
3526
- this.randomSeed = this.randomSeed * 16807 % 2147483647;
3527
- return (this.randomSeed - 1) / 2147483646;
3528
- }
3529
- /**
3530
- * The cost is the "probability of failure" of the node.
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
- const {
3580
- numEntryExitLayerChanges,
3581
- numSameLayerCrossings,
3582
- numTransitionCrossings
3583
- } = getIntraNodeCrossingsFromSegments(segments);
3584
- const estNumVias = numSameLayerCrossings * 0.82 + numEntryExitLayerChanges * 0.41 + numTransitionCrossings * 0.2;
3585
- const estUsedCapacity = (estNumVias / 2) ** 1.1;
3586
- return estUsedCapacity;
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 mutableSegments;
3626
- }
3627
- isSegmentMutable(segmentId) {
3628
- return this.mutableSegments.has(segmentId);
3424
+ return best;
3629
3425
  }
3630
- getRandomOperationForSegment(randomSegmentId) {
3631
- const segment = this.currentMutatedSegments.get(randomSegmentId);
3632
- let operationType = this.random() < 0.5 ? "switch" : "changeLayer";
3633
- if (segment.assignedPoints.length <= 1) {
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 randomPointIndex = Math.floor(
3654
- this.random() * segment.assignedPoints.length
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
- getNodesNearNode(nodeId, hops = 1) {
3665
- if (hops === 0) return [nodeId];
3666
- const segments = this.nodeIdToSegmentIds.get(nodeId);
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
- return Array.from(nodes);
3678
- }
3679
- getRandomCombinedOperationNearNode(nodeId) {
3680
- const adjacentNodeIds = this.getNodesNearNode(
3681
- nodeId,
3682
- this.MAX_NODE_CHAIN_PER_MUTATION
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
- const subOperations = [];
3685
- const adjacentSegments = adjacentNodeIds.flatMap((nodeId2) => this.nodeIdToSegmentIds.get(nodeId2)).filter((s) => this.isSegmentMutable(s));
3686
- const numOperations = Math.floor(this.random() * this.MAX_OPERATIONS_PER_MUTATION) + 1;
3687
- for (let i = 0; i < numOperations; i++) {
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
- return {
3695
- op: "combined",
3696
- subOperations
3697
- };
3698
- }
3699
- /**
3700
- * A combined operation can perform multiple operations on a single node, this
3701
- * allows it to reach outcomes that may not be beneficial with since
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
- getRandomOperation() {
3722
- const randomSegmentId = this.getRandomWeightedSegmentId();
3723
- const newOp = this.getRandomOperationForSegment(randomSegmentId);
3724
- if (newOp) {
3725
- return newOp;
3726
- }
3727
- return this.getRandomOperation();
3728
- }
3729
- /**
3730
- * We compute "overall probability of failure" as our overall cost, then
3731
- * linearize it to make it easier to work with
3732
- */
3733
- computeCurrentCost() {
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
- edges.sort((a, b) => a.weight - b.weight);
4226
- const disjointSet = new DisjointSet(points);
4227
- const mstEdges = [];
4228
- for (const edge of edges) {
4229
- if (disjointSet.union(edge.from, edge.to)) {
4230
- mstEdges.push(edge);
4231
- if (mstEdges.length === points.length - 1) {
4232
- break;
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
- return mstEdges;
4237
- }
3866
+ };
4238
3867
 
4239
- // lib/solvers/NetToPointPairsSolver/NetToPointPairsSolver.ts
4240
- var NetToPointPairsSolver = class extends BaseSolver {
4241
- constructor(ogSrj, colorMap = {}) {
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.ogSrj = ogSrj;
4244
- this.colorMap = colorMap;
4245
- this.unprocessedConnections = [...ogSrj.connections];
4246
- this.newConnections = [];
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.unprocessedConnections.length === 0) {
4252
- this.solved = true;
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 connection = this.unprocessedConnections.pop();
4256
- if (connection.pointsToConnect.length === 2) {
4257
- this.newConnections.push(connection);
3900
+ const unsolvedRoute = this.unsolvedRoutes.pop();
3901
+ if (!unsolvedRoute) {
3902
+ this.solved = true;
4258
3903
  return;
4259
3904
  }
4260
- const edges = buildMinimumSpanningTree(connection.pointsToConnect);
4261
- for (let i = 0; i < edges.length; i++) {
4262
- const edge = edges[i];
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
- getNewSimpleRouteJson() {
4270
- return {
4271
- ...this.ogSrj,
4272
- connections: this.newConnections
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
- rects: [],
3918
+ lines: [],
4280
3919
  circles: [],
4281
- coordinateSystem: "cartesian",
4282
- title: "Net To Point Pairs Visualization"
3920
+ title: "Multiple High Density Route Stitch Solver"
4283
3921
  };
4284
- this.unprocessedConnections.forEach((connection) => {
4285
- connection.pointsToConnect.forEach((point) => {
4286
- graphics.points.push({
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: "red",
4290
- label: connection.name
3953
+ color: solvedColor
4291
3954
  });
4292
- });
4293
- const fullyConnectedEdgeCount = connection.pointsToConnect.length ** 2;
4294
- const random = seededRandom(0);
4295
- const alreadyPlacedEdges = /* @__PURE__ */ new Set();
4296
- for (let i = 0; i < Math.max(
4297
- fullyConnectedEdgeCount,
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
- this.newConnections.forEach((connection) => {
4314
- const color = this.colorMap?.[connection.name] || "blue";
4315
- connection.pointsToConnect.forEach((point) => {
4316
- graphics.points.push({
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
- for (let i = 0; i < connection.pointsToConnect.length - 1; i++) {
4324
- for (let j = i + 1; j < connection.pointsToConnect.length; j++) {
4325
- graphics.lines.push({
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
- connection.pointsToConnect[i],
4328
- connection.pointsToConnect[j]
4046
+ { x: routePoint.x, y: routePoint.y },
4047
+ { x: nextRoutePoint.x, y: nextRoutePoint.y }
4329
4048
  ],
4330
- strokeColor: color
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/utils/mapZToLayerName.ts
4340
- var mapZToLayerName = (z, layerCount) => {
4341
- if (z < 0 || z >= layerCount) {
4342
- throw new Error(`Invalid z "${z}" for layer count: ${layerCount}`);
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
- if (z === 0) return "top";
4345
- if (z === layerCount - 1) return "bottom";
4346
- return `inner${z}`;
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/utils/convertHdRouteToSimplifiedRoute.ts
4350
- var convertHdRouteToSimplifiedRoute = (hdRoute, layerCount) => {
4351
- const result = [];
4352
- if (hdRoute.route.length === 0) return result;
4353
- let currentLayerPoints = [];
4354
- let currentZ = hdRoute.route[0].z;
4355
- for (let i = 0; i < hdRoute.route.length; i++) {
4356
- const point = hdRoute.route[i];
4357
- if (point.z !== currentZ) {
4358
- const layerName2 = mapZToLayerName(currentZ, layerCount);
4359
- for (const layerPoint of currentLayerPoints) {
4360
- result.push({
4361
- route_type: "wire",
4362
- x: layerPoint.x,
4363
- y: layerPoint.y,
4364
- width: hdRoute.traceThickness,
4365
- layer: layerName2
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
- const viaExists = hdRoute.vias.some(
4369
- (via) => Math.abs(via.x - point.x) < 1e-3 && Math.abs(via.y - point.y) < 1e-3
4370
- );
4371
- if (viaExists) {
4372
- const fromLayer = mapZToLayerName(currentZ, layerCount);
4373
- const toLayer = mapZToLayerName(point.z, layerCount);
4374
- result.push({
4375
- route_type: "via",
4376
- x: point.x,
4377
- y: point.y,
4378
- from_layer: fromLayer,
4379
- to_layer: toLayer
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
- currentLayerPoints = [point];
4383
- currentZ = point.z;
4384
- } else {
4385
- currentLayerPoints.push(point);
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
- const layerName = mapZToLayerName(currentZ, layerCount);
4389
- for (const layerPoint of currentLayerPoints) {
4390
- result.push({
4391
- route_type: "wire",
4392
- x: layerPoint.x,
4393
- y: layerPoint.y,
4394
- width: hdRoute.traceThickness,
4395
- layer: layerName
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/utils/mapLayerNameToZ.ts
4402
- var mapLayerNameToZ = (layerName, layerCount) => {
4403
- if (layerName === "top") return 0;
4404
- if (layerName === "bottom") return layerCount - 1;
4405
- return parseInt(layerName.slice(5));
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/RouteStitchingSolver/SingleHighDensityRouteStitchSolver.ts
4409
- var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
4410
- mergedHdRoute;
4411
- remainingHdRoutes;
4412
- start;
4413
- end;
4414
- constructor(opts) {
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.remainingHdRoutes = [...opts.hdRoutes];
4417
- this.mergedHdRoute = {
4418
- connectionName: opts.hdRoutes[0].connectionName,
4419
- route: [
4420
- {
4421
- x: opts.start.x,
4422
- y: opts.start.y,
4423
- z: opts.start.z
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
- vias: [],
4427
- viaDiameter: opts.hdRoutes[0].viaDiameter,
4428
- traceThickness: opts.hdRoutes[0].traceThickness
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
- this.start = opts.start;
4431
- this.end = opts.end;
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
- if (this.remainingHdRoutes.length === 0) {
4435
- this.mergedHdRoute.route.push({
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
- const lastMergedPoint = this.mergedHdRoute.route[this.mergedHdRoute.route.length - 1];
4444
- let closestRouteIndex = 0;
4445
- let matchedOn = "first";
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.mergedHdRoute.vias.push(...hdRouteToMerge.vias);
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
- title: "Single High Density Route Stitch Solver"
4674
+ coordinateSystem: "cartesian",
4675
+ title: "Unravel Section Solver"
4479
4676
  };
4480
- graphics.points?.push(
4481
- {
4482
- x: this.start.x,
4483
- y: this.start.y,
4484
- color: "green",
4485
- label: "Start"
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
- if (this.mergedHdRoute && this.mergedHdRoute.route.length > 1) {
4495
- graphics.lines?.push({
4496
- points: this.mergedHdRoute.route.map((point) => ({
4497
- x: point.x,
4498
- y: point.y
4499
- })),
4500
- strokeColor: "green"
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
- for (const point of this.mergedHdRoute.route) {
4503
- graphics.points?.push({
4504
- x: point.x,
4505
- y: point.y,
4506
- color: "green"
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
- for (const via of this.mergedHdRoute.vias) {
4510
- graphics.circles?.push({
4511
- center: { x: via.x, y: via.y },
4512
- radius: this.mergedHdRoute.viaDiameter / 2,
4513
- fill: "green"
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 colorList = Array.from(
4518
- { length: this.remainingHdRoutes.length },
4519
- (_, i) => `hsl(${i * 360 / this.remainingHdRoutes.length}, 100%, 50%)`
4520
- );
4521
- for (const [i, hdRoute] of this.remainingHdRoutes.entries()) {
4522
- if (hdRoute.route.length > 1) {
4523
- graphics.lines?.push({
4524
- points: hdRoute.route.map((point) => ({ x: point.x, y: point.y })),
4525
- strokeColor: colorList[i]
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
- for (let pi = 0; pi < hdRoute.route.length; pi++) {
4529
- const point = hdRoute.route[pi];
4530
- graphics.points?.push({
4531
- x: point.x + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
4532
- y: point.y + (i % 2 - 0.5) / 500 + (pi % 8 - 4) / 1e3,
4533
- color: colorList[i],
4534
- label: `Route ${i} ${point === hdRoute.route[0] ? "First" : point === hdRoute.route[hdRoute.route.length - 1] ? "Last" : ""}`
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
- for (const via of hdRoute.vias) {
4538
- graphics.circles?.push({
4539
- center: { x: via.x, y: via.y },
4540
- radius: hdRoute.viaDiameter / 2,
4541
- fill: colorList[i]
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
- // lib/solvers/RouteStitchingSolver/MultipleHighDensityRouteStitchSolver.ts
4550
- var MultipleHighDensityRouteStitchSolver = class extends BaseSolver {
4551
- unsolvedRoutes;
4552
- activeSolver = null;
4553
- mergedHdRoutes = [];
4554
- constructor(opts) {
4555
- super();
4556
- this.unsolvedRoutes = opts.connections.map((c) => ({
4557
- connectionName: c.name,
4558
- hdRoutes: opts.hdRoutes.filter((r) => r.connectionName === c.name),
4559
- start: {
4560
- ...c.pointsToConnect[0],
4561
- z: mapLayerNameToZ(c.pointsToConnect[0].layer, opts.layerCount)
4562
- },
4563
- end: {
4564
- ...c.pointsToConnect[1],
4565
- z: mapLayerNameToZ(c.pointsToConnect[1].layer, opts.layerCount)
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.activeSolver) {
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 (unsolvedRoute.hdRoutes.length === 0) {
4587
- console.warn(`No routes to stitch for ${unsolvedRoute.connectionName}`);
4588
- return;
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
- title: "Multiple High Density Route Stitch Solver"
5073
+ coordinateSystem: "cartesian",
5074
+ title: "Unravel Multi Section Solver"
4602
5075
  };
4603
- if (this.activeSolver) {
4604
- const activeSolverGraphics = this.activeSolver.visualize();
4605
- if (activeSolverGraphics.points?.length) {
4606
- graphics.points?.push(...activeSolverGraphics.points);
4607
- }
4608
- if (activeSolverGraphics.lines?.length) {
4609
- graphics.lines?.push(...activeSolverGraphics.lines);
4610
- }
4611
- if (activeSolverGraphics.circles?.length) {
4612
- graphics.circles?.push(...activeSolverGraphics.circles);
4613
- }
4614
- if (activeSolverGraphics.rects?.length) {
4615
- graphics.rects = activeSolverGraphics.rects;
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 [i, mergedRoute] of this.mergedHdRoutes.entries()) {
4619
- const solvedColor = `hsl(120, 100%, ${40 + i * 10 % 40}%)`;
4620
- if (mergedRoute.route.length > 1) {
4621
- graphics.lines?.push({
4622
- points: mergedRoute.route.map((point) => ({
4623
- x: point.x,
4624
- y: point.y
4625
- })),
4626
- strokeColor: solvedColor,
4627
- strokeWidth: mergedRoute.traceThickness
4628
- });
4629
- }
4630
- for (const point of mergedRoute.route) {
4631
- graphics.points?.push({
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
- for (const via of mergedRoute.vias) {
4638
- graphics.circles?.push({
4639
- center: { x: via.x, y: via.y },
4640
- radius: mergedRoute.viaDiameter / 2,
4641
- fill: solvedColor
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 colorList = Array.from(
4646
- { length: this.unsolvedRoutes.length },
4647
- (_, i) => `hsl(${i * 360 / this.unsolvedRoutes.length}, 100%, 50%)`
4648
- );
4649
- for (const [i, unsolvedRoute] of this.unsolvedRoutes.entries()) {
4650
- graphics.points?.push(
4651
- {
4652
- x: unsolvedRoute.start.x,
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
- for (const via of hdRoute.vias) {
4681
- graphics.circles?.push({
4682
- center: { x: via.x, y: via.y },
4683
- radius: hdRoute.viaDiameter / 2,
4684
- fill: colorList[i]
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
- // tests/fixtures/convertSrjToGraphicsObject.ts
4694
- var convertSrjToGraphicsObject = (srj) => {
4695
- const lines = [];
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
- if (srj.traces) {
4712
- for (const trace of srj.traces) {
4713
- for (let j = 0; j < trace.route.length - 1; j++) {
4714
- const routePoint = trace.route[j];
4715
- const nextRoutePoint = trace.route[j + 1];
4716
- if (routePoint.route_type === "via") {
4717
- circles.push({
4718
- center: { x: routePoint.x, y: routePoint.y },
4719
- radius: 0.3,
4720
- // 0.6 via diameter
4721
- fill: "blue",
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
- } else if (routePoint.route_type === "wire" && nextRoutePoint.route_type === "wire" && nextRoutePoint.layer === routePoint.layer) {
4725
- lines.push({
4726
- points: [
4727
- { x: routePoint.x, y: routePoint.y },
4728
- { x: nextRoutePoint.x, y: nextRoutePoint.y }
4729
- ],
4730
- strokeWidth: 0.15,
4731
- strokeColor: safeTransparentize(
4732
- {
4733
- top: "red",
4734
- bottom: "blue",
4735
- inner1: "green",
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
- "segmentToPointOptimizer",
4885
- CapacitySegmentPointOptimizer,
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 = {