@tscircuit/capacity-autorouter 0.0.30 → 0.0.32

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/LICENSE CHANGED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 tscircuit
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.d.ts CHANGED
@@ -118,12 +118,50 @@ declare class CapacityMeshEdgeSolver extends BaseSolver {
118
118
  visualize(): GraphicsObject;
119
119
  }
120
120
 
121
+ type BucketCoordinate$1 = `${number}x${number}`;
122
+ declare class ObstacleTree {
123
+ obstacles: Obstacle[];
124
+ buckets: Map<BucketCoordinate$1, [Obstacle, number][]>;
125
+ CELL_SIZE: number;
126
+ constructor(obstacles: Obstacle[]);
127
+ getBucketKey(x: number, y: number): BucketCoordinate$1;
128
+ getNodesInArea(centerX: number, centerY: number, width: number, height: number): Obstacle[];
129
+ }
130
+
131
+ interface Target$1 {
132
+ x: number;
133
+ y: number;
134
+ bounds: {
135
+ minX: number;
136
+ minY: number;
137
+ maxX: number;
138
+ maxY: number;
139
+ };
140
+ connectionName: string;
141
+ availableZ: number[];
142
+ }
143
+ type BucketCoordinate = `${number}x${number}`;
144
+ declare class TargetTree {
145
+ targets: Target$1[];
146
+ buckets: Map<BucketCoordinate, [Target$1, number][]>;
147
+ CELL_SIZE: number;
148
+ constructor(targets: Target$1[]);
149
+ getBucketKey(x: number, y: number): BucketCoordinate;
150
+ getTargetsInArea(centerX: number, centerY: number, width: number, height: number): Target$1[];
151
+ }
152
+
121
153
  interface CapacityMeshNodeSolverOptions$1 {
122
154
  capacityDepth?: number;
123
155
  }
124
156
  interface Target {
125
157
  x: number;
126
158
  y: number;
159
+ bounds: {
160
+ minX: number;
161
+ minY: number;
162
+ maxX: number;
163
+ maxY: number;
164
+ };
127
165
  connectionName: string;
128
166
  availableZ: number[];
129
167
  }
@@ -136,7 +174,10 @@ declare class CapacityMeshNodeSolver extends BaseSolver {
136
174
  layerCount: number;
137
175
  MAX_DEPTH: number;
138
176
  targets: Target[];
177
+ targetTree: TargetTree;
178
+ obstacleTree: ObstacleTree;
139
179
  constructor(srj: SimpleRouteJson, opts?: CapacityMeshNodeSolverOptions$1);
180
+ computeTargets(): Target[];
140
181
  _nextNodeCounter: number;
141
182
  getNextNodeId(): string;
142
183
  getCapacityFromDepth(depth: number): number;
@@ -965,6 +1006,11 @@ interface UnravelSection {
965
1006
  segmentPairsInNode: Map<CapacityMeshNodeId, Array<[SegmentPointId, SegmentPointId]>>;
966
1007
  segmentPointsInNode: Map<CapacityMeshNodeId, SegmentPointId[]>;
967
1008
  segmentPointsInSegment: Map<SegmentId, SegmentPointId[]>;
1009
+ originalPointMap: Map<SegmentPointId, {
1010
+ x: number;
1011
+ y: number;
1012
+ z: number;
1013
+ }>;
968
1014
  }
969
1015
  interface UnravelChangeLayerOperation {
970
1016
  type: "change_layer";
@@ -1013,6 +1059,12 @@ type UnravelCandidate = {
1013
1059
  f: number;
1014
1060
  };
1015
1061
 
1062
+ type SegmentPointMapAndReverseMaps = {
1063
+ segmentPointMap: SegmentPointMap;
1064
+ nodeToSegmentPointMap: Map<CapacityMeshNodeId, SegmentPointId[]>;
1065
+ segmentToSegmentPointMap: Map<SegmentId, SegmentPointId[]>;
1066
+ };
1067
+
1016
1068
  /**
1017
1069
  * The UntangleSectionSolver optimizes a section of connected capacity nodes
1018
1070
  * with their deduplicated segments.
@@ -1068,8 +1120,10 @@ declare class UnravelSectionSolver extends BaseSolver {
1068
1120
  nodeIdToSegmentIds: Map<CapacityMeshNodeId, CapacityMeshNodeId[]>;
1069
1121
  segmentIdToNodeIds: Map<CapacityMeshNodeId, CapacityMeshNodeId[]>;
1070
1122
  segmentPointMap?: SegmentPointMap;
1123
+ nodeToSegmentPointMap?: Map<CapacityMeshNodeId, SegmentPointId[]>;
1124
+ segmentToSegmentPointMap?: Map<SegmentId, SegmentPointId[]>;
1071
1125
  });
1072
- createUnravelSection(segmentPointMap?: SegmentPointMap): UnravelSection;
1126
+ createUnravelSection(largeSpMaps?: SegmentPointMapAndReverseMaps): UnravelSection;
1073
1127
  createInitialCandidate(): UnravelCandidate;
1074
1128
  get nextCandidate(): UnravelCandidate | null;
1075
1129
  getPointInCandidate(candidate: UnravelCandidate, segmentPointId: SegmentPointId): {
@@ -1085,7 +1139,7 @@ declare class UnravelSectionSolver extends BaseSolver {
1085
1139
  operationsPerformed: number;
1086
1140
  operation: UnravelOperation;
1087
1141
  }): number;
1088
- getNeighborByApplyingOperation(currentCandidate: UnravelCandidate, operation: UnravelOperation): UnravelCandidate;
1142
+ getUnexploredNeighborByApplyingOperation(currentCandidate: UnravelCandidate, operation: UnravelOperation): UnravelCandidate | null;
1089
1143
  getNeighborOperationsForCandidate(candidate: UnravelCandidate): UnravelOperation[];
1090
1144
  getNeighbors(candidate: UnravelCandidate): UnravelCandidate[];
1091
1145
  _step(): void;
@@ -1098,6 +1152,8 @@ declare class UnravelMultiSectionSolver extends BaseSolver {
1098
1152
  dedupedSegments: SegmentWithAssignedPoints[];
1099
1153
  nodeIdToSegmentIds: Map<CapacityMeshNodeId, CapacityMeshNodeId[]>;
1100
1154
  segmentIdToNodeIds: Map<CapacityMeshNodeId, CapacityMeshNodeId[]>;
1155
+ nodeToSegmentPointMap: Map<CapacityMeshNodeId, SegmentPointId[]>;
1156
+ segmentToSegmentPointMap: Map<SegmentId, SegmentPointId[]>;
1101
1157
  colorMap: Record<string, string>;
1102
1158
  tunedNodeCapacityMap: Map<CapacityMeshNodeId, number>;
1103
1159
  MAX_NODE_ATTEMPTS: number;
package/dist/index.js CHANGED
@@ -918,24 +918,6 @@ var safeTransparentize = (color, amount) => {
918
918
  }
919
919
  };
920
920
 
921
- // lib/utils/isPointInRect.ts
922
- function isPointInRect(point, rect) {
923
- return point.x >= rect.center.x - rect.width / 2 && point.x <= rect.center.x + rect.width / 2 && point.y >= rect.center.y - rect.height / 2 && point.y <= rect.center.y + rect.height / 2;
924
- }
925
-
926
- // lib/utils/doRectsOverlap.ts
927
- function doRectsOverlap(rect1, rect2) {
928
- const rect1Left = rect1.center.x - rect1.width / 2;
929
- const rect1Right = rect1.center.x + rect1.width / 2;
930
- const rect1Top = rect1.center.y - rect1.height / 2;
931
- const rect1Bottom = rect1.center.y + rect1.height / 2;
932
- const rect2Left = rect2.center.x - rect2.width / 2;
933
- const rect2Right = rect2.center.x + rect2.width / 2;
934
- const rect2Top = rect2.center.y - rect2.height / 2;
935
- const rect2Bottom = rect2.center.y + rect2.height / 2;
936
- return rect1Left <= rect2Right && rect1Right >= rect2Left && rect1Top <= rect2Bottom && rect1Bottom >= rect2Top;
937
- }
938
-
939
921
  // lib/utils/mapLayerNameToZ.ts
940
922
  var mapLayerNameToZ = (layerName, layerCount) => {
941
923
  if (layerName === "top") return 0;
@@ -966,6 +948,108 @@ var calculateOptimalCapacityDepth = (initialWidth, targetMinCapacity = 0.5, maxD
966
948
  return Math.max(1, depth);
967
949
  };
968
950
 
951
+ // lib/data-structures/ObstacleTree.ts
952
+ var ObstacleTree = class {
953
+ constructor(obstacles) {
954
+ this.obstacles = obstacles;
955
+ this.buckets = /* @__PURE__ */ new Map();
956
+ for (let i = 0; i < obstacles.length; i++) {
957
+ const obstacle = obstacles[i];
958
+ const nodeMinX = obstacle.center.x - obstacle.width / 2;
959
+ const nodeMinY = obstacle.center.y - obstacle.height / 2;
960
+ const nodeMaxX = obstacle.center.x + obstacle.width / 2;
961
+ const nodeMaxY = obstacle.center.y + obstacle.height / 2;
962
+ for (let x = nodeMinX; x <= nodeMaxX; x += this.CELL_SIZE) {
963
+ for (let y = nodeMinY; y <= nodeMaxY; y += this.CELL_SIZE) {
964
+ const bucketKey = this.getBucketKey(x, y);
965
+ const bucket = this.buckets.get(bucketKey);
966
+ if (!bucket) {
967
+ this.buckets.set(bucketKey, [[obstacle, i]]);
968
+ } else {
969
+ bucket.push([obstacle, i]);
970
+ }
971
+ }
972
+ }
973
+ }
974
+ }
975
+ buckets;
976
+ CELL_SIZE = 0.4;
977
+ getBucketKey(x, y) {
978
+ return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
979
+ }
980
+ getNodesInArea(centerX, centerY, width, height) {
981
+ const obstacles = [];
982
+ const alreadyAddedObstacles = /* @__PURE__ */ new Set();
983
+ const minX = centerX - width / 2;
984
+ const minY = centerY - height / 2;
985
+ const maxX = centerX + width / 2;
986
+ const maxY = centerY + height / 2;
987
+ for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
988
+ for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
989
+ const bucketKey = this.getBucketKey(x, y);
990
+ const bucket = this.buckets.get(bucketKey) || [];
991
+ for (const obstacleWithIndex of bucket) {
992
+ if (alreadyAddedObstacles.has(obstacleWithIndex[1])) continue;
993
+ alreadyAddedObstacles.add(obstacleWithIndex[1]);
994
+ obstacles.push(obstacleWithIndex[0]);
995
+ }
996
+ }
997
+ }
998
+ return obstacles;
999
+ }
1000
+ };
1001
+
1002
+ // lib/data-structures/TargetTree.ts
1003
+ var TargetTree = class {
1004
+ constructor(targets) {
1005
+ this.targets = targets;
1006
+ this.buckets = /* @__PURE__ */ new Map();
1007
+ for (let i = 0; i < targets.length; i++) {
1008
+ const target = targets[i];
1009
+ const targetBucketMinX = Math.floor(target.bounds.minX / this.CELL_SIZE) * this.CELL_SIZE;
1010
+ const targetBucketMinY = Math.floor(target.bounds.minY / this.CELL_SIZE) * this.CELL_SIZE;
1011
+ const targetMaxX = target.bounds.maxX;
1012
+ const targetMaxY = target.bounds.maxY;
1013
+ for (let x = targetBucketMinX; x <= targetMaxX; x += this.CELL_SIZE) {
1014
+ for (let y = targetBucketMinY; y <= targetMaxY; y += this.CELL_SIZE) {
1015
+ const bucketKey = this.getBucketKey(x, y);
1016
+ const bucket = this.buckets.get(bucketKey);
1017
+ if (!bucket) {
1018
+ this.buckets.set(bucketKey, [[target, i]]);
1019
+ } else {
1020
+ bucket.push([target, i]);
1021
+ }
1022
+ }
1023
+ }
1024
+ }
1025
+ }
1026
+ buckets;
1027
+ CELL_SIZE = 5;
1028
+ getBucketKey(x, y) {
1029
+ return `${Math.floor(x / this.CELL_SIZE)}x${Math.floor(y / this.CELL_SIZE)}`;
1030
+ }
1031
+ getTargetsInArea(centerX, centerY, width, height) {
1032
+ const targets = [];
1033
+ const alreadyAddedTargets = /* @__PURE__ */ new Set();
1034
+ const minX = Math.floor((centerX - width / 2) / this.CELL_SIZE) * this.CELL_SIZE;
1035
+ const minY = Math.floor((centerY - height / 2) / this.CELL_SIZE) * this.CELL_SIZE;
1036
+ const maxX = centerX + width / 2;
1037
+ const maxY = centerY + height / 2;
1038
+ for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
1039
+ for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
1040
+ const bucketKey = this.getBucketKey(x, y);
1041
+ const bucket = this.buckets.get(bucketKey) || [];
1042
+ for (const targetWithIndex of bucket) {
1043
+ if (alreadyAddedTargets.has(targetWithIndex[1])) continue;
1044
+ alreadyAddedTargets.add(targetWithIndex[1]);
1045
+ targets.push(targetWithIndex[0]);
1046
+ }
1047
+ }
1048
+ }
1049
+ return targets;
1050
+ }
1051
+ };
1052
+
969
1053
  // lib/solvers/CapacityMeshSolver/CapacityMeshNodeSolver1.ts
970
1054
  var CapacityMeshNodeSolver = class extends BaseSolver {
971
1055
  constructor(srj, opts = {}) {
@@ -1009,13 +1093,9 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1009
1093
  ];
1010
1094
  this.finishedNodes = [];
1011
1095
  this.nodeToXYOverlappingObstaclesMap = /* @__PURE__ */ new Map();
1012
- this.targets = this.srj.connections.flatMap(
1013
- (c) => c.pointsToConnect.map((p) => ({
1014
- ...p,
1015
- connectionName: c.name,
1016
- availableZ: p.layer === "top" ? [0] : [1]
1017
- }))
1018
- );
1096
+ this.obstacleTree = new ObstacleTree(this.srj.obstacles);
1097
+ this.targets = this.computeTargets();
1098
+ this.targetTree = new TargetTree(this.targets);
1019
1099
  }
1020
1100
  unfinishedNodes;
1021
1101
  finishedNodes;
@@ -1024,6 +1104,40 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1024
1104
  // targetObstacleMap: Record<string, { obstacle: Obstacle, node: CapacityMeshNode }>
1025
1105
  MAX_DEPTH = 4;
1026
1106
  targets;
1107
+ targetTree;
1108
+ obstacleTree;
1109
+ computeTargets() {
1110
+ const targets = [];
1111
+ for (const conn of this.srj.connections) {
1112
+ for (const ptc of conn.pointsToConnect) {
1113
+ const obstacles = this.obstacleTree.getNodesInArea(ptc.x, ptc.y, 0.01, 0.01).filter(
1114
+ (o) => o.zLayers.some((z) => ptc.layer === "top" ? z === 0 : z === 1)
1115
+ );
1116
+ let bounds = {
1117
+ minX: ptc.x - 5e-3,
1118
+ minY: ptc.y - 5e-3,
1119
+ maxX: ptc.x + 5e-3,
1120
+ maxY: ptc.y + 5e-3
1121
+ };
1122
+ if (obstacles.length > 0) {
1123
+ bounds = {
1124
+ minX: Math.min(...obstacles.map((o) => o.center.x - o.width / 2)),
1125
+ minY: Math.min(...obstacles.map((o) => o.center.y - o.height / 2)),
1126
+ maxX: Math.max(...obstacles.map((o) => o.center.x + o.width / 2)),
1127
+ maxY: Math.max(...obstacles.map((o) => o.center.y + o.height / 2))
1128
+ };
1129
+ }
1130
+ const target = {
1131
+ ...ptc,
1132
+ connectionName: conn.name,
1133
+ availableZ: ptc.layer === "top" ? [0] : [1],
1134
+ bounds
1135
+ };
1136
+ targets.push(target);
1137
+ }
1138
+ }
1139
+ return targets;
1140
+ }
1027
1141
  _nextNodeCounter = 0;
1028
1142
  getNextNodeId() {
1029
1143
  return `cn${this._nextNodeCounter++}`;
@@ -1032,18 +1146,17 @@ var CapacityMeshNodeSolver = class extends BaseSolver {
1032
1146
  return (this.MAX_DEPTH - depth + 1) ** 2;
1033
1147
  }
1034
1148
  getTargetIfNodeContainsTarget(node) {
1035
- const overlappingObstacles = this.getXYOverlappingObstacles(node);
1036
- for (const target of this.targets) {
1037
- if (!target.availableZ.some((z) => node.availableZ.includes(z))) continue;
1038
- const targetObstacle = overlappingObstacles.find(
1039
- (o) => isPointInRect(target, o)
1040
- );
1041
- if (targetObstacle) {
1042
- if (doRectsOverlap(node, targetObstacle)) {
1043
- return target;
1044
- }
1045
- }
1046
- if (target.x >= node.center.x - node.width / 2 && target.x <= node.center.x + node.width / 2 && target.y >= node.center.y - node.height / 2 && target.y <= node.center.y + node.height / 2) {
1149
+ const nearbyTargets = node.width > this.targetTree.CELL_SIZE * 4 ? this.targets : this.targetTree.getTargetsInArea(
1150
+ node.center.x,
1151
+ node.center.y,
1152
+ node.width,
1153
+ node.height
1154
+ );
1155
+ for (const target of nearbyTargets) {
1156
+ if (
1157
+ // Check if the node and target bounds overlap
1158
+ target.bounds.minX <= node.center.x + node.width / 2 && target.bounds.maxX >= node.center.x - node.width / 2 && target.bounds.minY <= node.center.y + node.height / 2 && target.bounds.maxY >= node.center.y - node.height / 2 && target.availableZ.some((z) => node.availableZ.includes(z))
1159
+ ) {
1047
1160
  return target;
1048
1161
  }
1049
1162
  }
@@ -3791,19 +3904,14 @@ var hasZRangeOverlap = (A_z1, A_z2, B_z1, B_z2) => {
3791
3904
  // lib/solvers/UnravelSolver/getIssuesInSection.ts
3792
3905
  var getIssuesInSection = (section, nodeMap, pointModifications, connMap) => {
3793
3906
  const issues = [];
3794
- const points = /* @__PURE__ */ new Map();
3795
- for (const nodeId of section.allNodeIds) {
3796
- for (const segmentPointId of section.segmentPointsInNode.get(nodeId)) {
3797
- if (!points.has(segmentPointId)) {
3798
- const ogPoint = section.segmentPointMap.get(segmentPointId);
3799
- const modPoint = pointModifications.get(segmentPointId);
3800
- points.set(segmentPointId, {
3801
- x: modPoint?.x ?? ogPoint.x,
3802
- y: modPoint?.y ?? ogPoint.y,
3803
- z: modPoint?.z ?? ogPoint.z
3804
- });
3805
- }
3806
- }
3907
+ const points = new Map(section.originalPointMap);
3908
+ for (const [segmentPointId, modPoint] of pointModifications.entries()) {
3909
+ const ogPoint = points.get(segmentPointId);
3910
+ points.set(segmentPointId, {
3911
+ x: modPoint.x ?? ogPoint.x,
3912
+ y: modPoint.y ?? ogPoint.y,
3913
+ z: modPoint.z ?? ogPoint.z
3914
+ });
3807
3915
  }
3808
3916
  for (const nodeId of section.allNodeIds) {
3809
3917
  const node = nodeMap.get(nodeId);
@@ -3928,11 +4036,14 @@ var applyOperationToPointModifications = (pointModifications, operation, getPoin
3928
4036
 
3929
4037
  // lib/solvers/UnravelSolver/createSegmentPointMap.ts
3930
4038
  var createSegmentPointMap = (dedupedSegments, segmentIdToNodeIds) => {
4039
+ const segmentPointMap = /* @__PURE__ */ new Map();
4040
+ const nodeToSegmentPointMap = /* @__PURE__ */ new Map();
4041
+ const segmentToSegmentPointMap = /* @__PURE__ */ new Map();
3931
4042
  const segmentPoints = [];
3932
4043
  let highestSegmentPointId = 0;
3933
4044
  for (const segment of dedupedSegments) {
3934
4045
  for (const point of segment.assignedPoints) {
3935
- segmentPoints.push({
4046
+ const sp = {
3936
4047
  segmentPointId: `SP${highestSegmentPointId++}`,
3937
4048
  segmentId: segment.nodePortSegmentId,
3938
4049
  capacityMeshNodeIds: segmentIdToNodeIds.get(
@@ -3943,14 +4054,26 @@ var createSegmentPointMap = (dedupedSegments, segmentIdToNodeIds) => {
3943
4054
  y: point.point.y,
3944
4055
  z: point.point.z,
3945
4056
  directlyConnectedSegmentPointIds: []
3946
- });
4057
+ };
4058
+ segmentPointMap.set(sp.segmentPointId, sp);
4059
+ for (const nodeId of sp.capacityMeshNodeIds) {
4060
+ nodeToSegmentPointMap.set(nodeId, [
4061
+ ...nodeToSegmentPointMap.get(nodeId) ?? [],
4062
+ sp.segmentPointId
4063
+ ]);
4064
+ }
4065
+ segmentToSegmentPointMap.set(segment.nodePortSegmentId, [
4066
+ ...segmentToSegmentPointMap.get(segment.nodePortSegmentId) ?? [],
4067
+ sp.segmentPointId
4068
+ ]);
4069
+ segmentPoints.push(sp);
3947
4070
  }
3948
4071
  }
3949
- const segmentPointMap = /* @__PURE__ */ new Map();
3950
- for (const segmentPoint of segmentPoints) {
3951
- segmentPointMap.set(segmentPoint.segmentPointId, segmentPoint);
3952
- }
3953
- return segmentPointMap;
4072
+ return {
4073
+ segmentPointMap,
4074
+ nodeToSegmentPointMap,
4075
+ segmentToSegmentPointMap
4076
+ };
3954
4077
  };
3955
4078
 
3956
4079
  // lib/solvers/UnravelSolver/UnravelSectionSolver.ts
@@ -3989,7 +4112,11 @@ var UnravelSectionSolver = class extends BaseSolver {
3989
4112
  this.segmentIdToNodeIds = params.segmentIdToNodeIds;
3990
4113
  this.rootNodeId = params.rootNodeId;
3991
4114
  this.colorMap = params.colorMap ?? {};
3992
- this.unravelSection = this.createUnravelSection(params.segmentPointMap);
4115
+ this.unravelSection = this.createUnravelSection({
4116
+ segmentPointMap: params.segmentPointMap,
4117
+ nodeToSegmentPointMap: params.nodeToSegmentPointMap,
4118
+ segmentToSegmentPointMap: params.segmentToSegmentPointMap
4119
+ });
3993
4120
  this.tunedNodeCapacityMap = /* @__PURE__ */ new Map();
3994
4121
  for (const nodeId of this.unravelSection.allNodeIds) {
3995
4122
  this.tunedNodeCapacityMap.set(
@@ -4000,38 +4127,43 @@ var UnravelSectionSolver = class extends BaseSolver {
4000
4127
  this.originalCandidate = this.createInitialCandidate();
4001
4128
  this.candidates = [this.originalCandidate];
4002
4129
  }
4003
- createUnravelSection(segmentPointMap) {
4130
+ createUnravelSection(largeSpMaps) {
4004
4131
  const mutableNodeIds = getNodesNearNode({
4005
4132
  nodeId: this.rootNodeId,
4006
4133
  nodeIdToSegmentIds: this.nodeIdToSegmentIds,
4007
4134
  segmentIdToNodeIds: this.segmentIdToNodeIds,
4008
4135
  hops: this.MUTABLE_HOPS
4009
4136
  });
4010
- const allNodeIds = getNodesNearNode({
4137
+ const allSectionNodeIds = getNodesNearNode({
4011
4138
  nodeId: this.rootNodeId,
4012
4139
  nodeIdToSegmentIds: this.nodeIdToSegmentIds,
4013
4140
  segmentIdToNodeIds: this.segmentIdToNodeIds,
4014
4141
  hops: this.MUTABLE_HOPS + 1
4015
4142
  });
4016
4143
  const immutableNodeIds = Array.from(
4017
- new Set(allNodeIds).difference(new Set(mutableNodeIds))
4144
+ new Set(allSectionNodeIds).difference(new Set(mutableNodeIds))
4018
4145
  );
4019
- if (!segmentPointMap) {
4020
- segmentPointMap = createSegmentPointMap(
4146
+ if (!largeSpMaps?.segmentPointMap) {
4147
+ largeSpMaps = createSegmentPointMap(
4021
4148
  this.dedupedSegments,
4022
4149
  this.segmentIdToNodeIds
4023
4150
  );
4024
4151
  }
4025
- const segmentPoints = Array.from(segmentPointMap.values());
4026
4152
  const segmentPointsInNode = /* @__PURE__ */ new Map();
4027
- for (const segmentPoint of segmentPoints) {
4028
- for (const nodeId of segmentPoint.capacityMeshNodeIds) {
4029
- segmentPointsInNode.set(nodeId, [
4030
- ...segmentPointsInNode.get(nodeId) ?? [],
4031
- segmentPoint.segmentPointId
4032
- ]);
4153
+ for (const nodeId of allSectionNodeIds) {
4154
+ segmentPointsInNode.set(
4155
+ nodeId,
4156
+ largeSpMaps.nodeToSegmentPointMap.get(nodeId)
4157
+ );
4158
+ }
4159
+ const sectionPointMap = /* @__PURE__ */ new Map();
4160
+ for (const nodeId of allSectionNodeIds) {
4161
+ for (const segmentPointId of segmentPointsInNode.get(nodeId)) {
4162
+ const point = largeSpMaps.segmentPointMap.get(segmentPointId);
4163
+ sectionPointMap.set(segmentPointId, point);
4033
4164
  }
4034
4165
  }
4166
+ const segmentPoints = Array.from(sectionPointMap.values());
4035
4167
  const segmentPointsInSegment = /* @__PURE__ */ new Map();
4036
4168
  for (const segmentPoint of segmentPoints) {
4037
4169
  segmentPointsInSegment.set(segmentPoint.segmentId, [
@@ -4041,9 +4173,9 @@ var UnravelSectionSolver = class extends BaseSolver {
4041
4173
  }
4042
4174
  for (const [nodeId, segmentPoints2] of segmentPointsInNode.entries()) {
4043
4175
  for (let i = 0; i < segmentPoints2.length; i++) {
4044
- const A = segmentPointMap.get(segmentPoints2[i]);
4176
+ const A = largeSpMaps.segmentPointMap.get(segmentPoints2[i]);
4045
4177
  for (let j = i + 1; j < segmentPoints2.length; j++) {
4046
- const B = segmentPointMap.get(segmentPoints2[j]);
4178
+ const B = largeSpMaps.segmentPointMap.get(segmentPoints2[j]);
4047
4179
  if (B.segmentPointId === A.segmentPointId) continue;
4048
4180
  if (B.segmentId === A.segmentId) continue;
4049
4181
  if (B.connectionName !== A.connectionName) continue;
@@ -4055,16 +4187,15 @@ var UnravelSectionSolver = class extends BaseSolver {
4055
4187
  }
4056
4188
  }
4057
4189
  const segmentPairsInNode = /* @__PURE__ */ new Map();
4058
- for (const nodeId of allNodeIds) {
4190
+ for (const nodeId of allSectionNodeIds) {
4059
4191
  segmentPairsInNode.set(nodeId, []);
4060
4192
  }
4061
4193
  for (const A of segmentPoints) {
4062
4194
  for (const nodeId of A.capacityMeshNodeIds) {
4063
- const otherSegmentPoints = segmentPointsInNode.get(nodeId).map((spId) => segmentPointMap.get(spId));
4064
4195
  const segmentPairs = segmentPairsInNode.get(nodeId);
4065
4196
  if (!segmentPairs) continue;
4066
4197
  for (const BId of A.directlyConnectedSegmentPointIds) {
4067
- const B = segmentPointMap.get(BId);
4198
+ const B = largeSpMaps.segmentPointMap.get(BId);
4068
4199
  if (B.segmentPointId === A.segmentPointId) continue;
4069
4200
  if (!B.capacityMeshNodeIds.some((nId) => nId === nodeId)) continue;
4070
4201
  if (!segmentPairs.some(
@@ -4087,14 +4218,15 @@ var UnravelSectionSolver = class extends BaseSolver {
4087
4218
  }
4088
4219
  }
4089
4220
  return {
4090
- allNodeIds,
4221
+ allNodeIds: allSectionNodeIds,
4091
4222
  mutableNodeIds,
4092
4223
  immutableNodeIds,
4093
4224
  mutableSegmentIds,
4094
4225
  segmentPairsInNode,
4095
- segmentPointMap,
4226
+ segmentPointMap: sectionPointMap,
4096
4227
  segmentPointsInNode,
4097
- segmentPointsInSegment
4228
+ segmentPointsInSegment,
4229
+ originalPointMap: sectionPointMap
4098
4230
  };
4099
4231
  }
4100
4232
  createInitialCandidate() {
@@ -4277,13 +4409,17 @@ var UnravelSectionSolver = class extends BaseSolver {
4277
4409
  }
4278
4410
  return cost;
4279
4411
  }
4280
- getNeighborByApplyingOperation(currentCandidate, operation) {
4412
+ getUnexploredNeighborByApplyingOperation(currentCandidate, operation) {
4281
4413
  const pointModifications = new Map(currentCandidate.pointModifications);
4282
4414
  applyOperationToPointModifications(
4283
4415
  pointModifications,
4284
4416
  operation,
4285
4417
  (segmentPointId) => this.getPointInCandidate(currentCandidate, segmentPointId)
4286
4418
  );
4419
+ const candidateHash = createPointModificationsHash(pointModifications);
4420
+ if (this.queuedOrExploredCandidatePointModificationHashes.has(candidateHash)) {
4421
+ return null;
4422
+ }
4287
4423
  const issues = getIssuesInSection(
4288
4424
  this.unravelSection,
4289
4425
  this.nodeMap,
@@ -4302,7 +4438,7 @@ var UnravelSectionSolver = class extends BaseSolver {
4302
4438
  h: 0,
4303
4439
  f: g,
4304
4440
  pointModifications,
4305
- candidateHash: createPointModificationsHash(pointModifications),
4441
+ candidateHash,
4306
4442
  // TODO PERFORMANCE allow disabling this
4307
4443
  // candidateFullHash: createFullPointModificationsHash(
4308
4444
  // this.unravelSection.segmentPointMap,
@@ -4320,8 +4456,15 @@ var UnravelSectionSolver = class extends BaseSolver {
4320
4456
  const neighbors = [];
4321
4457
  const operations = this.getNeighborOperationsForCandidate(candidate);
4322
4458
  for (const operation of operations) {
4323
- const neighbor = this.getNeighborByApplyingOperation(candidate, operation);
4459
+ const neighbor = this.getUnexploredNeighborByApplyingOperation(
4460
+ candidate,
4461
+ operation
4462
+ );
4463
+ if (!neighbor) continue;
4324
4464
  neighbors.push(neighbor);
4465
+ this.queuedOrExploredCandidatePointModificationHashes.add(
4466
+ neighbor.candidateHash
4467
+ );
4325
4468
  }
4326
4469
  return neighbors;
4327
4470
  }
@@ -4615,6 +4758,8 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
4615
4758
  dedupedSegments;
4616
4759
  nodeIdToSegmentIds;
4617
4760
  segmentIdToNodeIds;
4761
+ nodeToSegmentPointMap;
4762
+ segmentToSegmentPointMap;
4618
4763
  colorMap;
4619
4764
  tunedNodeCapacityMap;
4620
4765
  MAX_NODE_ATTEMPTS = 2;
@@ -4661,10 +4806,10 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
4661
4806
  for (const [nodeId, node] of this.nodeMap) {
4662
4807
  this.tunedNodeCapacityMap.set(nodeId, getTunedTotalCapacity1(node));
4663
4808
  }
4664
- this.segmentPointMap = createSegmentPointMap(
4665
- this.dedupedSegments,
4666
- this.segmentIdToNodeIds
4667
- );
4809
+ const { segmentPointMap, nodeToSegmentPointMap, segmentToSegmentPointMap } = createSegmentPointMap(this.dedupedSegments, this.segmentIdToNodeIds);
4810
+ this.segmentPointMap = segmentPointMap;
4811
+ this.nodeToSegmentPointMap = nodeToSegmentPointMap;
4812
+ this.segmentToSegmentPointMap = segmentToSegmentPointMap;
4668
4813
  this.nodePfMap = this.computeInitialPfMap();
4669
4814
  }
4670
4815
  computeInitialPfMap() {
@@ -4680,9 +4825,7 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
4680
4825
  numEntryExitLayerChanges,
4681
4826
  numTransitionCrossings
4682
4827
  } = getIntraNodeCrossingsFromSegments(
4683
- this.dedupedSegments.filter(
4684
- (seg) => this.segmentIdToNodeIds.get(seg.nodePortSegmentId).includes(node.capacityMeshNodeId)
4685
- )
4828
+ this.nodeIdToSegmentIds.get(node.capacityMeshNodeId)?.map((segId) => this.dedupedSegmentMap.get(segId)) || []
4686
4829
  );
4687
4830
  const probabilityOfFailure = calculateNodeProbabilityOfFailure(
4688
4831
  node,
@@ -4724,7 +4867,9 @@ var UnravelMultiSectionSolver = class extends BaseSolver {
4724
4867
  colorMap: this.colorMap,
4725
4868
  rootNodeId: highestPfNodeId,
4726
4869
  MUTABLE_HOPS: this.MUTABLE_HOPS,
4727
- segmentPointMap: this.segmentPointMap
4870
+ segmentPointMap: this.segmentPointMap,
4871
+ nodeToSegmentPointMap: this.nodeToSegmentPointMap,
4872
+ segmentToSegmentPointMap: this.segmentToSegmentPointMap
4728
4873
  });
4729
4874
  }
4730
4875
  this.activeSolver.step();
@@ -6066,8 +6211,10 @@ var SegmentTree = class {
6066
6211
  this.buckets = /* @__PURE__ */ new Map();
6067
6212
  for (const segment of segments) {
6068
6213
  const bounds = getSegmentBounds(segment);
6069
- for (let x = bounds.minX; x <= bounds.maxX; x += this.CELL_SIZE) {
6070
- for (let y = bounds.minY; y <= bounds.maxY; y += this.CELL_SIZE) {
6214
+ const bucketMinX = Math.floor(bounds.minX / this.CELL_SIZE) * this.CELL_SIZE;
6215
+ const bucketMinY = Math.floor(bounds.minY / this.CELL_SIZE) * this.CELL_SIZE;
6216
+ for (let x = bucketMinX; x <= bounds.maxX; x += this.CELL_SIZE) {
6217
+ for (let y = bucketMinY; y <= bounds.maxY; y += this.CELL_SIZE) {
6071
6218
  const bucketKey = this.getBucketKey(x, y);
6072
6219
  const bucket = this.buckets.get(bucketKey);
6073
6220
  if (!bucket) {
@@ -6092,12 +6239,14 @@ var SegmentTree = class {
6092
6239
  getSegmentsThatCouldIntersect(A, B) {
6093
6240
  const segments = [];
6094
6241
  const alreadyAddedSegments = /* @__PURE__ */ new Set();
6095
- const minX = Math.min(A.x, B.x) - this.CELL_SIZE;
6096
- const minY = Math.min(A.y, B.y) - this.CELL_SIZE;
6097
- const maxX = Math.max(A.x, B.x) + this.CELL_SIZE;
6098
- const maxY = Math.max(A.y, B.y) + this.CELL_SIZE;
6099
- for (let x = minX; x <= maxX; x += this.CELL_SIZE) {
6100
- for (let y = minY; y <= maxY; y += this.CELL_SIZE) {
6242
+ const minX = Math.min(A.x, B.x);
6243
+ const minY = Math.min(A.y, B.y);
6244
+ const maxX = Math.max(A.x, B.x);
6245
+ const maxY = Math.max(A.y, B.y);
6246
+ const bucketMinX = Math.floor(minX / this.CELL_SIZE) * this.CELL_SIZE;
6247
+ const bucketMinY = Math.floor(minY / this.CELL_SIZE) * this.CELL_SIZE;
6248
+ for (let x = bucketMinX; x <= maxX; x += this.CELL_SIZE) {
6249
+ for (let y = bucketMinY; y <= maxY; y += this.CELL_SIZE) {
6101
6250
  const bucketKey = this.getBucketKey(x, y);
6102
6251
  const bucket = this.buckets.get(bucketKey) || [];
6103
6252
  for (const segment of bucket) {