@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.d.ts +228 -4
- package/dist/index.js +1313 -802
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -43,7 +43,9 @@ var BaseSolver = class {
|
|
|
43
43
|
iterations = 0;
|
|
44
44
|
progress = 0;
|
|
45
45
|
error = null;
|
|
46
|
+
activeSubSolver;
|
|
46
47
|
failedSubSolvers;
|
|
48
|
+
timeToSolve;
|
|
47
49
|
/** DO NOT OVERRIDE! Override _step() instead */
|
|
48
50
|
step() {
|
|
49
51
|
if (this.solved) return;
|
|
@@ -66,9 +68,12 @@ var BaseSolver = class {
|
|
|
66
68
|
_step() {
|
|
67
69
|
}
|
|
68
70
|
solve() {
|
|
71
|
+
const startTime = Date.now();
|
|
69
72
|
while (!this.solved && !this.failed) {
|
|
70
73
|
this.step();
|
|
71
74
|
}
|
|
75
|
+
const endTime = Date.now();
|
|
76
|
+
this.timeToSolve = endTime - startTime;
|
|
72
77
|
}
|
|
73
78
|
visualize() {
|
|
74
79
|
return {
|
|
@@ -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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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/
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
1144
|
-
const cachedObstacles = this.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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:
|
|
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)" :
|
|
1325
|
-
|
|
1326
|
-
|
|
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/
|
|
3351
|
-
var
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
for (const { connectionName: aConnName, point: A } of portPoints) {
|
|
3358
|
-
if (pointPairs.some((p) => p.connectionName === aConnName)) {
|
|
3359
|
-
continue;
|
|
3360
|
-
}
|
|
3361
|
-
if (transitionPairPoints.some((p) => p.connectionName === aConnName)) {
|
|
3362
|
-
continue;
|
|
3363
|
-
}
|
|
3364
|
-
const pointPair = {
|
|
3365
|
-
connectionName: aConnName,
|
|
3366
|
-
z: A.z,
|
|
3367
|
-
points: [A]
|
|
3368
|
-
};
|
|
3369
|
-
for (const { connectionName: bConnName, point: B } of portPoints) {
|
|
3370
|
-
if (aConnName !== bConnName) continue;
|
|
3371
|
-
if (A === B) continue;
|
|
3372
|
-
pointPair.points.push(B);
|
|
3373
|
-
}
|
|
3374
|
-
if (pointPair.points.some((p) => p.z !== pointPair.z)) {
|
|
3375
|
-
numEntryExitLayerChanges++;
|
|
3376
|
-
transitionPairPoints.push(pointPair);
|
|
3377
|
-
continue;
|
|
3378
|
-
}
|
|
3379
|
-
pointPairs.push(pointPair);
|
|
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
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
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
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
};
|
|
3429
|
-
};
|
|
3430
|
-
|
|
3431
|
-
// lib/solvers/CapacitySegmentPointOptimizer/CapacitySegmentPointOptimizer.ts
|
|
3432
|
-
var CapacitySegmentPointOptimizer = class extends BaseSolver {
|
|
3433
|
-
assignedSegments;
|
|
3434
|
-
colorMap;
|
|
3435
|
-
nodeMap;
|
|
3436
|
-
nodeIdToSegmentIds;
|
|
3437
|
-
segmentIdToNodeIds;
|
|
3438
|
-
currentMutatedSegments;
|
|
3439
|
-
allSegmentIds;
|
|
3440
|
-
lastAppliedOperation = null;
|
|
3441
|
-
lastCreatedOperation = null;
|
|
3442
|
-
currentNodeCosts;
|
|
3443
|
-
lastAcceptedIteration = 0;
|
|
3444
|
-
currentCost;
|
|
3445
|
-
randomSeed;
|
|
3446
|
-
numNodes;
|
|
3447
|
-
probabilityOfFailure;
|
|
3448
|
-
nodesThatCantFitVias;
|
|
3449
|
-
mutableSegments;
|
|
3450
|
-
VIA_DIAMETER = 0.6;
|
|
3451
|
-
OBSTACLE_MARGIN = 0.15;
|
|
3452
|
-
MAX_OPERATIONS_PER_MUTATION = 5;
|
|
3453
|
-
MAX_NODE_CHAIN_PER_MUTATION = 2;
|
|
3454
|
-
NOOP_ITERATIONS_BEFORE_EARLY_STOP = 2e4;
|
|
3455
|
-
// We use an extra property on segments to remember assigned points.
|
|
3456
|
-
// Each segment will get an added property "assignedPoints" which is an array of:
|
|
3457
|
-
// { connectionName: string, point: {x: number, y: number } }
|
|
3458
|
-
// This is a temporary extension used by the solver.
|
|
3459
|
-
constructor({
|
|
3460
|
-
assignedSegments,
|
|
3461
|
-
colorMap,
|
|
3462
|
-
nodes
|
|
3463
|
-
}) {
|
|
3464
|
-
super();
|
|
3465
|
-
this.MAX_ITERATIONS = 5e5;
|
|
3466
|
-
this.assignedSegments = assignedSegments;
|
|
3467
|
-
const dedupedSegments = [];
|
|
3468
|
-
const dedupedSegPointMap = /* @__PURE__ */ new Map();
|
|
3469
|
-
let highestSegmentId = -1;
|
|
3470
|
-
for (const seg of this.assignedSegments) {
|
|
3471
|
-
const segKey = `${seg.start.x}-${seg.start.y}-${seg.end.x}-${seg.end.y}`;
|
|
3472
|
-
const existingSeg = dedupedSegPointMap.get(segKey);
|
|
3473
|
-
if (!existingSeg) {
|
|
3474
|
-
highestSegmentId++;
|
|
3475
|
-
seg.nodePortSegmentId = `SEG${highestSegmentId}`;
|
|
3476
|
-
dedupedSegPointMap.set(segKey, seg);
|
|
3477
|
-
dedupedSegments.push(seg);
|
|
3478
|
-
continue;
|
|
3479
|
-
}
|
|
3480
|
-
seg.nodePortSegmentId = existingSeg.nodePortSegmentId;
|
|
3481
|
-
}
|
|
3482
|
-
this.currentMutatedSegments = /* @__PURE__ */ new Map();
|
|
3483
|
-
for (const seg of dedupedSegments) {
|
|
3484
|
-
this.currentMutatedSegments.set(seg.nodePortSegmentId, {
|
|
3485
|
-
...seg,
|
|
3486
|
-
assignedPoints: seg.assignedPoints?.map((p) => ({
|
|
3487
|
-
...p,
|
|
3488
|
-
point: { x: p.point.x, y: p.point.y, z: p.point.z }
|
|
3489
|
-
}))
|
|
3490
|
-
});
|
|
3491
|
-
}
|
|
3492
|
-
this.nodeIdToSegmentIds = /* @__PURE__ */ new Map();
|
|
3493
|
-
this.segmentIdToNodeIds = /* @__PURE__ */ new Map();
|
|
3494
|
-
for (const segment of this.assignedSegments) {
|
|
3495
|
-
this.segmentIdToNodeIds.set(segment.nodePortSegmentId, [
|
|
3496
|
-
...this.segmentIdToNodeIds.get(segment.nodePortSegmentId) ?? [],
|
|
3497
|
-
segment.capacityMeshNodeId
|
|
3498
|
-
]);
|
|
3499
|
-
this.nodeIdToSegmentIds.set(segment.capacityMeshNodeId, [
|
|
3500
|
-
...this.nodeIdToSegmentIds.get(segment.capacityMeshNodeId) ?? [],
|
|
3501
|
-
segment.nodePortSegmentId
|
|
3502
|
-
]);
|
|
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
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
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
|
-
|
|
3510
|
-
const
|
|
3511
|
-
|
|
3512
|
-
|
|
3513
|
-
|
|
3514
|
-
this.randomSeed = 1;
|
|
3515
|
-
this.allSegmentIds = Array.from(this.currentMutatedSegments.keys());
|
|
3516
|
-
this.nodesThatCantFitVias = /* @__PURE__ */ new Set();
|
|
3517
|
-
for (const nodeId of this.nodeIdToSegmentIds.keys()) {
|
|
3518
|
-
const node = this.nodeMap.get(nodeId);
|
|
3519
|
-
if (node.width < this.VIA_DIAMETER + this.OBSTACLE_MARGIN) {
|
|
3520
|
-
this.nodesThatCantFitVias.add(nodeId);
|
|
3521
|
-
}
|
|
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
|
-
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3530
|
-
|
|
3531
|
-
|
|
3532
|
-
computeNodeCost(nodeId) {
|
|
3533
|
-
const node = this.nodeMap.get(nodeId);
|
|
3534
|
-
if (node?._containsTarget) return 0;
|
|
3535
|
-
const totalCapacity = getTunedTotalCapacity1(node);
|
|
3536
|
-
const usedViaCapacity = this.getUsedViaCapacity(nodeId);
|
|
3537
|
-
const usedTraceCapacity = this.getUsedTraceCapacity(nodeId);
|
|
3538
|
-
const approxProb = usedViaCapacity * usedTraceCapacity / totalCapacity ** 2;
|
|
3539
|
-
const K = -2.3;
|
|
3540
|
-
return 1 - Math.exp(approxProb * K);
|
|
3541
|
-
}
|
|
3542
|
-
/**
|
|
3543
|
-
* Number of traces that can go through this node if they are completely
|
|
3544
|
-
* straight without crossings
|
|
3545
|
-
*/
|
|
3546
|
-
getUsedTraceCapacity(nodeId) {
|
|
3547
|
-
const segmentIds = this.nodeIdToSegmentIds.get(nodeId);
|
|
3548
|
-
const segments = segmentIds.map(
|
|
3549
|
-
(segmentId) => this.currentMutatedSegments.get(segmentId)
|
|
3550
|
-
);
|
|
3551
|
-
const points = segments.flatMap((s) => s.assignedPoints);
|
|
3552
|
-
const numTracesThroughNode = points.length / 2;
|
|
3553
|
-
const numLayers = 2;
|
|
3554
|
-
return numTracesThroughNode / numLayers;
|
|
3555
|
-
}
|
|
3556
|
-
/**
|
|
3557
|
-
* Granular via capacity is a consideration of capacity that includes...
|
|
3558
|
-
* - The number of traces
|
|
3559
|
-
* - The number of trace crossings (0-2 vias per trace crossing)
|
|
3560
|
-
* - Empirically, each crossing typically results in 0.82 vias
|
|
3561
|
-
* - e.g. 17 traces would typically have 51 crossings & 42 vias
|
|
3562
|
-
* - The number of layer changes (at least 1 via per layer change)
|
|
3563
|
-
* - We don't know how a entry/exit being on separated layers effects
|
|
3564
|
-
* the capacity/number of vias yet
|
|
3565
|
-
*
|
|
3566
|
-
* - Generally minimizing the number of crossings is pretty good, if there
|
|
3567
|
-
* is no trace crossing you basically don't have any used capacity
|
|
3568
|
-
* - If the entry/exit layer is different, you're guaranteed to have at least
|
|
3569
|
-
* one via
|
|
3570
|
-
*
|
|
3571
|
-
* - Total capacity is computed by estimating the number of vias that could
|
|
3572
|
-
* be created using the formula (viaFitAcross / 2) ** 1.1
|
|
3573
|
-
*/
|
|
3574
|
-
getUsedViaCapacity(nodeId) {
|
|
3575
|
-
const segmentIds = this.nodeIdToSegmentIds.get(nodeId);
|
|
3576
|
-
const segments = segmentIds.map(
|
|
3577
|
-
(segmentId) => this.currentMutatedSegments.get(segmentId)
|
|
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
|
-
|
|
3580
|
-
|
|
3581
|
-
|
|
3582
|
-
|
|
3583
|
-
|
|
3584
|
-
|
|
3585
|
-
|
|
3586
|
-
|
|
3587
|
-
}
|
|
3588
|
-
getRandomWeightedNodeId() {
|
|
3589
|
-
const nodeIdsWithCosts = [...this.currentNodeCosts.entries()].filter(([nodeId, cost]) => cost > 1e-5).filter(([nodeId]) => !this.nodeMap.get(nodeId)?._containsTarget);
|
|
3590
|
-
if (nodeIdsWithCosts.length === 0) {
|
|
3591
|
-
console.error(
|
|
3592
|
-
"No nodes with cost > 0.00001 (why are you even running this solver)"
|
|
3593
|
-
);
|
|
3594
|
-
return this.currentNodeCosts.keys().next().value;
|
|
3595
|
-
}
|
|
3596
|
-
const totalCost = nodeIdsWithCosts.reduce((acc, [, cost]) => acc + cost, 0);
|
|
3597
|
-
const randomValue = this.random() * totalCost;
|
|
3598
|
-
let cumulativeCost = 0;
|
|
3599
|
-
for (let i = 0; i < nodeIdsWithCosts.length; i++) {
|
|
3600
|
-
const [nodeId, cost] = nodeIdsWithCosts[i];
|
|
3601
|
-
cumulativeCost += cost;
|
|
3602
|
-
if (cumulativeCost >= randomValue) {
|
|
3603
|
-
return nodeId;
|
|
3604
|
-
}
|
|
3605
|
-
}
|
|
3606
|
-
throw new Error("RANDOM SELECTION FAILURE FOR NODES (this is a bug)");
|
|
3607
|
-
}
|
|
3608
|
-
getRandomWeightedSegmentId() {
|
|
3609
|
-
const nodeId = this.getRandomWeightedNodeId();
|
|
3610
|
-
const segmentsIds = this.nodeIdToSegmentIds.get(nodeId).filter((s) => this.isSegmentMutable(s));
|
|
3611
|
-
return segmentsIds[Math.floor(this.random() * segmentsIds.length)];
|
|
3612
|
-
}
|
|
3613
|
-
getMutableSegments() {
|
|
3614
|
-
const mutableSegments = /* @__PURE__ */ new Set();
|
|
3615
|
-
for (const segmentId of this.currentMutatedSegments.keys()) {
|
|
3616
|
-
const segment = this.currentMutatedSegments.get(segmentId);
|
|
3617
|
-
const nodes = this.segmentIdToNodeIds.get(segmentId);
|
|
3618
|
-
const isMutable = nodes.every(
|
|
3619
|
-
(nodeId) => !this.nodeMap.get(nodeId)?._containsTarget
|
|
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
|
|
3626
|
-
}
|
|
3627
|
-
isSegmentMutable(segmentId) {
|
|
3628
|
-
return this.mutableSegments.has(segmentId);
|
|
3492
|
+
return best;
|
|
3629
3493
|
}
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
operationType = "changeLayer";
|
|
3635
|
-
}
|
|
3636
|
-
if (operationType === "switch") {
|
|
3637
|
-
const randomPointIndex1 = Math.floor(
|
|
3638
|
-
this.random() * segment.assignedPoints.length
|
|
3639
|
-
);
|
|
3640
|
-
let randomPointIndex2 = randomPointIndex1;
|
|
3641
|
-
while (randomPointIndex1 === randomPointIndex2) {
|
|
3642
|
-
randomPointIndex2 = Math.floor(
|
|
3643
|
-
this.random() * segment.assignedPoints.length
|
|
3644
|
-
);
|
|
3645
|
-
}
|
|
3646
|
-
return {
|
|
3647
|
-
op: "switch",
|
|
3648
|
-
segmentId: randomSegmentId,
|
|
3649
|
-
point1Index: randomPointIndex1,
|
|
3650
|
-
point2Index: randomPointIndex2
|
|
3651
|
-
};
|
|
3494
|
+
// Find k nearest neighbors
|
|
3495
|
+
findKNearestNeighbors(queryPoint, k) {
|
|
3496
|
+
if (!this.root) {
|
|
3497
|
+
return [];
|
|
3652
3498
|
}
|
|
3653
|
-
const
|
|
3654
|
-
|
|
3655
|
-
);
|
|
3656
|
-
const point = segment.assignedPoints[randomPointIndex];
|
|
3657
|
-
return {
|
|
3658
|
-
op: "changeLayer",
|
|
3659
|
-
segmentId: randomSegmentId,
|
|
3660
|
-
pointIndex: randomPointIndex,
|
|
3661
|
-
newLayer: point.point.z === 0 ? 1 : 0
|
|
3662
|
-
};
|
|
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
|
-
|
|
3665
|
-
if (
|
|
3666
|
-
|
|
3667
|
-
const nodes = /* @__PURE__ */ new Set();
|
|
3668
|
-
for (const segmentId of segments) {
|
|
3669
|
-
const adjacentNodeIds = this.segmentIdToNodeIds.get(segmentId);
|
|
3670
|
-
for (const adjacentNodeId of adjacentNodeIds) {
|
|
3671
|
-
const ancestors = this.getNodesNearNode(adjacentNodeId, hops - 1);
|
|
3672
|
-
for (const ancestor of ancestors) {
|
|
3673
|
-
nodes.add(ancestor);
|
|
3674
|
-
}
|
|
3675
|
-
}
|
|
3503
|
+
kNearestNeighborSearch(node, queryPoint, depth, neighbors, k) {
|
|
3504
|
+
if (!node) {
|
|
3505
|
+
return;
|
|
3676
3506
|
}
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
const
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
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/
|
|
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
|
-
"
|
|
4885
|
-
|
|
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 = {
|