@tscircuit/capacity-autorouter 0.0.4 → 0.0.5

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 CHANGED
@@ -213,6 +213,7 @@ declare class CapacityPathingSolver extends BaseSolver {
213
213
  reduceCapacityAlongPath(nextConnection: {
214
214
  path?: CapacityMeshNode[];
215
215
  }): void;
216
+ isConnectedToEndGoal(node: CapacityMeshNode, endGoal: CapacityMeshNode): boolean;
216
217
  _step(): void;
217
218
  visualize(): GraphicsObject;
218
219
  }
@@ -771,6 +772,30 @@ declare class CapacitySegmentPointOptimizer extends BaseSolver {
771
772
  visualize(): GraphicsObject;
772
773
  }
773
774
 
775
+ /**
776
+ * Converts a net containing many points to connect into an array of point pair
777
+ * connections.
778
+ *
779
+ * For example, a connection with 3 pointsToConnect could be turned into 2
780
+ * connections of 2 points each.
781
+ *
782
+ * Where we create the minimum number of pairs, we're using a minimum spanning
783
+ * tree (MST).
784
+ *
785
+ * Sometimes it can be used to add additional traces to help make sure we
786
+ * distribute load effectively. In this version we don't do that!
787
+ */
788
+ declare class NetToPointPairsSolver extends BaseSolver {
789
+ ogSrj: SimpleRouteJson;
790
+ colorMap: Record<string, string>;
791
+ unprocessedConnections: Array<SimpleRouteConnection>;
792
+ newConnections: Array<SimpleRouteConnection>;
793
+ constructor(ogSrj: SimpleRouteJson, colorMap?: Record<string, string>);
794
+ _step(): void;
795
+ getNewSimpleRouteJson(): SimpleRouteJson;
796
+ visualize(): GraphicsObject;
797
+ }
798
+
774
799
  interface CapacityMeshSolverOptions {
775
800
  capacityDepth?: number;
776
801
  targetMinCapacity?: number;
@@ -778,7 +803,8 @@ interface CapacityMeshSolverOptions {
778
803
  declare class CapacityMeshSolver extends BaseSolver {
779
804
  srj: SimpleRouteJson;
780
805
  opts: CapacityMeshSolverOptions;
781
- nodeSolver: CapacityMeshNodeSolver;
806
+ netToPointPairsSolver?: NetToPointPairsSolver;
807
+ nodeSolver?: CapacityMeshNodeSolver;
782
808
  nodeTargetMerger?: CapacityNodeTargetMerger;
783
809
  edgeSolver?: CapacityMeshEdgeSolver;
784
810
  pathingSolver?: CapacityPathingSolver;
package/dist/index.js CHANGED
@@ -2896,6 +2896,9 @@ var CapacityPathingSolver = class extends BaseSolver {
2896
2896
  );
2897
2897
  }
2898
2898
  }
2899
+ isConnectedToEndGoal(node, endGoal) {
2900
+ return this.nodeEdgeMap.get(node.capacityMeshNodeId).some((edge) => edge.nodeIds.includes(endGoal.capacityMeshNodeId));
2901
+ }
2899
2902
  _step() {
2900
2903
  const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
2901
2904
  if (!nextConnection) {
@@ -2922,8 +2925,14 @@ var CapacityPathingSolver = class extends BaseSolver {
2922
2925
  this.visitedNodes = null;
2923
2926
  return;
2924
2927
  }
2925
- if (currentCandidate.node.capacityMeshNodeId === end.capacityMeshNodeId) {
2926
- nextConnection.path = this.getBacktrackedPath(currentCandidate);
2928
+ if (this.isConnectedToEndGoal(currentCandidate.node, end)) {
2929
+ nextConnection.path = this.getBacktrackedPath({
2930
+ prevCandidate: currentCandidate,
2931
+ node: end,
2932
+ f: 0,
2933
+ g: 0,
2934
+ h: 0
2935
+ });
2927
2936
  this.reduceCapacityAlongPath(nextConnection);
2928
2937
  this.currentConnectionIndex++;
2929
2938
  this.candidates = null;
@@ -3002,19 +3011,30 @@ ${node.width.toFixed(2)}x${node.height.toFixed(2)}`
3002
3011
  }
3003
3012
  }
3004
3013
  }
3014
+ const nextConnection = this.connectionsWithNodes[this.currentConnectionIndex];
3015
+ if (nextConnection) {
3016
+ const [start, end] = nextConnection.connection.pointsToConnect;
3017
+ graphics.lines.push({
3018
+ points: [
3019
+ { x: start.x, y: start.y },
3020
+ { x: end.x, y: end.y }
3021
+ ],
3022
+ strokeColor: "red",
3023
+ strokeDash: "10 5"
3024
+ });
3025
+ }
3005
3026
  if (this.candidates) {
3006
- const topCandidates = this.candidates.slice(0, 50);
3027
+ const topCandidates = this.candidates.slice(0, 5);
3007
3028
  const connectionName = this.connectionsWithNodes[this.currentConnectionIndex].connection.name;
3008
3029
  topCandidates.forEach((candidate, index) => {
3009
- const opacity = 0.05 * (1 - index / 50);
3030
+ const opacity = 0.5 * (1 - index / 5);
3010
3031
  const backtrackedPath = this.getBacktrackedPath(candidate);
3011
3032
  graphics.lines.push({
3012
3033
  points: backtrackedPath.map(({ center: { x, y } }) => ({ x, y })),
3013
3034
  strokeColor: safeTransparentize(
3014
3035
  this.colorMap[connectionName] ?? "red",
3015
3036
  1 - opacity
3016
- ),
3017
- strokeWidth: 0.5
3037
+ )
3018
3038
  });
3019
3039
  });
3020
3040
  }
@@ -3964,6 +3984,308 @@ ${segment.nodePortSegmentId}`
3964
3984
  }
3965
3985
  };
3966
3986
 
3987
+ // lib/solvers/NetToPointPairsSolver/buildMinimumSpanningTree.ts
3988
+ var KDNode = class {
3989
+ point;
3990
+ left = null;
3991
+ right = null;
3992
+ constructor(point) {
3993
+ this.point = point;
3994
+ }
3995
+ };
3996
+ var KDTree = class {
3997
+ root = null;
3998
+ constructor(points) {
3999
+ if (points.length > 0) {
4000
+ this.root = this.buildTree(points, 0);
4001
+ }
4002
+ }
4003
+ buildTree(points, depth) {
4004
+ const axis = depth % 2 === 0 ? "x" : "y";
4005
+ points.sort((a, b) => a[axis] - b[axis]);
4006
+ const medianIndex = Math.floor(points.length / 2);
4007
+ const node = new KDNode(points[medianIndex]);
4008
+ if (medianIndex > 0) {
4009
+ node.left = this.buildTree(points.slice(0, medianIndex), depth + 1);
4010
+ }
4011
+ if (medianIndex < points.length - 1) {
4012
+ node.right = this.buildTree(points.slice(medianIndex + 1), depth + 1);
4013
+ }
4014
+ return node;
4015
+ }
4016
+ // Find the nearest neighbor to a query point
4017
+ findNearestNeighbor(queryPoint) {
4018
+ if (!this.root) {
4019
+ throw new Error("Tree is empty");
4020
+ }
4021
+ const best = this.root.point;
4022
+ const bestDistance = this.distance(queryPoint, best);
4023
+ this.nearestNeighborSearch(this.root, queryPoint, 0, best, bestDistance);
4024
+ return best;
4025
+ }
4026
+ nearestNeighborSearch(node, queryPoint, depth, best, bestDistance) {
4027
+ if (!node) {
4028
+ return best;
4029
+ }
4030
+ const axis = depth % 2 ? "x" : "y";
4031
+ const currentDistance = this.distance(queryPoint, node.point);
4032
+ if (currentDistance < bestDistance) {
4033
+ best = node.point;
4034
+ bestDistance = currentDistance;
4035
+ }
4036
+ const axisDiff = queryPoint[axis] - node.point[axis];
4037
+ const firstBranch = axisDiff <= 0 ? node.left : node.right;
4038
+ const secondBranch = axisDiff <= 0 ? node.right : node.left;
4039
+ best = this.nearestNeighborSearch(
4040
+ firstBranch,
4041
+ queryPoint,
4042
+ depth + 1,
4043
+ best,
4044
+ bestDistance
4045
+ );
4046
+ bestDistance = this.distance(queryPoint, best);
4047
+ if (Math.abs(axisDiff) < bestDistance) {
4048
+ best = this.nearestNeighborSearch(
4049
+ secondBranch,
4050
+ queryPoint,
4051
+ depth + 1,
4052
+ best,
4053
+ bestDistance
4054
+ );
4055
+ }
4056
+ return best;
4057
+ }
4058
+ // Find k nearest neighbors
4059
+ findKNearestNeighbors(queryPoint, k) {
4060
+ if (!this.root) {
4061
+ return [];
4062
+ }
4063
+ const neighbors = [];
4064
+ this.kNearestNeighborSearch(this.root, queryPoint, 0, neighbors, k);
4065
+ return neighbors.sort((a, b) => a.distance - b.distance).slice(0, k).map((n) => n.point);
4066
+ }
4067
+ kNearestNeighborSearch(node, queryPoint, depth, neighbors, k) {
4068
+ if (!node) {
4069
+ return;
4070
+ }
4071
+ const axis = depth % 2 ? "x" : "y";
4072
+ const currentDistance = this.distance(queryPoint, node.point);
4073
+ neighbors.push({ point: node.point, distance: currentDistance });
4074
+ const axisDiff = queryPoint[axis] - node.point[axis];
4075
+ const firstBranch = axisDiff <= 0 ? node.left : node.right;
4076
+ const secondBranch = axisDiff <= 0 ? node.right : node.left;
4077
+ this.kNearestNeighborSearch(
4078
+ firstBranch,
4079
+ queryPoint,
4080
+ depth + 1,
4081
+ neighbors,
4082
+ k
4083
+ );
4084
+ let kthDistance = Infinity;
4085
+ if (neighbors.length >= k) {
4086
+ neighbors.sort((a, b) => a.distance - b.distance);
4087
+ kthDistance = neighbors[k - 1]?.distance || Infinity;
4088
+ }
4089
+ if (Math.abs(axisDiff) < kthDistance || neighbors.length < k) {
4090
+ this.kNearestNeighborSearch(
4091
+ secondBranch,
4092
+ queryPoint,
4093
+ depth + 1,
4094
+ neighbors,
4095
+ k
4096
+ );
4097
+ }
4098
+ }
4099
+ // Calculate Euclidean distance between two points
4100
+ distance(a, b) {
4101
+ return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
4102
+ }
4103
+ };
4104
+ var DisjointSet = class {
4105
+ parent = /* @__PURE__ */ new Map();
4106
+ rank = /* @__PURE__ */ new Map();
4107
+ constructor(points) {
4108
+ for (const point of points) {
4109
+ const key = this.pointToKey(point);
4110
+ this.parent.set(key, key);
4111
+ this.rank.set(key, 0);
4112
+ }
4113
+ }
4114
+ pointToKey(point) {
4115
+ return `${point.x},${point.y}`;
4116
+ }
4117
+ find(point) {
4118
+ const key = this.pointToKey(point);
4119
+ if (!this.parent.has(key)) {
4120
+ throw new Error(`Point ${key} not found in DisjointSet`);
4121
+ }
4122
+ let root = key;
4123
+ while (root !== this.parent.get(root)) {
4124
+ root = this.parent.get(root);
4125
+ }
4126
+ let current = key;
4127
+ while (current !== root) {
4128
+ const next = this.parent.get(current);
4129
+ this.parent.set(current, root);
4130
+ current = next;
4131
+ }
4132
+ return root;
4133
+ }
4134
+ union(pointA, pointB) {
4135
+ const rootA = this.find(pointA);
4136
+ const rootB = this.find(pointB);
4137
+ if (rootA === rootB) {
4138
+ return false;
4139
+ }
4140
+ const rankA = this.rank.get(rootA) || 0;
4141
+ const rankB = this.rank.get(rootB) || 0;
4142
+ if (rankA < rankB) {
4143
+ this.parent.set(rootA, rootB);
4144
+ } else if (rankA > rankB) {
4145
+ this.parent.set(rootB, rootA);
4146
+ } else {
4147
+ this.parent.set(rootB, rootA);
4148
+ this.rank.set(rootA, rankA + 1);
4149
+ }
4150
+ return true;
4151
+ }
4152
+ };
4153
+ function buildMinimumSpanningTree(points) {
4154
+ if (points.length <= 1) {
4155
+ return [];
4156
+ }
4157
+ const kdTree = new KDTree(points);
4158
+ const edges = [];
4159
+ const k = Math.min(10, points.length - 1);
4160
+ for (const point of points) {
4161
+ const neighbors = kdTree.findKNearestNeighbors(point, k + 1);
4162
+ for (const neighbor of neighbors) {
4163
+ if (point.x === neighbor.x && point.y === neighbor.y) {
4164
+ continue;
4165
+ }
4166
+ const distance3 = Math.sqrt(
4167
+ (point.x - neighbor.x) ** 2 + (point.y - neighbor.y) ** 2
4168
+ );
4169
+ edges.push({
4170
+ from: point,
4171
+ to: neighbor,
4172
+ weight: distance3
4173
+ });
4174
+ }
4175
+ }
4176
+ edges.sort((a, b) => a.weight - b.weight);
4177
+ const disjointSet = new DisjointSet(points);
4178
+ const mstEdges = [];
4179
+ for (const edge of edges) {
4180
+ if (disjointSet.union(edge.from, edge.to)) {
4181
+ mstEdges.push(edge);
4182
+ if (mstEdges.length === points.length - 1) {
4183
+ break;
4184
+ }
4185
+ }
4186
+ }
4187
+ return mstEdges;
4188
+ }
4189
+
4190
+ // lib/solvers/NetToPointPairsSolver/NetToPointPairsSolver.ts
4191
+ var NetToPointPairsSolver = class extends BaseSolver {
4192
+ constructor(ogSrj, colorMap = {}) {
4193
+ super();
4194
+ this.ogSrj = ogSrj;
4195
+ this.colorMap = colorMap;
4196
+ this.unprocessedConnections = ogSrj.connections;
4197
+ this.newConnections = [];
4198
+ }
4199
+ unprocessedConnections;
4200
+ newConnections;
4201
+ _step() {
4202
+ if (this.unprocessedConnections.length === 0) {
4203
+ this.solved = true;
4204
+ return;
4205
+ }
4206
+ const connection = this.unprocessedConnections.pop();
4207
+ if (connection.pointsToConnect.length === 2) {
4208
+ this.newConnections.push(connection);
4209
+ return;
4210
+ }
4211
+ const edges = buildMinimumSpanningTree(connection.pointsToConnect);
4212
+ for (const edge of edges) {
4213
+ this.newConnections.push({
4214
+ pointsToConnect: [edge.from, edge.to],
4215
+ name: connection.name
4216
+ });
4217
+ }
4218
+ }
4219
+ getNewSimpleRouteJson() {
4220
+ return {
4221
+ ...this.ogSrj,
4222
+ connections: this.newConnections
4223
+ };
4224
+ }
4225
+ visualize() {
4226
+ const graphics = {
4227
+ lines: [],
4228
+ points: [],
4229
+ rects: [],
4230
+ circles: [],
4231
+ coordinateSystem: "cartesian",
4232
+ title: "Net To Point Pairs Visualization"
4233
+ };
4234
+ this.unprocessedConnections.forEach((connection) => {
4235
+ connection.pointsToConnect.forEach((point) => {
4236
+ graphics.points.push({
4237
+ x: point.x,
4238
+ y: point.y,
4239
+ color: "red",
4240
+ label: connection.name
4241
+ });
4242
+ });
4243
+ const fullyConnectedEdgeCount = connection.pointsToConnect.length ** 2;
4244
+ const random = seededRandom(0);
4245
+ const alreadyPlacedEdges = /* @__PURE__ */ new Set();
4246
+ for (let i = 0; i < Math.max(
4247
+ fullyConnectedEdgeCount,
4248
+ connection.pointsToConnect.length * 2
4249
+ ); i++) {
4250
+ const a = Math.floor(random() * connection.pointsToConnect.length);
4251
+ const b = Math.floor(random() * connection.pointsToConnect.length);
4252
+ if (alreadyPlacedEdges.has(`${a}-${b}`)) continue;
4253
+ alreadyPlacedEdges.add(`${a}-${b}`);
4254
+ graphics.lines.push({
4255
+ points: [
4256
+ connection.pointsToConnect[a],
4257
+ connection.pointsToConnect[b]
4258
+ ],
4259
+ strokeColor: "rgba(255,0,0,0.25)"
4260
+ });
4261
+ }
4262
+ });
4263
+ this.newConnections.forEach((connection) => {
4264
+ const color = this.colorMap?.[connection.name] || "blue";
4265
+ connection.pointsToConnect.forEach((point) => {
4266
+ graphics.points.push({
4267
+ x: point.x,
4268
+ y: point.y,
4269
+ color,
4270
+ label: connection.name
4271
+ });
4272
+ });
4273
+ for (let i = 0; i < connection.pointsToConnect.length - 1; i++) {
4274
+ for (let j = i + 1; j < connection.pointsToConnect.length; j++) {
4275
+ graphics.lines.push({
4276
+ points: [
4277
+ connection.pointsToConnect[i],
4278
+ connection.pointsToConnect[j]
4279
+ ],
4280
+ strokeColor: color
4281
+ });
4282
+ }
4283
+ }
4284
+ });
4285
+ return graphics;
4286
+ }
4287
+ };
4288
+
3967
4289
  // lib/solvers/CapacityMeshSolver/CapacityMeshSolver.ts
3968
4290
  var CapacityMeshSolver = class extends BaseSolver {
3969
4291
  constructor(srj, opts = {}) {
@@ -3981,10 +4303,10 @@ var CapacityMeshSolver = class extends BaseSolver {
3981
4303
  targetMinCapacity
3982
4304
  );
3983
4305
  }
3984
- this.nodeSolver = new CapacityMeshNodeSolver(srj, this.opts);
3985
4306
  this.connMap = getConnectivityMapFromSimpleRouteJson(srj);
3986
4307
  this.colorMap = getColorMap(srj, this.connMap);
3987
4308
  }
4309
+ netToPointPairsSolver;
3988
4310
  nodeSolver;
3989
4311
  nodeTargetMerger;
3990
4312
  edgeSolver;
@@ -4008,7 +4330,19 @@ var CapacityMeshSolver = class extends BaseSolver {
4008
4330
  }
4009
4331
  return;
4010
4332
  }
4011
- if (!this.nodeSolver.solved) {
4333
+ if (!this.netToPointPairsSolver) {
4334
+ this.netToPointPairsSolver = new NetToPointPairsSolver(
4335
+ this.srj,
4336
+ this.colorMap
4337
+ );
4338
+ this.activeSolver = this.netToPointPairsSolver;
4339
+ return;
4340
+ }
4341
+ if (!this.nodeSolver) {
4342
+ this.nodeSolver = new CapacityMeshNodeSolver(
4343
+ this.netToPointPairsSolver.getNewSimpleRouteJson(),
4344
+ this.opts
4345
+ );
4012
4346
  this.activeSolver = this.nodeSolver;
4013
4347
  return;
4014
4348
  }
@@ -4029,7 +4363,7 @@ var CapacityMeshSolver = class extends BaseSolver {
4029
4363
  }
4030
4364
  if (!this.pathingSolver) {
4031
4365
  this.pathingSolver = new CapacityPathingSolver4_FlexibleNegativeCapacity({
4032
- simpleRouteJson: this.srj,
4366
+ simpleRouteJson: this.netToPointPairsSolver.getNewSimpleRouteJson(),
4033
4367
  nodes,
4034
4368
  edges: this.edgeSolver.edges,
4035
4369
  colorMap: this.colorMap,
@@ -4086,7 +4420,8 @@ var CapacityMeshSolver = class extends BaseSolver {
4086
4420
  }
4087
4421
  visualize() {
4088
4422
  if (!this.solved && this.activeSolver) return this.activeSolver.visualize();
4089
- const nodeViz = this.nodeSolver.visualize();
4423
+ const netToPPSolver = this.netToPointPairsSolver?.visualize();
4424
+ const nodeViz = this.nodeSolver?.visualize();
4090
4425
  const edgeViz = this.edgeSolver?.visualize();
4091
4426
  const pathingViz = this.pathingSolver?.visualize();
4092
4427
  const edgeToPortSegmentViz = this.edgeToPortSegmentSolver?.visualize();
@@ -4094,11 +4429,17 @@ var CapacityMeshSolver = class extends BaseSolver {
4094
4429
  const segmentOptimizationViz = this.segmentToPointOptimizer?.visualize();
4095
4430
  const highDensityViz = this.highDensityRouteSolver?.visualize();
4096
4431
  const problemViz = {
4097
- points: [...nodeViz.points],
4098
- rects: [...nodeViz.rects?.filter((r) => r.label?.includes("obstacle"))]
4432
+ points: [...this.srj.connections.flatMap((c) => c.pointsToConnect)],
4433
+ rects: [
4434
+ ...(this.srj.obstacles ?? []).map((o) => ({
4435
+ ...o,
4436
+ fill: "rgba(255,0,0,0.25)"
4437
+ }))
4438
+ ]
4099
4439
  };
4100
4440
  const visualizations = [
4101
4441
  problemViz,
4442
+ netToPPSolver,
4102
4443
  nodeViz,
4103
4444
  edgeViz,
4104
4445
  pathingViz,