@tscircuit/capacity-autorouter 0.0.19 → 0.0.21

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 {
@@ -135,7 +140,7 @@ var CapacityMeshEdgeSolver = class extends BaseSolver {
135
140
  this.edges = [];
136
141
  for (let i = 0; i < this.nodes.length; i++) {
137
142
  for (let j = i + 1; j < this.nodes.length; j++) {
138
- if (this.areNodesBordering(this.nodes[i], this.nodes[j])) {
143
+ if (this.areNodesBordering(this.nodes[i], this.nodes[j]) && this.doNodesHaveSharedLayer(this.nodes[i], this.nodes[j])) {
139
144
  this.edges.push({
140
145
  capacityMeshEdgeId: this.getNextCapacityMeshEdgeId(),
141
146
  nodeIds: [
@@ -189,16 +194,35 @@ var CapacityMeshEdgeSolver = class extends BaseSolver {
189
194
  const shareHorizontalBorder = (Math.abs(n1Bottom - n2Top) < epsilon || Math.abs(n1Top - n2Bottom) < epsilon) && Math.min(n1Right, n2Right) - Math.max(n1Left, n2Left) >= epsilon;
190
195
  return shareVerticalBorder || shareHorizontalBorder;
191
196
  }
197
+ doNodesHaveSharedLayer(node1, node2) {
198
+ return node1.availableZ.some((z) => node2.availableZ.includes(z));
199
+ }
192
200
  visualize() {
193
201
  const graphics = {
194
202
  lines: [],
195
203
  points: [],
196
- rects: this.nodes.map((node) => ({
197
- width: Math.max(node.width - 2, node.width * 0.8),
198
- height: Math.max(node.height - 2, node.height * 0.8),
199
- center: node.center,
200
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : node._containsTarget ? "rgba(0,255,0,0.2)" : "rgba(0,0,0,0.1)"
201
- })),
204
+ rects: this.nodes.map((node) => {
205
+ const lowestZ = Math.min(...node.availableZ);
206
+ return {
207
+ width: Math.max(node.width - 2, node.width * 0.8),
208
+ height: Math.max(node.height - 2, node.height * 0.8),
209
+ center: {
210
+ x: node.center.x + lowestZ * node.width * 0.05,
211
+ y: node.center.y - lowestZ * node.width * 0.05
212
+ },
213
+ fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
214
+ "0,1": "rgba(0,0,0,0.1)",
215
+ "0": "rgba(0,200,200, 0.1)",
216
+ "1": "rgba(0,0,200, 0.1)"
217
+ }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
218
+ label: [
219
+ node.capacityMeshNodeId,
220
+ `availableZ: ${node.availableZ.join(",")}`,
221
+ `target? ${node._containsTarget ?? false}`,
222
+ `obs? ${node._containsObstacle ?? false}`
223
+ ].join("\n")
224
+ };
225
+ }),
202
226
  circles: []
203
227
  };
204
228
  for (const edge of this.edges) {
@@ -1069,7 +1093,14 @@ function doRectsOverlap(rect1, rect2) {
1069
1093
  return rect1Left <= rect2Right && rect1Right >= rect2Left && rect1Top <= rect2Bottom && rect1Bottom >= rect2Top;
1070
1094
  }
1071
1095
 
1072
- // lib/solvers/CapacityMeshSolver/CapacityMeshNodeSolver.ts
1096
+ // lib/utils/mapLayerNameToZ.ts
1097
+ var mapLayerNameToZ = (layerName, layerCount) => {
1098
+ if (layerName === "top") return 0;
1099
+ if (layerName === "bottom") return layerCount - 1;
1100
+ return parseInt(layerName.slice(5));
1101
+ };
1102
+
1103
+ // lib/solvers/CapacityMeshSolver/CapacityMeshNodeSolver1.ts
1073
1104
  var CapacityMeshNodeSolver = class extends BaseSolver {
1074
1105
  constructor(srj, opts = {}) {
1075
1106
  super();
@@ -1077,6 +1108,16 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1077
1108
  this.opts = opts;
1078
1109
  this.MAX_DEPTH = opts?.capacityDepth ?? this.MAX_DEPTH;
1079
1110
  this.MAX_ITERATIONS = 1e5;
1111
+ this.layerCount = srj.layerCount ?? 2;
1112
+ for (const obstacle of srj.obstacles) {
1113
+ if (!obstacle.zLayers) {
1114
+ const zLayers = [];
1115
+ for (const layer of obstacle.layers) {
1116
+ zLayers.push(mapLayerNameToZ(layer, srj.layerCount));
1117
+ }
1118
+ obstacle.zLayers = zLayers;
1119
+ }
1120
+ }
1080
1121
  const boundsCenter = {
1081
1122
  x: (srj.bounds.minX + srj.bounds.maxX) / 2,
1082
1123
  y: (srj.bounds.minY + srj.bounds.maxY) / 2
@@ -1101,7 +1142,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1101
1142
  }
1102
1143
  ];
1103
1144
  this.finishedNodes = [];
1104
- this.nodeToOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1145
+ this.nodeToXYOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1105
1146
  this.targets = this.srj.connections.flatMap(
1106
1147
  (c) => c.pointsToConnect.map((p) => ({
1107
1148
  ...p,
@@ -1112,7 +1153,8 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1112
1153
  }
1113
1154
  unfinishedNodes;
1114
1155
  finishedNodes;
1115
- nodeToOverlappingObstaclesMap;
1156
+ nodeToXYOverlappingObstaclesMap;
1157
+ layerCount;
1116
1158
  // targetObstacleMap: Record<string, { obstacle: Obstacle, node: CapacityMeshNode }>
1117
1159
  MAX_DEPTH = 4;
1118
1160
  targets;
@@ -1124,7 +1166,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1124
1166
  return (this.MAX_DEPTH - depth + 1) ** 2;
1125
1167
  }
1126
1168
  getTargetIfNodeContainsTarget(node) {
1127
- const overlappingObstacles = this.getOverlappingObstacles(node);
1169
+ const overlappingObstacles = this.getXYOverlappingObstacles(node);
1128
1170
  for (const target of this.targets) {
1129
1171
  const targetObstacle = overlappingObstacles.find(
1130
1172
  (o) => isPointInRect(target, o)
@@ -1140,8 +1182,8 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1140
1182
  }
1141
1183
  return null;
1142
1184
  }
1143
- getOverlappingObstacles(node) {
1144
- const cachedObstacles = this.nodeToOverlappingObstaclesMap.get(
1185
+ getXYOverlappingObstacles(node) {
1186
+ const cachedObstacles = this.nodeToXYOverlappingObstaclesMap.get(
1145
1187
  node.capacityMeshNodeId
1146
1188
  );
1147
1189
  if (cachedObstacles) {
@@ -1152,7 +1194,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1152
1194
  const nodeRight = node.center.x + node.width / 2;
1153
1195
  const nodeTop = node.center.y - node.height / 2;
1154
1196
  const nodeBottom = node.center.y + node.height / 2;
1155
- const obstacles = node._parent ? this.getOverlappingObstacles(node._parent) : this.srj.obstacles;
1197
+ const obstacles = node._parent ? this.getXYOverlappingObstacles(node._parent) : this.srj.obstacles;
1156
1198
  for (const obstacle of obstacles) {
1157
1199
  const obsLeft = obstacle.center.x - obstacle.width / 2;
1158
1200
  const obsRight = obstacle.center.x + obstacle.width / 2;
@@ -1160,20 +1202,38 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1160
1202
  const obsBottom = obstacle.center.y + obstacle.height / 2;
1161
1203
  if (nodeRight >= obsLeft && nodeLeft <= obsRight && nodeBottom >= obsTop && nodeTop <= obsBottom) {
1162
1204
  overlappingObstacles.push(obstacle);
1205
+ continue;
1206
+ }
1207
+ if (nodeLeft >= obsLeft && nodeRight <= obsRight && nodeTop >= obsTop && nodeBottom <= obsBottom) {
1208
+ overlappingObstacles.push(obstacle);
1209
+ continue;
1210
+ }
1211
+ if (obsLeft >= nodeLeft && obsRight <= nodeRight && obsTop >= nodeTop && obsBottom <= nodeBottom) {
1212
+ overlappingObstacles.push(obstacle);
1163
1213
  }
1164
1214
  }
1165
- this.nodeToOverlappingObstaclesMap.set(
1215
+ this.nodeToXYOverlappingObstaclesMap.set(
1166
1216
  node.capacityMeshNodeId,
1167
1217
  overlappingObstacles
1168
1218
  );
1169
1219
  return overlappingObstacles;
1170
1220
  }
1221
+ getXYZOverlappingObstacles(node) {
1222
+ const xyOverlappingObstacles = this.getXYOverlappingObstacles(node);
1223
+ const xyzOverlappingObstacles = [];
1224
+ for (const obstacle of xyOverlappingObstacles) {
1225
+ if (node.availableZ.some((z) => obstacle.zLayers.includes(z))) {
1226
+ xyzOverlappingObstacles.push(obstacle);
1227
+ }
1228
+ }
1229
+ return xyzOverlappingObstacles;
1230
+ }
1171
1231
  /**
1172
1232
  * Checks if the given mesh node overlaps with any obstacle.
1173
1233
  * We treat both obstacles and nodes as axis‐aligned rectangles.
1174
1234
  */
1175
1235
  doesNodeOverlapObstacle(node) {
1176
- const overlappingObstacles = this.getOverlappingObstacles(node);
1236
+ const overlappingObstacles = this.getXYZOverlappingObstacles(node);
1177
1237
  if (overlappingObstacles.length > 0) {
1178
1238
  return true;
1179
1239
  }
@@ -1190,7 +1250,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1190
1250
  * Checks if the entire node is contained within any obstacle.
1191
1251
  */
1192
1252
  isNodeCompletelyInsideObstacle(node) {
1193
- const overlappingObstacles = this.getOverlappingObstacles(node);
1253
+ const overlappingObstacles = this.getXYZOverlappingObstacles(node);
1194
1254
  const nodeLeft = node.center.x - node.width / 2;
1195
1255
  const nodeRight = node.center.x + node.width / 2;
1196
1256
  const nodeTop = node.center.y - node.height / 2;
@@ -1258,7 +1318,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1258
1318
  }
1259
1319
  return childNodes;
1260
1320
  }
1261
- shouldNodeBeSubdivided(node) {
1321
+ shouldNodeBeXYSubdivided(node) {
1262
1322
  if (node._depth >= this.MAX_DEPTH) return false;
1263
1323
  if (node._containsTarget) return true;
1264
1324
  if (node._containsObstacle && !node._completelyInsideObstacle) return true;
@@ -1274,7 +1334,7 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1274
1334
  const finishedNewNodes = [];
1275
1335
  const unfinishedNewNodes = [];
1276
1336
  for (const newNode of newNodes) {
1277
- const shouldBeSubdivided = this.shouldNodeBeSubdivided(newNode);
1337
+ const shouldBeSubdivided = this.shouldNodeBeXYSubdivided(newNode);
1278
1338
  if (shouldBeSubdivided) {
1279
1339
  unfinishedNewNodes.push(newNode);
1280
1340
  } else if (!shouldBeSubdivided && !newNode._containsObstacle) {
@@ -1312,20 +1372,33 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1312
1372
  height: obstacle.height,
1313
1373
  fill: "rgba(255,0,0,0.3)",
1314
1374
  stroke: "red",
1315
- label: "obstacle"
1375
+ label: ["obstacle", obstacle.zLayers.join(",")].join("\n")
1316
1376
  });
1317
1377
  }
1318
1378
  const allNodes = [...this.finishedNodes, ...this.unfinishedNodes];
1319
1379
  for (const node of allNodes) {
1380
+ const lowestZ = Math.min(...node.availableZ);
1320
1381
  graphics.rects.push({
1321
- center: node.center,
1382
+ center: {
1383
+ x: node.center.x + lowestZ * node.width * 0.05,
1384
+ y: node.center.y - lowestZ * node.width * 0.05
1385
+ },
1322
1386
  width: Math.max(node.width - 2, node.width * 0.8),
1323
1387
  height: Math.max(node.height - 2, node.height * 0.8),
1324
- fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : "rgba(0,0,0,0.1)",
1325
- label: `${node.capacityMeshNodeId}
1326
- availableZ: ${node.availableZ.join(",")}`
1388
+ fill: node._containsObstacle ? "rgba(255,0,0,0.1)" : {
1389
+ "0,1": "rgba(0,0,0,0.1)",
1390
+ "0": "rgba(0,200,200, 0.1)",
1391
+ "1": "rgba(0,0,200, 0.1)"
1392
+ }[node.availableZ.join(",")] ?? "rgba(0,200,200,0.1)",
1393
+ label: [
1394
+ node.capacityMeshNodeId,
1395
+ `availableZ: ${node.availableZ.join(",")}`,
1396
+ `target? ${node._containsTarget ?? false}`,
1397
+ `obs? ${node._containsObstacle ?? false}`
1398
+ ].join("\n")
1327
1399
  });
1328
1400
  }
1401
+ graphics.rects.sort((a, b) => a.center.y - b.center.y);
1329
1402
  this.srj.connections.forEach((connection, index) => {
1330
1403
  const color = COLORS[index % COLORS.length];
1331
1404
  for (const pt of connection.pointsToConnect) {
@@ -3347,788 +3420,102 @@ var CapacityNodeTargetMerger = class extends BaseSolver {
3347
3420
  }
3348
3421
  };
3349
3422
 
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);
3423
+ // lib/solvers/NetToPointPairsSolver/buildMinimumSpanningTree.ts
3424
+ var KDNode = class {
3425
+ point;
3426
+ left = null;
3427
+ right = null;
3428
+ constructor(point) {
3429
+ this.point = point;
3380
3430
  }
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
- }
3431
+ };
3432
+ var KDTree = class {
3433
+ root = null;
3434
+ constructor(points) {
3435
+ if (points.length > 0) {
3436
+ this.root = this.buildTree(points, 0);
3393
3437
  }
3394
3438
  }
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
- }
3439
+ buildTree(points, depth) {
3440
+ const axis = depth % 2 === 0 ? "x" : "y";
3441
+ points.sort((a, b) => a[axis] - b[axis]);
3442
+ const medianIndex = Math.floor(points.length / 2);
3443
+ const node = new KDNode(points[medianIndex]);
3444
+ if (medianIndex > 0) {
3445
+ node.left = this.buildTree(points.slice(0, medianIndex), depth + 1);
3408
3446
  }
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
- }
3447
+ if (medianIndex < points.length - 1) {
3448
+ node.right = this.buildTree(points.slice(medianIndex + 1), depth + 1);
3422
3449
  }
3450
+ return node;
3423
3451
  }
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
- ]);
3452
+ // Find the nearest neighbor to a query point
3453
+ findNearestNeighbor(queryPoint) {
3454
+ if (!this.root) {
3455
+ throw new Error("Tree is empty");
3503
3456
  }
3504
- this.colorMap = colorMap ?? {};
3505
- this.nodeMap = /* @__PURE__ */ new Map();
3506
- for (const node of nodes) {
3507
- this.nodeMap.set(node.capacityMeshNodeId, node);
3457
+ const best = this.root.point;
3458
+ const bestDistance = this.distance(queryPoint, best);
3459
+ this.nearestNeighborSearch(this.root, queryPoint, 0, best, bestDistance);
3460
+ return best;
3461
+ }
3462
+ nearestNeighborSearch(node, queryPoint, depth, best, bestDistance) {
3463
+ if (!node) {
3464
+ return best;
3508
3465
  }
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
- }
3466
+ const axis = depth % 2 ? "x" : "y";
3467
+ const currentDistance = this.distance(queryPoint, node.point);
3468
+ if (currentDistance < bestDistance) {
3469
+ best = node.point;
3470
+ bestDistance = currentDistance;
3522
3471
  }
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)
3472
+ const axisDiff = queryPoint[axis] - node.point[axis];
3473
+ const firstBranch = axisDiff <= 0 ? node.left : node.right;
3474
+ const secondBranch = axisDiff <= 0 ? node.right : node.left;
3475
+ best = this.nearestNeighborSearch(
3476
+ firstBranch,
3477
+ queryPoint,
3478
+ depth + 1,
3479
+ best,
3480
+ bestDistance
3578
3481
  );
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
3482
+ bestDistance = this.distance(queryPoint, best);
3483
+ if (Math.abs(axisDiff) < bestDistance) {
3484
+ best = this.nearestNeighborSearch(
3485
+ secondBranch,
3486
+ queryPoint,
3487
+ depth + 1,
3488
+ best,
3489
+ bestDistance
3620
3490
  );
3621
- if (isMutable) {
3622
- mutableSegments.add(segmentId);
3623
- }
3624
3491
  }
3625
- return mutableSegments;
3626
- }
3627
- isSegmentMutable(segmentId) {
3628
- return this.mutableSegments.has(segmentId);
3492
+ return best;
3629
3493
  }
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
- };
3494
+ // Find k nearest neighbors
3495
+ findKNearestNeighbors(queryPoint, k) {
3496
+ if (!this.root) {
3497
+ return [];
3652
3498
  }
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
- };
3499
+ const neighbors = [];
3500
+ this.kNearestNeighborSearch(this.root, queryPoint, 0, neighbors, k);
3501
+ return neighbors.sort((a, b) => a.distance - b.distance).slice(0, k).map((n) => n.point);
3663
3502
  }
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
- }
3503
+ kNearestNeighborSearch(node, queryPoint, depth, neighbors, k) {
3504
+ if (!node) {
3505
+ return;
3676
3506
  }
3677
- return Array.from(nodes);
3678
- }
3679
- getRandomCombinedOperationNearNode(nodeId) {
3680
- const adjacentNodeIds = this.getNodesNearNode(
3681
- nodeId,
3682
- this.MAX_NODE_CHAIN_PER_MUTATION
3683
- );
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
- }
3693
- }
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
- }
3715
- }
3716
- return {
3717
- op: "combined",
3718
- subOperations
3719
- };
3720
- }
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
3507
+ const axis = depth % 2 ? "x" : "y";
3508
+ const currentDistance = this.distance(queryPoint, node.point);
3509
+ neighbors.push({ point: node.point, distance: currentDistance });
3510
+ const axisDiff = queryPoint[axis] - node.point[axis];
3511
+ const firstBranch = axisDiff <= 0 ? node.left : node.right;
3512
+ const secondBranch = axisDiff <= 0 ? node.right : node.left;
3513
+ this.kNearestNeighborSearch(
3514
+ firstBranch,
3515
+ queryPoint,
3516
+ depth + 1,
3517
+ neighbors,
3518
+ k
4132
3519
  );
4133
3520
  let kthDistance = Infinity;
4134
3521
  if (neighbors.length >= k) {
@@ -4398,13 +3785,6 @@ var convertHdRouteToSimplifiedRoute = (hdRoute, layerCount) => {
4398
3785
  return result;
4399
3786
  };
4400
3787
 
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));
4406
- };
4407
-
4408
3788
  // lib/solvers/RouteStitchingSolver/SingleHighDensityRouteStitchSolver.ts
4409
3789
  var SingleHighDensityRouteStitchSolver = class extends BaseSolver {
4410
3790
  mergedHdRoute;
@@ -4759,7 +4139,1126 @@ var convertSrjToGraphicsObject = (srj) => {
4759
4139
  };
4760
4140
  };
4761
4141
 
4762
- // lib/solvers/CapacityMeshSolver/CapacityMeshSolver.ts
4142
+ // lib/solvers/UnravelSolver/getNodesNearNode.ts
4143
+ function getNodesNearNode(params) {
4144
+ const { nodeId, nodeIdToSegmentIds, segmentIdToNodeIds, hops } = params;
4145
+ if (hops === 0) return [nodeId];
4146
+ const segments = nodeIdToSegmentIds.get(nodeId);
4147
+ const nodes = /* @__PURE__ */ new Set();
4148
+ for (const segmentId of segments) {
4149
+ const adjacentNodeIds = segmentIdToNodeIds.get(segmentId);
4150
+ for (const adjacentNodeId of adjacentNodeIds) {
4151
+ const ancestors = getNodesNearNode({
4152
+ nodeId: adjacentNodeId,
4153
+ nodeIdToSegmentIds,
4154
+ segmentIdToNodeIds,
4155
+ hops: hops - 1
4156
+ });
4157
+ for (const ancestor of ancestors) {
4158
+ nodes.add(ancestor);
4159
+ }
4160
+ }
4161
+ }
4162
+ return Array.from(nodes);
4163
+ }
4164
+
4165
+ // lib/solvers/UnravelSolver/createPointModificationsHash.ts
4166
+ var createPointModificationsHash = (pointModifications) => {
4167
+ return Array.from(pointModifications.entries()).map(
4168
+ ([id, { x, y, z }]) => `${id}(${x?.toFixed(3) ?? ""},${y?.toFixed(3) ?? ""},${z ?? ""})`
4169
+ ).sort().join("&");
4170
+ };
4171
+
4172
+ // lib/solvers/UnravelSolver/hasZRangeOverlap.ts
4173
+ var hasZRangeOverlap = (A_z1, A_z2, B_z1, B_z2) => {
4174
+ const Amin = Math.min(A_z1, A_z2);
4175
+ const Amax = Math.max(A_z1, A_z2);
4176
+ const Bmin = Math.min(B_z1, B_z2);
4177
+ const Bmax = Math.max(B_z1, B_z2);
4178
+ return Amin <= Bmax && Amax >= Bmin;
4179
+ };
4180
+
4181
+ // lib/solvers/UnravelSolver/getIssuesInSection.ts
4182
+ var getIssuesInSection = (section, nodeMap, pointModifications, connMap) => {
4183
+ const issues = [];
4184
+ const points = /* @__PURE__ */ new Map();
4185
+ for (const nodeId of section.allNodeIds) {
4186
+ for (const segmentPointId of section.segmentPointsInNode.get(nodeId)) {
4187
+ if (!points.has(segmentPointId)) {
4188
+ const ogPoint = section.segmentPointMap.get(segmentPointId);
4189
+ const modPoint = pointModifications.get(segmentPointId);
4190
+ points.set(segmentPointId, {
4191
+ x: modPoint?.x ?? ogPoint.x,
4192
+ y: modPoint?.y ?? ogPoint.y,
4193
+ z: modPoint?.z ?? ogPoint.z
4194
+ });
4195
+ }
4196
+ }
4197
+ }
4198
+ for (const nodeId of section.allNodeIds) {
4199
+ const node = nodeMap.get(nodeId);
4200
+ if (!node) continue;
4201
+ const nodeSegmentPairs = section.segmentPairsInNode.get(nodeId);
4202
+ for (const pair of nodeSegmentPairs) {
4203
+ const A = points.get(pair[0]);
4204
+ const B = points.get(pair[1]);
4205
+ if (A.z !== B.z) {
4206
+ issues.push({
4207
+ type: "transition_via",
4208
+ segmentPoints: pair,
4209
+ capacityMeshNodeId: nodeId,
4210
+ probabilityOfFailure: 0
4211
+ });
4212
+ }
4213
+ }
4214
+ for (let i = 0; i < nodeSegmentPairs.length; i++) {
4215
+ for (let j = i + 1; j < nodeSegmentPairs.length; j++) {
4216
+ if (connMap?.areIdsConnected(
4217
+ nodeSegmentPairs[i][0],
4218
+ nodeSegmentPairs[i][1]
4219
+ )) {
4220
+ continue;
4221
+ }
4222
+ const pair1 = nodeSegmentPairs[i];
4223
+ const pair2 = nodeSegmentPairs[j];
4224
+ const A = points.get(pair1[0]);
4225
+ const B = points.get(pair1[1]);
4226
+ const C = points.get(pair2[0]);
4227
+ const D = points.get(pair2[1]);
4228
+ if (!hasZRangeOverlap(A.z, B.z, C.z, D.z)) continue;
4229
+ const areCrossing = doSegmentsIntersect(A, B, C, D);
4230
+ const isSameLayer = A.z === B.z && C.z === D.z && A.z === C.z;
4231
+ if (areCrossing) {
4232
+ if (isSameLayer) {
4233
+ issues.push({
4234
+ type: "same_layer_crossing",
4235
+ segmentPoints: [pair1, pair2],
4236
+ capacityMeshNodeId: nodeId,
4237
+ crossingLine1: pair1,
4238
+ crossingLine2: pair2,
4239
+ probabilityOfFailure: 0
4240
+ });
4241
+ } else if (A.z === B.z && C.z !== D.z) {
4242
+ issues.push({
4243
+ type: "single_transition_crossing",
4244
+ segmentPoints: [pair1, pair2],
4245
+ capacityMeshNodeId: nodeId,
4246
+ sameLayerCrossingLine: pair1,
4247
+ transitionCrossingLine: pair2,
4248
+ probabilityOfFailure: 0
4249
+ });
4250
+ } else if (A.z !== B.z && C.z === D.z) {
4251
+ issues.push({
4252
+ type: "single_transition_crossing",
4253
+ segmentPoints: [pair1, pair2],
4254
+ capacityMeshNodeId: nodeId,
4255
+ sameLayerCrossingLine: pair2,
4256
+ transitionCrossingLine: pair1,
4257
+ probabilityOfFailure: 0
4258
+ });
4259
+ } else if (A.z !== B.z && C.z !== D.z) {
4260
+ issues.push({
4261
+ type: "double_transition_crossing",
4262
+ segmentPoints: [pair1, pair2],
4263
+ capacityMeshNodeId: nodeId,
4264
+ crossingLine1: pair1,
4265
+ crossingLine2: pair2,
4266
+ probabilityOfFailure: 0
4267
+ });
4268
+ }
4269
+ }
4270
+ }
4271
+ }
4272
+ }
4273
+ return issues;
4274
+ };
4275
+
4276
+ // lib/solvers/UnravelSolver/getLogProbability.ts
4277
+ var getLogProbability = (probability) => {
4278
+ const K = -2.3;
4279
+ return 1 - Math.exp(probability * K);
4280
+ };
4281
+
4282
+ // lib/solvers/UnravelSolver/applyOperationToPointModifications.ts
4283
+ var applyOperationToPointModifications = (pointModifications, operation, getPointInCandidate) => {
4284
+ if (operation.type === "change_layer") {
4285
+ for (const segmentPointId of operation.segmentPointIds) {
4286
+ const existingMods = pointModifications.get(segmentPointId) || {};
4287
+ pointModifications.set(segmentPointId, {
4288
+ ...existingMods,
4289
+ z: operation.newZ
4290
+ });
4291
+ }
4292
+ } else if (operation.type === "swap_position_on_segment") {
4293
+ const [ASpId, BSpId] = operation.segmentPointIds;
4294
+ const A = getPointInCandidate(ASpId);
4295
+ const B = getPointInCandidate(BSpId);
4296
+ const existingModsA = pointModifications.get(ASpId) || {};
4297
+ const existingModsB = pointModifications.get(BSpId) || {};
4298
+ pointModifications.set(ASpId, {
4299
+ ...existingModsA,
4300
+ x: B.x,
4301
+ y: B.y
4302
+ });
4303
+ pointModifications.set(BSpId, {
4304
+ ...existingModsB,
4305
+ x: A.x,
4306
+ y: A.y
4307
+ });
4308
+ } else if (operation.type === "combined") {
4309
+ for (const subOperation of operation.operations) {
4310
+ applyOperationToPointModifications(
4311
+ pointModifications,
4312
+ subOperation,
4313
+ getPointInCandidate
4314
+ );
4315
+ }
4316
+ }
4317
+ };
4318
+
4319
+ // lib/solvers/UnravelSolver/createSegmentPointMap.ts
4320
+ var createSegmentPointMap = (dedupedSegments, segmentIdToNodeIds) => {
4321
+ const segmentPoints = [];
4322
+ let highestSegmentPointId = 0;
4323
+ for (const segment of dedupedSegments) {
4324
+ for (const point of segment.assignedPoints) {
4325
+ segmentPoints.push({
4326
+ segmentPointId: `SP${highestSegmentPointId++}`,
4327
+ segmentId: segment.nodePortSegmentId,
4328
+ capacityMeshNodeIds: segmentIdToNodeIds.get(
4329
+ segment.nodePortSegmentId
4330
+ ),
4331
+ connectionName: point.connectionName,
4332
+ x: point.point.x,
4333
+ y: point.point.y,
4334
+ z: point.point.z,
4335
+ directlyConnectedSegmentPointIds: []
4336
+ });
4337
+ }
4338
+ }
4339
+ const segmentPointMap = /* @__PURE__ */ new Map();
4340
+ for (const segmentPoint of segmentPoints) {
4341
+ segmentPointMap.set(segmentPoint.segmentPointId, segmentPoint);
4342
+ }
4343
+ return segmentPointMap;
4344
+ };
4345
+
4346
+ // lib/solvers/UnravelSolver/UnravelSectionSolver.ts
4347
+ var UnravelSectionSolver = class extends BaseSolver {
4348
+ nodeMap;
4349
+ dedupedSegments;
4350
+ MUTABLE_HOPS = 1;
4351
+ unravelSection;
4352
+ candidates = [];
4353
+ lastProcessedCandidate = null;
4354
+ bestCandidate = null;
4355
+ originalCandidate;
4356
+ rootNodeId;
4357
+ nodeIdToSegmentIds;
4358
+ segmentIdToNodeIds;
4359
+ colorMap;
4360
+ tunedNodeCapacityMap;
4361
+ MAX_CANDIDATES = 500;
4362
+ selectedCandidateIndex = null;
4363
+ queuedOrExploredCandidatePointModificationHashes = /* @__PURE__ */ new Set();
4364
+ constructor(params) {
4365
+ super();
4366
+ this.MUTABLE_HOPS = params.MUTABLE_HOPS ?? this.MUTABLE_HOPS;
4367
+ this.nodeMap = params.nodeMap;
4368
+ this.dedupedSegments = params.dedupedSegments;
4369
+ this.nodeIdToSegmentIds = params.nodeIdToSegmentIds;
4370
+ this.segmentIdToNodeIds = params.segmentIdToNodeIds;
4371
+ this.rootNodeId = params.rootNodeId;
4372
+ this.colorMap = params.colorMap ?? {};
4373
+ this.unravelSection = this.createUnravelSection(params.segmentPointMap);
4374
+ this.tunedNodeCapacityMap = /* @__PURE__ */ new Map();
4375
+ for (const nodeId of this.unravelSection.allNodeIds) {
4376
+ this.tunedNodeCapacityMap.set(
4377
+ nodeId,
4378
+ getTunedTotalCapacity1(this.nodeMap.get(nodeId))
4379
+ );
4380
+ }
4381
+ this.originalCandidate = this.createInitialCandidate();
4382
+ this.candidates = [this.originalCandidate];
4383
+ }
4384
+ createUnravelSection(segmentPointMap) {
4385
+ const mutableNodeIds = getNodesNearNode({
4386
+ nodeId: this.rootNodeId,
4387
+ nodeIdToSegmentIds: this.nodeIdToSegmentIds,
4388
+ segmentIdToNodeIds: this.segmentIdToNodeIds,
4389
+ hops: this.MUTABLE_HOPS
4390
+ });
4391
+ const allNodeIds = getNodesNearNode({
4392
+ nodeId: this.rootNodeId,
4393
+ nodeIdToSegmentIds: this.nodeIdToSegmentIds,
4394
+ segmentIdToNodeIds: this.segmentIdToNodeIds,
4395
+ hops: this.MUTABLE_HOPS + 1
4396
+ });
4397
+ const immutableNodeIds = Array.from(
4398
+ new Set(allNodeIds).difference(new Set(mutableNodeIds))
4399
+ );
4400
+ if (!segmentPointMap) {
4401
+ segmentPointMap = createSegmentPointMap(
4402
+ this.dedupedSegments,
4403
+ this.segmentIdToNodeIds
4404
+ );
4405
+ }
4406
+ const segmentPoints = Array.from(segmentPointMap.values());
4407
+ const segmentPointsInNode = /* @__PURE__ */ new Map();
4408
+ for (const segmentPoint of segmentPoints) {
4409
+ for (const nodeId of segmentPoint.capacityMeshNodeIds) {
4410
+ segmentPointsInNode.set(nodeId, [
4411
+ ...segmentPointsInNode.get(nodeId) ?? [],
4412
+ segmentPoint.segmentPointId
4413
+ ]);
4414
+ }
4415
+ }
4416
+ const segmentPointsInSegment = /* @__PURE__ */ new Map();
4417
+ for (const segmentPoint of segmentPoints) {
4418
+ segmentPointsInSegment.set(segmentPoint.segmentId, [
4419
+ ...segmentPointsInSegment.get(segmentPoint.segmentId) ?? [],
4420
+ segmentPoint.segmentPointId
4421
+ ]);
4422
+ }
4423
+ for (let i = 0; i < segmentPoints.length; i++) {
4424
+ const A = segmentPoints[i];
4425
+ for (let j = i + 1; j < segmentPoints.length; j++) {
4426
+ const B = segmentPoints[j];
4427
+ if (B.segmentPointId === A.segmentPointId) continue;
4428
+ if (B.segmentId === A.segmentId) continue;
4429
+ if (B.connectionName !== A.connectionName) continue;
4430
+ if (A.capacityMeshNodeIds.some(
4431
+ (nId) => B.capacityMeshNodeIds.includes(nId)
4432
+ )) {
4433
+ A.directlyConnectedSegmentPointIds.push(B.segmentPointId);
4434
+ B.directlyConnectedSegmentPointIds.push(A.segmentPointId);
4435
+ }
4436
+ }
4437
+ }
4438
+ const segmentPairsInNode = /* @__PURE__ */ new Map();
4439
+ for (const nodeId of allNodeIds) {
4440
+ segmentPairsInNode.set(nodeId, []);
4441
+ }
4442
+ for (const A of segmentPoints) {
4443
+ for (const nodeId of A.capacityMeshNodeIds) {
4444
+ const otherSegmentPoints = segmentPointsInNode.get(nodeId).map((spId) => segmentPointMap.get(spId));
4445
+ const segmentPairs = segmentPairsInNode.get(nodeId);
4446
+ if (!segmentPairs) continue;
4447
+ for (const BId of A.directlyConnectedSegmentPointIds) {
4448
+ const B = segmentPointMap.get(BId);
4449
+ if (B.segmentPointId === A.segmentPointId) continue;
4450
+ if (!B.capacityMeshNodeIds.some((nId) => nId === nodeId)) continue;
4451
+ if (!segmentPairs.some(
4452
+ ([a, b]) => a === A.segmentPointId && b === B.segmentPointId || a === B.segmentPointId && b === A.segmentPointId
4453
+ )) {
4454
+ segmentPairs.push([A.segmentPointId, B.segmentPointId]);
4455
+ }
4456
+ }
4457
+ }
4458
+ }
4459
+ const mutableSegmentIds = /* @__PURE__ */ new Set();
4460
+ for (const nodeId of mutableNodeIds) {
4461
+ for (const segmentId of this.nodeIdToSegmentIds.get(nodeId)) {
4462
+ const allNodeIdsWithSegment = this.segmentIdToNodeIds.get(segmentId);
4463
+ if (allNodeIdsWithSegment.every(
4464
+ (nodeId2) => !this.nodeMap.get(nodeId2)._containsTarget
4465
+ )) {
4466
+ mutableSegmentIds.add(segmentId);
4467
+ }
4468
+ }
4469
+ }
4470
+ return {
4471
+ allNodeIds,
4472
+ mutableNodeIds,
4473
+ immutableNodeIds,
4474
+ mutableSegmentIds,
4475
+ segmentPairsInNode,
4476
+ segmentPointMap,
4477
+ segmentPointsInNode,
4478
+ segmentPointsInSegment
4479
+ };
4480
+ }
4481
+ createInitialCandidate() {
4482
+ const pointModifications = /* @__PURE__ */ new Map();
4483
+ const issues = getIssuesInSection(
4484
+ this.unravelSection,
4485
+ this.nodeMap,
4486
+ pointModifications
4487
+ );
4488
+ const g = this.computeG({
4489
+ issues,
4490
+ originalCandidate: {},
4491
+ operationsPerformed: 0,
4492
+ operation: {}
4493
+ });
4494
+ return {
4495
+ pointModifications,
4496
+ issues,
4497
+ g,
4498
+ h: 0,
4499
+ f: g,
4500
+ operationsPerformed: 0,
4501
+ candidateHash: createPointModificationsHash(pointModifications)
4502
+ // candidateFullHash: createFullPointModificationsHash(
4503
+ // this.unravelSection.segmentPointMap,
4504
+ // pointModifications,
4505
+ // ),
4506
+ };
4507
+ }
4508
+ get nextCandidate() {
4509
+ return this.candidates[0] ?? null;
4510
+ }
4511
+ getPointInCandidate(candidate, segmentPointId) {
4512
+ const originalPoint = this.unravelSection.segmentPointMap.get(segmentPointId);
4513
+ const modifications = candidate.pointModifications.get(segmentPointId);
4514
+ return {
4515
+ x: modifications?.x ?? originalPoint.x,
4516
+ y: modifications?.y ?? originalPoint.y,
4517
+ z: modifications?.z ?? originalPoint.z,
4518
+ segmentId: originalPoint.segmentId
4519
+ };
4520
+ }
4521
+ getOperationsForIssue(candidate, issue) {
4522
+ const operations = [];
4523
+ if (issue.type === "transition_via") {
4524
+ const [APointId, BPointId] = issue.segmentPoints;
4525
+ const pointA = this.getPointInCandidate(candidate, APointId);
4526
+ const pointB = this.getPointInCandidate(candidate, BPointId);
4527
+ if (this.unravelSection.mutableSegmentIds.has(pointA.segmentId)) {
4528
+ operations.push({
4529
+ type: "change_layer",
4530
+ newZ: pointB.z,
4531
+ segmentPointIds: [APointId]
4532
+ });
4533
+ }
4534
+ if (this.unravelSection.mutableSegmentIds.has(pointB.segmentId)) {
4535
+ operations.push({
4536
+ type: "change_layer",
4537
+ newZ: pointA.z,
4538
+ segmentPointIds: [BPointId]
4539
+ });
4540
+ }
4541
+ }
4542
+ if (issue.type === "same_layer_crossing") {
4543
+ const [APointId, BPointId] = issue.crossingLine1;
4544
+ const [CPointId, DPointId] = issue.crossingLine2;
4545
+ const sharedSegments = [];
4546
+ const A = this.unravelSection.segmentPointMap.get(APointId);
4547
+ const B = this.unravelSection.segmentPointMap.get(BPointId);
4548
+ const C = this.unravelSection.segmentPointMap.get(CPointId);
4549
+ const D = this.unravelSection.segmentPointMap.get(DPointId);
4550
+ if (A.segmentId === C.segmentId) {
4551
+ sharedSegments.push([APointId, CPointId]);
4552
+ }
4553
+ if (A.segmentId === D.segmentId) {
4554
+ sharedSegments.push([APointId, DPointId]);
4555
+ }
4556
+ if (B.segmentId === C.segmentId) {
4557
+ sharedSegments.push([BPointId, CPointId]);
4558
+ }
4559
+ if (B.segmentId === D.segmentId) {
4560
+ sharedSegments.push([BPointId, DPointId]);
4561
+ }
4562
+ for (const [EPointId, FPointId] of sharedSegments) {
4563
+ operations.push({
4564
+ type: "swap_position_on_segment",
4565
+ segmentPointIds: [EPointId, FPointId]
4566
+ });
4567
+ }
4568
+ const Amutable = this.unravelSection.mutableSegmentIds.has(A.segmentId);
4569
+ const Bmutable = this.unravelSection.mutableSegmentIds.has(B.segmentId);
4570
+ const Cmutable = this.unravelSection.mutableSegmentIds.has(C.segmentId);
4571
+ const Dmutable = this.unravelSection.mutableSegmentIds.has(D.segmentId);
4572
+ if (Amutable && Bmutable) {
4573
+ operations.push({
4574
+ type: "change_layer",
4575
+ newZ: A.z === 0 ? 1 : 0,
4576
+ segmentPointIds: [APointId, BPointId]
4577
+ });
4578
+ }
4579
+ if (Cmutable && Dmutable) {
4580
+ operations.push({
4581
+ type: "change_layer",
4582
+ newZ: C.z === 0 ? 1 : 0,
4583
+ segmentPointIds: [CPointId, DPointId]
4584
+ });
4585
+ }
4586
+ if (Amutable) {
4587
+ operations.push({
4588
+ type: "change_layer",
4589
+ newZ: A.z === 0 ? 1 : 0,
4590
+ segmentPointIds: [APointId]
4591
+ });
4592
+ }
4593
+ if (Bmutable) {
4594
+ operations.push({
4595
+ type: "change_layer",
4596
+ newZ: B.z === 0 ? 1 : 0,
4597
+ segmentPointIds: [BPointId]
4598
+ });
4599
+ }
4600
+ if (Cmutable) {
4601
+ operations.push({
4602
+ type: "change_layer",
4603
+ newZ: C.z === 0 ? 1 : 0,
4604
+ segmentPointIds: [CPointId]
4605
+ });
4606
+ }
4607
+ if (Dmutable) {
4608
+ operations.push({
4609
+ type: "change_layer",
4610
+ newZ: D.z === 0 ? 1 : 0,
4611
+ segmentPointIds: [DPointId]
4612
+ });
4613
+ }
4614
+ }
4615
+ return operations;
4616
+ }
4617
+ computeG(params) {
4618
+ const { issues, originalCandidate, operationsPerformed, operation } = params;
4619
+ const nodeProblemCounts = /* @__PURE__ */ new Map();
4620
+ for (const issue of issues) {
4621
+ if (!nodeProblemCounts.has(issue.capacityMeshNodeId)) {
4622
+ nodeProblemCounts.set(issue.capacityMeshNodeId, {
4623
+ numTransitionCrossings: 0,
4624
+ numSameLayerCrossings: 0,
4625
+ numEntryExitLayerChanges: 0
4626
+ });
4627
+ }
4628
+ const nodeProblemCount = nodeProblemCounts.get(issue.capacityMeshNodeId);
4629
+ if (issue.type === "transition_via") {
4630
+ nodeProblemCount.numTransitionCrossings++;
4631
+ } else if (issue.type === "same_layer_crossing") {
4632
+ nodeProblemCount.numSameLayerCrossings++;
4633
+ } else if (issue.type === "double_transition_crossing" || issue.type === "single_transition_crossing") {
4634
+ nodeProblemCount.numEntryExitLayerChanges++;
4635
+ } else if (issue.type === "same_layer_trace_imbalance_with_low_capacity") {
4636
+ }
4637
+ }
4638
+ let cost = 0;
4639
+ for (const [
4640
+ nodeId,
4641
+ {
4642
+ numEntryExitLayerChanges,
4643
+ numSameLayerCrossings,
4644
+ numTransitionCrossings
4645
+ }
4646
+ ] of nodeProblemCounts) {
4647
+ const estNumVias = numSameLayerCrossings * 0.82 + numEntryExitLayerChanges * 0.41 + numTransitionCrossings * 0.2;
4648
+ const estUsedCapacity = (estNumVias / 2) ** 1.1;
4649
+ const totalCapacity = this.tunedNodeCapacityMap.get(nodeId);
4650
+ const estPf = estUsedCapacity / totalCapacity;
4651
+ cost += getLogProbability(estPf);
4652
+ }
4653
+ return cost;
4654
+ }
4655
+ getNeighborByApplyingOperation(currentCandidate, operation) {
4656
+ const pointModifications = new Map(currentCandidate.pointModifications);
4657
+ applyOperationToPointModifications(
4658
+ pointModifications,
4659
+ operation,
4660
+ (segmentPointId) => this.getPointInCandidate(currentCandidate, segmentPointId)
4661
+ );
4662
+ const issues = getIssuesInSection(
4663
+ this.unravelSection,
4664
+ this.nodeMap,
4665
+ pointModifications
4666
+ );
4667
+ const operationsPerformed = currentCandidate.operationsPerformed + 1;
4668
+ const g = this.computeG({
4669
+ issues,
4670
+ originalCandidate: currentCandidate,
4671
+ operationsPerformed,
4672
+ operation
4673
+ });
4674
+ return {
4675
+ issues,
4676
+ g,
4677
+ h: 0,
4678
+ f: g,
4679
+ pointModifications,
4680
+ candidateHash: createPointModificationsHash(pointModifications),
4681
+ // TODO PERFORMANCE allow disabling this
4682
+ // candidateFullHash: createFullPointModificationsHash(
4683
+ // this.unravelSection.segmentPointMap,
4684
+ // pointModifications,
4685
+ // ),
4686
+ operationsPerformed
4687
+ };
4688
+ }
4689
+ getNeighborOperationsForCandidate(candidate) {
4690
+ return candidate.issues.flatMap(
4691
+ (issue) => this.getOperationsForIssue(candidate, issue)
4692
+ );
4693
+ }
4694
+ getNeighbors(candidate) {
4695
+ const neighbors = [];
4696
+ const operations = this.getNeighborOperationsForCandidate(candidate);
4697
+ for (const operation of operations) {
4698
+ const neighbor = this.getNeighborByApplyingOperation(candidate, operation);
4699
+ neighbors.push(neighbor);
4700
+ }
4701
+ return neighbors;
4702
+ }
4703
+ _step() {
4704
+ const candidate = this.candidates.shift();
4705
+ if (!candidate) {
4706
+ this.solved = true;
4707
+ return;
4708
+ }
4709
+ this.lastProcessedCandidate = candidate;
4710
+ if (candidate.f < (this.bestCandidate?.f ?? Infinity)) {
4711
+ this.bestCandidate = candidate;
4712
+ }
4713
+ this.getNeighbors(candidate).forEach((neighbor) => {
4714
+ const isPartialHashExplored = this.queuedOrExploredCandidatePointModificationHashes.has(
4715
+ neighbor.candidateHash
4716
+ );
4717
+ if (isPartialHashExplored) return;
4718
+ this.queuedOrExploredCandidatePointModificationHashes.add(
4719
+ neighbor.candidateHash
4720
+ );
4721
+ this.candidates.push(neighbor);
4722
+ });
4723
+ this.candidates.sort((a, b) => a.f - b.f);
4724
+ this.candidates.length = Math.min(
4725
+ this.candidates.length,
4726
+ this.MAX_CANDIDATES
4727
+ );
4728
+ }
4729
+ visualize() {
4730
+ const graphics = {
4731
+ points: [],
4732
+ lines: [],
4733
+ rects: [],
4734
+ circles: [],
4735
+ coordinateSystem: "cartesian",
4736
+ title: "Unravel Section Solver"
4737
+ };
4738
+ let candidate = null;
4739
+ if (this.selectedCandidateIndex !== null) {
4740
+ if (this.selectedCandidateIndex === "best") {
4741
+ candidate = this.bestCandidate;
4742
+ } else if (this.selectedCandidateIndex === "original") {
4743
+ candidate = this.originalCandidate;
4744
+ } else {
4745
+ candidate = this.candidates[this.selectedCandidateIndex];
4746
+ }
4747
+ } else {
4748
+ candidate = this.lastProcessedCandidate || this.candidates[0];
4749
+ }
4750
+ if (!candidate) return graphics;
4751
+ const modifiedSegmentPoints = /* @__PURE__ */ new Map();
4752
+ for (const [segmentPointId, segmentPoint] of this.unravelSection.segmentPointMap) {
4753
+ const modifiedPoint = { ...segmentPoint };
4754
+ const modification = candidate.pointModifications.get(segmentPointId);
4755
+ if (modification) {
4756
+ if (modification.x !== void 0) modifiedPoint.x = modification.x;
4757
+ if (modification.y !== void 0) modifiedPoint.y = modification.y;
4758
+ if (modification.z !== void 0) modifiedPoint.z = modification.z;
4759
+ }
4760
+ modifiedSegmentPoints.set(segmentPointId, modifiedPoint);
4761
+ }
4762
+ for (const [segmentPointId, segmentPoint] of modifiedSegmentPoints) {
4763
+ graphics.points.push({
4764
+ x: segmentPoint.x,
4765
+ y: segmentPoint.y,
4766
+ label: `${segmentPointId}
4767
+ Segment: ${segmentPoint.segmentId} ${this.unravelSection.mutableSegmentIds.has(segmentPoint.segmentId) ? "MUTABLE" : "IMMUTABLE"}
4768
+ Layer: ${segmentPoint.z}`,
4769
+ color: this.colorMap[segmentPoint.connectionName] || "#000"
4770
+ });
4771
+ }
4772
+ for (const nodeId of this.unravelSection.allNodeIds) {
4773
+ const node = this.nodeMap.get(nodeId);
4774
+ const isMutable = this.unravelSection.mutableNodeIds.includes(nodeId);
4775
+ graphics.rects.push({
4776
+ center: node.center,
4777
+ label: `${nodeId}
4778
+ ${node.width.toFixed(2)}x${node.height.toFixed(2)}
4779
+ ${isMutable ? "MUTABLE" : "IMMUTABLE"}`,
4780
+ color: isMutable ? "green" : "red",
4781
+ width: node.width / 8,
4782
+ height: node.height / 8
4783
+ });
4784
+ }
4785
+ for (const [segmentId, segmentPointIds] of this.unravelSection.segmentPointsInSegment) {
4786
+ if (segmentPointIds.length <= 1) continue;
4787
+ const points = segmentPointIds.map(
4788
+ (spId) => modifiedSegmentPoints.get(spId)
4789
+ );
4790
+ for (let i = 0; i < points.length - 1; i++) {
4791
+ graphics.lines.push({
4792
+ points: [
4793
+ { x: points[i].x, y: points[i].y },
4794
+ { x: points[i + 1].x, y: points[i + 1].y }
4795
+ ],
4796
+ strokeColor: this.colorMap[segmentId] || "#000"
4797
+ });
4798
+ }
4799
+ }
4800
+ for (const [segmentPointId, segmentPoint] of modifiedSegmentPoints) {
4801
+ for (const connectedPointId of segmentPoint.directlyConnectedSegmentPointIds) {
4802
+ if (segmentPointId < connectedPointId) {
4803
+ const connectedPoint = modifiedSegmentPoints.get(connectedPointId);
4804
+ const sameLayer = segmentPoint.z === connectedPoint.z;
4805
+ const commonLayer = segmentPoint.z;
4806
+ let strokeDash;
4807
+ if (sameLayer) {
4808
+ strokeDash = commonLayer === 0 ? void 0 : "10 5";
4809
+ } else {
4810
+ strokeDash = "3 3 10";
4811
+ }
4812
+ graphics.lines.push({
4813
+ points: [
4814
+ { x: segmentPoint.x, y: segmentPoint.y },
4815
+ { x: connectedPoint.x, y: connectedPoint.y }
4816
+ ],
4817
+ strokeDash,
4818
+ strokeColor: this.colorMap[segmentPoint.connectionName] || "#000"
4819
+ });
4820
+ }
4821
+ }
4822
+ }
4823
+ for (const issue of candidate.issues) {
4824
+ const node = this.nodeMap.get(issue.capacityMeshNodeId);
4825
+ if (issue.type === "transition_via") {
4826
+ for (const segmentPointId of issue.segmentPoints) {
4827
+ const segmentPoint = modifiedSegmentPoints.get(segmentPointId);
4828
+ graphics.circles.push({
4829
+ center: { x: segmentPoint.x, y: segmentPoint.y },
4830
+ radius: node.width / 16,
4831
+ stroke: "#ff0000",
4832
+ fill: "rgba(255, 0, 0, 0.2)",
4833
+ label: `Via Issue
4834
+ ${segmentPointId}
4835
+ Layer: ${segmentPoint.z}`
4836
+ });
4837
+ }
4838
+ } else if (issue.type === "same_layer_crossing") {
4839
+ for (const [sp1Id, sp2Id] of [
4840
+ issue.crossingLine1,
4841
+ issue.crossingLine2
4842
+ ]) {
4843
+ const sp1 = modifiedSegmentPoints.get(sp1Id);
4844
+ const sp2 = modifiedSegmentPoints.get(sp2Id);
4845
+ graphics.lines.push({
4846
+ points: [
4847
+ { x: sp1.x, y: sp1.y },
4848
+ { x: sp2.x, y: sp2.y }
4849
+ ],
4850
+ strokeColor: "rgba(255,0,0,0.2)",
4851
+ strokeWidth: node.width / 32
4852
+ });
4853
+ }
4854
+ }
4855
+ }
4856
+ for (const [segmentPointId, modification] of candidate.pointModifications) {
4857
+ const modifiedPoint = modifiedSegmentPoints.get(segmentPointId);
4858
+ const originalPoint = this.unravelSection.segmentPointMap.get(segmentPointId);
4859
+ graphics.circles.push({
4860
+ center: { x: modifiedPoint.x, y: modifiedPoint.y },
4861
+ radius: 0.05,
4862
+ stroke: "#0000ff",
4863
+ fill: "rgba(0, 0, 255, 0.2)",
4864
+ label: `${segmentPointId}
4865
+ Original: (${originalPoint.x.toFixed(2)}, ${originalPoint.y.toFixed(2)}, ${originalPoint.z})
4866
+ New: (${modifiedPoint.x.toFixed(2)}, ${modifiedPoint.y.toFixed(2)}, ${modifiedPoint.z})`
4867
+ });
4868
+ }
4869
+ return graphics;
4870
+ }
4871
+ };
4872
+
4873
+ // lib/solvers/UnravelSolver/getDedupedSegments.ts
4874
+ var getDedupedSegments = (assignedSegments) => {
4875
+ const dedupedSegments = [];
4876
+ const dedupedSegPointMap = /* @__PURE__ */ new Map();
4877
+ let highestSegmentId = -1;
4878
+ for (const seg of assignedSegments) {
4879
+ const segKey = `${seg.start.x}-${seg.start.y}-${seg.end.x}-${seg.end.y}`;
4880
+ const existingSeg = dedupedSegPointMap.get(segKey);
4881
+ if (!existingSeg) {
4882
+ highestSegmentId++;
4883
+ seg.nodePortSegmentId = `SEG${highestSegmentId}`;
4884
+ dedupedSegPointMap.set(segKey, seg);
4885
+ dedupedSegments.push(seg);
4886
+ continue;
4887
+ }
4888
+ seg.nodePortSegmentId = existingSeg.nodePortSegmentId;
4889
+ }
4890
+ return dedupedSegments;
4891
+ };
4892
+
4893
+ // lib/utils/getIntraNodeCrossingsFromSegments.ts
4894
+ var getIntraNodeCrossingsFromSegments = (segments) => {
4895
+ let numSameLayerCrossings = 0;
4896
+ const pointPairs = [];
4897
+ const transitionPairPoints = [];
4898
+ let numEntryExitLayerChanges = 0;
4899
+ const portPoints = segments.flatMap((seg) => seg.assignedPoints);
4900
+ for (const { connectionName: aConnName, point: A } of portPoints) {
4901
+ if (pointPairs.some((p) => p.connectionName === aConnName)) {
4902
+ continue;
4903
+ }
4904
+ if (transitionPairPoints.some((p) => p.connectionName === aConnName)) {
4905
+ continue;
4906
+ }
4907
+ const pointPair = {
4908
+ connectionName: aConnName,
4909
+ z: A.z,
4910
+ points: [A]
4911
+ };
4912
+ for (const { connectionName: bConnName, point: B } of portPoints) {
4913
+ if (aConnName !== bConnName) continue;
4914
+ if (A === B) continue;
4915
+ pointPair.points.push(B);
4916
+ if (pointPair.points.some((p) => p.z !== pointPair.z)) {
4917
+ numEntryExitLayerChanges++;
4918
+ transitionPairPoints.push(pointPair);
4919
+ break;
4920
+ } else {
4921
+ pointPairs.push(pointPair);
4922
+ break;
4923
+ }
4924
+ }
4925
+ }
4926
+ for (let i = 0; i < pointPairs.length; i++) {
4927
+ for (let j = i + 1; j < pointPairs.length; j++) {
4928
+ const pair1 = pointPairs[i];
4929
+ const pair2 = pointPairs[j];
4930
+ if (pair1.z === pair2.z && doSegmentsIntersect(
4931
+ pair1.points[0],
4932
+ pair1.points[1],
4933
+ pair2.points[0],
4934
+ pair2.points[1]
4935
+ )) {
4936
+ numSameLayerCrossings++;
4937
+ }
4938
+ }
4939
+ }
4940
+ let numTransitionCrossings = 0;
4941
+ for (let i = 0; i < transitionPairPoints.length; i++) {
4942
+ for (let j = i + 1; j < transitionPairPoints.length; j++) {
4943
+ const pair1 = transitionPairPoints[i];
4944
+ const pair2 = transitionPairPoints[j];
4945
+ if (doSegmentsIntersect(
4946
+ pair1.points[0],
4947
+ pair1.points[1],
4948
+ pair2.points[0],
4949
+ pair2.points[1]
4950
+ )) {
4951
+ numTransitionCrossings++;
4952
+ }
4953
+ }
4954
+ }
4955
+ for (let i = 0; i < transitionPairPoints.length; i++) {
4956
+ for (let j = 0; j < pointPairs.length; j++) {
4957
+ const pair1 = transitionPairPoints[i];
4958
+ const pair2 = pointPairs[j];
4959
+ if (doSegmentsIntersect(
4960
+ pair1.points[0],
4961
+ pair1.points[1],
4962
+ pair2.points[0],
4963
+ pair2.points[1]
4964
+ )) {
4965
+ numTransitionCrossings++;
4966
+ }
4967
+ }
4968
+ }
4969
+ return {
4970
+ numSameLayerCrossings,
4971
+ numEntryExitLayerChanges,
4972
+ numTransitionCrossings
4973
+ };
4974
+ };
4975
+
4976
+ // lib/solvers/UnravelSolver/calculateCrossingProbabilityOfFailure.ts
4977
+ var calculateNodeProbabilityOfFailure = (node, numSameLayerCrossings, numEntryExitLayerChanges, numTransitionCrossings) => {
4978
+ if (node?._containsTarget) return 0;
4979
+ const totalCapacity = getTunedTotalCapacity1(node);
4980
+ const estNumVias = numSameLayerCrossings * 0.82 + numEntryExitLayerChanges * 0.41 + numTransitionCrossings * 0.2;
4981
+ const estUsedCapacity = (estNumVias / 2) ** 1.1;
4982
+ const approxProb = estUsedCapacity / totalCapacity;
4983
+ return approxProb;
4984
+ };
4985
+
4986
+ // lib/solvers/UnravelSolver/UnravelMultiSectionSolver.ts
4987
+ var UnravelMultiSectionSolver = class extends BaseSolver {
4988
+ nodeMap;
4989
+ dedupedSegments;
4990
+ nodeIdToSegmentIds;
4991
+ segmentIdToNodeIds;
4992
+ colorMap;
4993
+ tunedNodeCapacityMap;
4994
+ MAX_NODE_ATTEMPTS = 2;
4995
+ MUTABLE_HOPS = 1;
4996
+ ACCEPTABLE_PF = 0.05;
4997
+ /**
4998
+ * Probability of failure for each node
4999
+ */
5000
+ nodePfMap;
5001
+ attemptsToFixNode;
5002
+ activeSolver = null;
5003
+ segmentPointMap;
5004
+ constructor({
5005
+ assignedSegments,
5006
+ colorMap,
5007
+ nodes
5008
+ }) {
5009
+ super();
5010
+ this.MAX_ITERATIONS = 1e5;
5011
+ this.dedupedSegments = getDedupedSegments(assignedSegments);
5012
+ this.nodeMap = /* @__PURE__ */ new Map();
5013
+ for (const node of nodes) {
5014
+ this.nodeMap.set(node.capacityMeshNodeId, node);
5015
+ }
5016
+ this.nodeIdToSegmentIds = /* @__PURE__ */ new Map();
5017
+ this.segmentIdToNodeIds = /* @__PURE__ */ new Map();
5018
+ this.attemptsToFixNode = /* @__PURE__ */ new Map();
5019
+ for (const segment of assignedSegments) {
5020
+ this.segmentIdToNodeIds.set(segment.nodePortSegmentId, [
5021
+ ...this.segmentIdToNodeIds.get(segment.nodePortSegmentId) ?? [],
5022
+ segment.capacityMeshNodeId
5023
+ ]);
5024
+ this.nodeIdToSegmentIds.set(segment.capacityMeshNodeId, [
5025
+ ...this.nodeIdToSegmentIds.get(segment.capacityMeshNodeId) ?? [],
5026
+ segment.nodePortSegmentId
5027
+ ]);
5028
+ }
5029
+ this.colorMap = colorMap ?? {};
5030
+ this.tunedNodeCapacityMap = /* @__PURE__ */ new Map();
5031
+ for (const [nodeId, node] of this.nodeMap) {
5032
+ this.tunedNodeCapacityMap.set(nodeId, getTunedTotalCapacity1(node));
5033
+ }
5034
+ this.segmentPointMap = createSegmentPointMap(
5035
+ this.dedupedSegments,
5036
+ this.segmentIdToNodeIds
5037
+ );
5038
+ this.nodePfMap = this.computeInitialPfMap();
5039
+ }
5040
+ computeInitialPfMap() {
5041
+ const pfMap = /* @__PURE__ */ new Map();
5042
+ for (const [nodeId, node] of this.nodeMap.entries()) {
5043
+ pfMap.set(nodeId, this.computeNodePf(node));
5044
+ }
5045
+ return pfMap;
5046
+ }
5047
+ computeNodePf(node) {
5048
+ const {
5049
+ numSameLayerCrossings,
5050
+ numEntryExitLayerChanges,
5051
+ numTransitionCrossings
5052
+ } = getIntraNodeCrossingsFromSegments(
5053
+ this.dedupedSegments.filter(
5054
+ (seg) => this.segmentIdToNodeIds.get(seg.nodePortSegmentId).includes(node.capacityMeshNodeId)
5055
+ )
5056
+ );
5057
+ const probabilityOfFailure = calculateNodeProbabilityOfFailure(
5058
+ node,
5059
+ numSameLayerCrossings,
5060
+ numEntryExitLayerChanges,
5061
+ numTransitionCrossings
5062
+ );
5063
+ return probabilityOfFailure;
5064
+ }
5065
+ _step() {
5066
+ if (this.iterations >= this.MAX_ITERATIONS - 1) {
5067
+ this.solved = true;
5068
+ return;
5069
+ }
5070
+ if (!this.activeSolver) {
5071
+ let highestPfNodeId = null;
5072
+ let highestPf = 0;
5073
+ for (const [nodeId, pf] of this.nodePfMap.entries()) {
5074
+ const pfReduced = pf * (1 - (this.attemptsToFixNode.get(nodeId) ?? 0) / this.MAX_NODE_ATTEMPTS);
5075
+ if (pfReduced > highestPf) {
5076
+ highestPf = pf;
5077
+ highestPfNodeId = nodeId;
5078
+ }
5079
+ }
5080
+ if (!highestPfNodeId || highestPf < this.ACCEPTABLE_PF) {
5081
+ this.solved = true;
5082
+ return;
5083
+ }
5084
+ this.attemptsToFixNode.set(
5085
+ highestPfNodeId,
5086
+ (this.attemptsToFixNode.get(highestPfNodeId) ?? 0) + 1
5087
+ );
5088
+ this.activeSolver = new UnravelSectionSolver({
5089
+ dedupedSegments: this.dedupedSegments,
5090
+ nodeMap: this.nodeMap,
5091
+ nodeIdToSegmentIds: this.nodeIdToSegmentIds,
5092
+ segmentIdToNodeIds: this.segmentIdToNodeIds,
5093
+ colorMap: this.colorMap,
5094
+ rootNodeId: highestPfNodeId,
5095
+ MUTABLE_HOPS: this.MUTABLE_HOPS,
5096
+ segmentPointMap: this.segmentPointMap
5097
+ });
5098
+ }
5099
+ this.activeSolver.step();
5100
+ const { bestCandidate, originalCandidate, lastProcessedCandidate } = this.activeSolver;
5101
+ const giveUpFactor = 1 + 4 * (1 - Math.min(1, this.activeSolver.iterations / 40));
5102
+ const shouldEarlyStop = lastProcessedCandidate && lastProcessedCandidate.g > bestCandidate.g * giveUpFactor;
5103
+ if (this.activeSolver.solved || shouldEarlyStop) {
5104
+ const foundBetterSolution = bestCandidate && bestCandidate.g < originalCandidate.g;
5105
+ if (foundBetterSolution) {
5106
+ for (const [
5107
+ segmentPointId,
5108
+ pointModification
5109
+ ] of bestCandidate.pointModifications.entries()) {
5110
+ const segmentPoint = this.segmentPointMap.get(segmentPointId);
5111
+ segmentPoint.x = pointModification.x ?? segmentPoint.x;
5112
+ segmentPoint.y = pointModification.y ?? segmentPoint.y;
5113
+ segmentPoint.z = pointModification.z ?? segmentPoint.z;
5114
+ }
5115
+ }
5116
+ for (const nodeId of this.activeSolver.unravelSection.allNodeIds) {
5117
+ this.nodePfMap.set(
5118
+ nodeId,
5119
+ this.computeNodePf(this.nodeMap.get(nodeId))
5120
+ );
5121
+ }
5122
+ this.activeSolver = null;
5123
+ }
5124
+ }
5125
+ visualize() {
5126
+ if (this.activeSolver) {
5127
+ return this.activeSolver.visualize();
5128
+ }
5129
+ const graphics = {
5130
+ lines: [],
5131
+ points: [],
5132
+ rects: [],
5133
+ circles: [],
5134
+ coordinateSystem: "cartesian",
5135
+ title: "Unravel Multi Section Solver"
5136
+ };
5137
+ for (const [nodeId, node] of this.nodeMap.entries()) {
5138
+ const probabilityOfFailure = this.nodePfMap.get(nodeId) || 0;
5139
+ const pf = Math.min(probabilityOfFailure, 1);
5140
+ const red = Math.floor(255 * pf);
5141
+ const green = Math.floor(255 * (1 - pf));
5142
+ const color = `rgb(${red}, ${green}, 0)`;
5143
+ graphics.rects.push({
5144
+ center: node.center,
5145
+ label: `${nodeId}
5146
+ ${node.width.toFixed(2)}x${node.height.toFixed(2)}
5147
+ Pf: ${probabilityOfFailure.toFixed(3)}`,
5148
+ color,
5149
+ width: node.width / 8,
5150
+ height: node.height / 8
5151
+ });
5152
+ }
5153
+ for (const segmentPoint of this.segmentPointMap.values()) {
5154
+ graphics.points.push({
5155
+ x: segmentPoint.x,
5156
+ y: segmentPoint.y,
5157
+ label: `${segmentPoint.segmentPointId}
5158
+ Segment: ${segmentPoint.segmentId}
5159
+ Layer: ${segmentPoint.z}`,
5160
+ color: this.colorMap[segmentPoint.connectionName] || "#000"
5161
+ });
5162
+ }
5163
+ const pointsBySegment = /* @__PURE__ */ new Map();
5164
+ for (const point of this.segmentPointMap.values()) {
5165
+ if (!pointsBySegment.has(point.segmentId)) {
5166
+ pointsBySegment.set(point.segmentId, []);
5167
+ }
5168
+ pointsBySegment.get(point.segmentId).push(point);
5169
+ }
5170
+ for (const [segmentId, points] of pointsBySegment.entries()) {
5171
+ if (points.length < 2) continue;
5172
+ const sortedPoints = [...points].sort(
5173
+ (a, b) => a.x !== b.x ? a.x - b.x : a.y - b.y
5174
+ );
5175
+ for (let i = 0; i < sortedPoints.length - 1; i++) {
5176
+ graphics.lines.push({
5177
+ points: [
5178
+ { x: sortedPoints[i].x, y: sortedPoints[i].y },
5179
+ { x: sortedPoints[i + 1].x, y: sortedPoints[i + 1].y }
5180
+ ],
5181
+ strokeColor: this.colorMap[segmentId] || "#000"
5182
+ });
5183
+ }
5184
+ }
5185
+ const processedConnections = /* @__PURE__ */ new Set();
5186
+ const allPoints = Array.from(this.segmentPointMap.values());
5187
+ for (let i = 0; i < allPoints.length; i++) {
5188
+ const point1 = allPoints[i];
5189
+ for (let j = i + 1; j < allPoints.length; j++) {
5190
+ const point2 = allPoints[j];
5191
+ if (point1.connectionName !== point2.connectionName || point1.segmentId === point2.segmentId) {
5192
+ continue;
5193
+ }
5194
+ const hasSharedNode = point1.capacityMeshNodeIds.some(
5195
+ (nodeId) => point2.capacityMeshNodeIds.includes(nodeId)
5196
+ );
5197
+ if (hasSharedNode) {
5198
+ const connectionKey = `${point1.segmentPointId}-${point2.segmentPointId}`;
5199
+ if (processedConnections.has(connectionKey)) continue;
5200
+ processedConnections.add(connectionKey);
5201
+ const sameLayer = point1.z === point2.z;
5202
+ const layer = point1.z;
5203
+ let strokeDash;
5204
+ if (sameLayer) {
5205
+ strokeDash = layer === 0 ? void 0 : "10 5";
5206
+ } else {
5207
+ strokeDash = "3 3 10";
5208
+ }
5209
+ graphics.lines.push({
5210
+ points: [
5211
+ { x: point1.x, y: point1.y },
5212
+ { x: point2.x, y: point2.y }
5213
+ ],
5214
+ strokeDash,
5215
+ strokeColor: this.colorMap[point1.connectionName] || "#666"
5216
+ });
5217
+ }
5218
+ }
5219
+ }
5220
+ return graphics;
5221
+ }
5222
+ getNodesWithPortPoints() {
5223
+ if (!this.solved) {
5224
+ throw new Error(
5225
+ "CapacitySegmentToPointSolver not solved, can't give port points yet"
5226
+ );
5227
+ }
5228
+ const nodeWithPortPointsMap = /* @__PURE__ */ new Map();
5229
+ for (const segment of this.dedupedSegments) {
5230
+ const segId = segment.nodePortSegmentId;
5231
+ for (const nodeId of this.segmentIdToNodeIds.get(segId)) {
5232
+ const node = this.nodeMap.get(nodeId);
5233
+ if (!nodeWithPortPointsMap.has(nodeId)) {
5234
+ nodeWithPortPointsMap.set(nodeId, {
5235
+ capacityMeshNodeId: nodeId,
5236
+ portPoints: [],
5237
+ center: node.center,
5238
+ width: node.width,
5239
+ height: node.height
5240
+ });
5241
+ }
5242
+ }
5243
+ }
5244
+ for (const segmentPoint of this.segmentPointMap.values()) {
5245
+ for (const nodeId of segmentPoint.capacityMeshNodeIds) {
5246
+ const nodeWithPortPoints = nodeWithPortPointsMap.get(nodeId);
5247
+ if (nodeWithPortPoints) {
5248
+ nodeWithPortPoints.portPoints.push({
5249
+ x: segmentPoint.x,
5250
+ y: segmentPoint.y,
5251
+ z: segmentPoint.z,
5252
+ connectionName: segmentPoint.connectionName
5253
+ });
5254
+ }
5255
+ }
5256
+ }
5257
+ return Array.from(nodeWithPortPointsMap.values());
5258
+ }
5259
+ };
5260
+
5261
+ // lib/solvers/AutoroutingPipelineSolver.ts
4763
5262
  function definePipelineStep(solverName, solverClass, getConstructorParams, opts = {}) {
4764
5263
  return {
4765
5264
  solverName,
@@ -4798,6 +5297,7 @@ var CapacityMeshSolver = class extends BaseSolver {
4798
5297
  edgeToPortSegmentSolver;
4799
5298
  colorMap;
4800
5299
  segmentToPointSolver;
5300
+ unravelMultiSectionSolver;
4801
5301
  segmentToPointOptimizer;
4802
5302
  highDensityRouteSolver;
4803
5303
  highDensityStitchSolver;
@@ -4880,9 +5380,20 @@ var CapacityMeshSolver = class extends BaseSolver {
4880
5380
  ];
4881
5381
  }
4882
5382
  ),
5383
+ // definePipelineStep(
5384
+ // "segmentToPointOptimizer",
5385
+ // CapacitySegmentPointOptimizer,
5386
+ // (cms) => [
5387
+ // {
5388
+ // assignedSegments: cms.segmentToPointSolver?.solvedSegments || [],
5389
+ // colorMap: cms.colorMap,
5390
+ // nodes: cms.nodeTargetMerger?.newNodes || [],
5391
+ // },
5392
+ // ],
5393
+ // ),
4883
5394
  definePipelineStep(
4884
- "segmentToPointOptimizer",
4885
- CapacitySegmentPointOptimizer,
5395
+ "unravelMultiSectionSolver",
5396
+ UnravelMultiSectionSolver,
4886
5397
  (cms) => [
4887
5398
  {
4888
5399
  assignedSegments: cms.segmentToPointSolver?.solvedSegments || [],
@@ -4893,7 +5404,7 @@ var CapacityMeshSolver = class extends BaseSolver {
4893
5404
  ),
4894
5405
  definePipelineStep("highDensityRouteSolver", HighDensitySolver, (cms) => [
4895
5406
  {
4896
- nodePortPoints: cms.segmentToPointOptimizer?.getNodesWithPortPoints() || [],
5407
+ nodePortPoints: cms.unravelMultiSectionSolver?.getNodesWithPortPoints() ?? cms.segmentToPointOptimizer?.getNodesWithPortPoints() ?? [],
4897
5408
  colorMap: cms.colorMap,
4898
5409
  connMap: cms.connMap
4899
5410
  }
@@ -4951,7 +5462,7 @@ var CapacityMeshSolver = class extends BaseSolver {
4951
5462
  const pathingViz = this.pathingSolver?.visualize();
4952
5463
  const edgeToPortSegmentViz = this.edgeToPortSegmentSolver?.visualize();
4953
5464
  const segmentToPointViz = this.segmentToPointSolver?.visualize();
4954
- const segmentOptimizationViz = this.segmentToPointOptimizer?.visualize();
5465
+ const segmentOptimizationViz = this.unravelMultiSectionSolver?.visualize() ?? this.segmentToPointOptimizer?.visualize();
4955
5466
  const highDensityViz = this.highDensityRouteSolver?.visualize();
4956
5467
  const highDensityStitchViz = this.highDensityStitchSolver?.visualize();
4957
5468
  const problemViz = {