@tscircuit/hypergraph 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,2318 @@
1
+ // lib/JumperGraphSolver/geometry/getBoundsCenter.ts
2
+ var computeBoundsCenter = (bounds) => {
3
+ return {
4
+ x: (bounds.minX + bounds.maxX) / 2,
5
+ y: (bounds.minY + bounds.maxY) / 2
6
+ };
7
+ };
8
+
9
+ // lib/JumperGraphSolver/jumper-graph-generator/generateSingleJumperX4Regions.ts
10
+ var dims1206x4 = {
11
+ padWidth: 0.8,
12
+ // X direction (horizontal)
13
+ padHeight: 0.5,
14
+ // Y direction (vertical)
15
+ leftPadCenterX: -1.35,
16
+ // X position of left pads (P1, P2, P3, P4)
17
+ rightPadCenterX: 1.35,
18
+ // X position of right pads (P5, P6, P7, P8)
19
+ row1CenterY: 1.905,
20
+ // Y position of row 1 (P1, P8)
21
+ row2CenterY: 0.635,
22
+ // Y position of row 2 (P2, P7)
23
+ row3CenterY: -0.635,
24
+ // Y position of row 3 (P3, P6)
25
+ row4CenterY: -1.905
26
+ // Y position of row 4 (P4, P5)
27
+ };
28
+
29
+ // lib/JumperGraphSolver/jumper-graph-generator/calculateGraphBounds.ts
30
+ var calculateGraphBounds = (regions) => {
31
+ let minX = Number.POSITIVE_INFINITY;
32
+ let maxX = Number.NEGATIVE_INFINITY;
33
+ let minY = Number.POSITIVE_INFINITY;
34
+ let maxY = Number.NEGATIVE_INFINITY;
35
+ for (const region of regions) {
36
+ const { bounds } = region.d;
37
+ minX = Math.min(minX, bounds.minX);
38
+ maxX = Math.max(maxX, bounds.maxX);
39
+ minY = Math.min(minY, bounds.minY);
40
+ maxY = Math.max(maxY, bounds.maxY);
41
+ }
42
+ return { minX, maxX, minY, maxY };
43
+ };
44
+
45
+ // node_modules/transformation-matrix/src/applyToPoint.js
46
+ function applyToPoint(matrix, point) {
47
+ return Array.isArray(point) ? [
48
+ matrix.a * point[0] + matrix.c * point[1] + matrix.e,
49
+ matrix.b * point[0] + matrix.d * point[1] + matrix.f
50
+ ] : {
51
+ x: matrix.a * point.x + matrix.c * point.y + matrix.e,
52
+ y: matrix.b * point.x + matrix.d * point.y + matrix.f
53
+ };
54
+ }
55
+
56
+ // node_modules/transformation-matrix/src/utils.js
57
+ function isUndefined(val) {
58
+ return typeof val === "undefined";
59
+ }
60
+
61
+ // node_modules/transformation-matrix/src/translate.js
62
+ function translate(tx, ty = 0) {
63
+ return {
64
+ a: 1,
65
+ c: 0,
66
+ e: tx,
67
+ b: 0,
68
+ d: 1,
69
+ f: ty
70
+ };
71
+ }
72
+
73
+ // node_modules/transformation-matrix/src/transform.js
74
+ function transform(...matrices) {
75
+ matrices = Array.isArray(matrices[0]) ? matrices[0] : matrices;
76
+ const multiply = (m1, m2) => {
77
+ return {
78
+ a: m1.a * m2.a + m1.c * m2.b,
79
+ c: m1.a * m2.c + m1.c * m2.d,
80
+ e: m1.a * m2.e + m1.c * m2.f + m1.e,
81
+ b: m1.b * m2.a + m1.d * m2.b,
82
+ d: m1.b * m2.c + m1.d * m2.d,
83
+ f: m1.b * m2.e + m1.d * m2.f + m1.f
84
+ };
85
+ };
86
+ switch (matrices.length) {
87
+ case 0:
88
+ throw new Error("no matrices provided");
89
+ case 1:
90
+ return matrices[0];
91
+ case 2:
92
+ return multiply(matrices[0], matrices[1]);
93
+ default: {
94
+ const [m1, m2, ...rest] = matrices;
95
+ const m = multiply(m1, m2);
96
+ return transform(m, ...rest);
97
+ }
98
+ }
99
+ }
100
+ function compose(...matrices) {
101
+ return transform(...matrices);
102
+ }
103
+
104
+ // node_modules/transformation-matrix/src/rotate.js
105
+ var { cos, sin, PI } = Math;
106
+ function rotate(angle, cx, cy) {
107
+ const cosAngle = cos(angle);
108
+ const sinAngle = sin(angle);
109
+ const rotationMatrix = {
110
+ a: cosAngle,
111
+ c: -sinAngle,
112
+ e: 0,
113
+ b: sinAngle,
114
+ d: cosAngle,
115
+ f: 0
116
+ };
117
+ if (isUndefined(cx) || isUndefined(cy)) {
118
+ return rotationMatrix;
119
+ }
120
+ return transform([
121
+ translate(cx, cy),
122
+ rotationMatrix,
123
+ translate(-cx, -cy)
124
+ ]);
125
+ }
126
+
127
+ // node_modules/transformation-matrix/src/skew.js
128
+ var { tan } = Math;
129
+
130
+ // lib/JumperGraphSolver/geometry/applyTransformToGraph.ts
131
+ var applyTransformToGraph = (graph, matrix) => {
132
+ const transformedRegions = graph.regions.map((region) => {
133
+ const { bounds, center, ...rest } = region.d;
134
+ const corners = [
135
+ { x: bounds.minX, y: bounds.minY },
136
+ { x: bounds.maxX, y: bounds.minY },
137
+ { x: bounds.minX, y: bounds.maxY },
138
+ { x: bounds.maxX, y: bounds.maxY }
139
+ ].map((corner) => applyToPoint(matrix, corner));
140
+ const newBounds = {
141
+ minX: Math.min(...corners.map((c) => c.x)),
142
+ maxX: Math.max(...corners.map((c) => c.x)),
143
+ minY: Math.min(...corners.map((c) => c.y)),
144
+ maxY: Math.max(...corners.map((c) => c.y))
145
+ };
146
+ const newCenter = applyToPoint(matrix, center);
147
+ return {
148
+ ...region,
149
+ // Clear ports array - will be rebuilt with new port objects
150
+ ports: [],
151
+ d: {
152
+ ...rest,
153
+ bounds: newBounds,
154
+ center: newCenter
155
+ }
156
+ };
157
+ });
158
+ const regionMap = /* @__PURE__ */ new Map();
159
+ for (let i = 0; i < graph.regions.length; i++) {
160
+ regionMap.set(graph.regions[i], transformedRegions[i]);
161
+ }
162
+ const transformedPorts = graph.ports.map((port) => {
163
+ const newPosition = applyToPoint(matrix, port.d);
164
+ const newRegion1 = regionMap.get(port.region1);
165
+ const newRegion2 = regionMap.get(port.region2);
166
+ const newPort = {
167
+ ...port,
168
+ region1: newRegion1,
169
+ region2: newRegion2,
170
+ d: newPosition
171
+ };
172
+ newRegion1.ports.push(newPort);
173
+ newRegion2.ports.push(newPort);
174
+ return newPort;
175
+ });
176
+ return {
177
+ regions: transformedRegions,
178
+ ports: transformedPorts
179
+ };
180
+ };
181
+ var rotateGraph90Degrees = (graph) => {
182
+ const bounds = calculateGraphBounds(graph.regions);
183
+ const center = computeBoundsCenter(bounds);
184
+ const matrix = compose(
185
+ translate(center.x, center.y),
186
+ rotate(-Math.PI / 2),
187
+ // -90 degrees (clockwise)
188
+ translate(-center.x, -center.y)
189
+ );
190
+ return applyTransformToGraph(graph, matrix);
191
+ };
192
+
193
+ // lib/JumperGraphSolver/jumper-graph-generator/generateJumperX4Grid.ts
194
+ var generateJumperX4Grid = ({
195
+ cols,
196
+ rows,
197
+ marginX,
198
+ marginY,
199
+ innerColChannelPointCount = 1,
200
+ innerRowChannelPointCount = 1,
201
+ regionsBetweenPads = false,
202
+ outerPaddingX = 0.5,
203
+ outerPaddingY = 0.5,
204
+ outerChannelXPointCount,
205
+ outerChannelYPointCount,
206
+ orientation: orientation2 = "vertical",
207
+ center
208
+ }) => {
209
+ const effectiveOuterChannelXPoints = outerChannelXPointCount ?? Math.max(1, Math.floor(outerPaddingX / 0.4));
210
+ const effectiveOuterChannelYPoints = outerChannelYPointCount ?? Math.max(1, Math.floor(outerPaddingY / 0.4));
211
+ const regions = [];
212
+ const ports = [];
213
+ const {
214
+ padWidth,
215
+ padHeight,
216
+ leftPadCenterX,
217
+ rightPadCenterX,
218
+ row1CenterY,
219
+ row2CenterY,
220
+ row3CenterY,
221
+ row4CenterY
222
+ } = dims1206x4;
223
+ const padHalfWidth = padWidth / 2;
224
+ const padHalfHeight = padHeight / 2;
225
+ const cellWidth = rightPadCenterX - leftPadCenterX + padWidth;
226
+ const horizontalSpacing = cellWidth + marginX;
227
+ const cellHeight = row1CenterY - row4CenterY + padHeight;
228
+ const verticalSpacing = cellHeight + marginY;
229
+ const cells = [];
230
+ const createRegion = (id, bounds, isPad, isThroughJumper) => ({
231
+ regionId: id,
232
+ ports: [],
233
+ d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper }
234
+ });
235
+ const createPort = (id, region1, region2) => {
236
+ const b1 = region1.d.bounds;
237
+ const b2 = region2.d.bounds;
238
+ let x;
239
+ let y;
240
+ if (Math.abs(b1.maxX - b2.minX) < 1e-3) {
241
+ x = b1.maxX;
242
+ y = (Math.max(b1.minY, b2.minY) + Math.min(b1.maxY, b2.maxY)) / 2;
243
+ } else if (Math.abs(b1.minX - b2.maxX) < 1e-3) {
244
+ x = b1.minX;
245
+ y = (Math.max(b1.minY, b2.minY) + Math.min(b1.maxY, b2.maxY)) / 2;
246
+ } else if (Math.abs(b1.maxY - b2.minY) < 1e-3) {
247
+ x = (Math.max(b1.minX, b2.minX) + Math.min(b1.maxX, b2.maxX)) / 2;
248
+ y = b1.maxY;
249
+ } else {
250
+ x = (Math.max(b1.minX, b2.minX) + Math.min(b1.maxX, b2.maxX)) / 2;
251
+ y = b1.minY;
252
+ }
253
+ const port = {
254
+ portId: id,
255
+ region1,
256
+ region2,
257
+ d: { x, y }
258
+ };
259
+ region1.ports.push(port);
260
+ region2.ports.push(port);
261
+ return port;
262
+ };
263
+ const createMultiplePorts = (idPrefix, region1, region2, count) => {
264
+ if (count <= 0) return [];
265
+ if (count === 1) {
266
+ return [createPort(idPrefix, region1, region2)];
267
+ }
268
+ const b1 = region1.d.bounds;
269
+ const b2 = region2.d.bounds;
270
+ const result = [];
271
+ let isVerticalBoundary;
272
+ let boundaryCoord;
273
+ let sharedMin;
274
+ let sharedMax;
275
+ if (Math.abs(b1.maxX - b2.minX) < 1e-3) {
276
+ isVerticalBoundary = true;
277
+ boundaryCoord = b1.maxX;
278
+ sharedMin = Math.max(b1.minY, b2.minY);
279
+ sharedMax = Math.min(b1.maxY, b2.maxY);
280
+ } else if (Math.abs(b1.minX - b2.maxX) < 1e-3) {
281
+ isVerticalBoundary = true;
282
+ boundaryCoord = b1.minX;
283
+ sharedMin = Math.max(b1.minY, b2.minY);
284
+ sharedMax = Math.min(b1.maxY, b2.maxY);
285
+ } else if (Math.abs(b1.maxY - b2.minY) < 1e-3) {
286
+ isVerticalBoundary = false;
287
+ boundaryCoord = b1.maxY;
288
+ sharedMin = Math.max(b1.minX, b2.minX);
289
+ sharedMax = Math.min(b1.maxX, b2.maxX);
290
+ } else {
291
+ isVerticalBoundary = false;
292
+ boundaryCoord = b1.minY;
293
+ sharedMin = Math.max(b1.minX, b2.minX);
294
+ sharedMax = Math.min(b1.maxX, b2.maxX);
295
+ }
296
+ for (let i = 0; i < count; i++) {
297
+ const t = (i + 0.5) / count;
298
+ const coord = sharedMin + t * (sharedMax - sharedMin);
299
+ const x = isVerticalBoundary ? boundaryCoord : coord;
300
+ const y = isVerticalBoundary ? coord : boundaryCoord;
301
+ const port = {
302
+ portId: `${idPrefix}:${i}`,
303
+ region1,
304
+ region2,
305
+ d: { x, y }
306
+ };
307
+ region1.ports.push(port);
308
+ region2.ports.push(port);
309
+ result.push(port);
310
+ }
311
+ return result;
312
+ };
313
+ for (let row = 0; row < rows; row++) {
314
+ cells[row] = [];
315
+ for (let col = 0; col < cols; col++) {
316
+ const idPrefix = `cell_${row}_${col}`;
317
+ const centerX = col * horizontalSpacing;
318
+ const centerY = -row * verticalSpacing;
319
+ const p1CenterX = centerX + leftPadCenterX;
320
+ const p1CenterY = centerY + row1CenterY;
321
+ const p2CenterX = centerX + leftPadCenterX;
322
+ const p2CenterY = centerY + row2CenterY;
323
+ const p3CenterX = centerX + leftPadCenterX;
324
+ const p3CenterY = centerY + row3CenterY;
325
+ const p4CenterX = centerX + leftPadCenterX;
326
+ const p4CenterY = centerY + row4CenterY;
327
+ const p5CenterX = centerX + rightPadCenterX;
328
+ const p5CenterY = centerY + row4CenterY;
329
+ const p6CenterX = centerX + rightPadCenterX;
330
+ const p6CenterY = centerY + row3CenterY;
331
+ const p7CenterX = centerX + rightPadCenterX;
332
+ const p7CenterY = centerY + row2CenterY;
333
+ const p8CenterX = centerX + rightPadCenterX;
334
+ const p8CenterY = centerY + row1CenterY;
335
+ const createPadBounds = (padCenterX, padCenterY) => ({
336
+ minX: padCenterX - padHalfWidth,
337
+ maxX: padCenterX + padHalfWidth,
338
+ minY: padCenterY - padHalfHeight,
339
+ maxY: padCenterY + padHalfHeight
340
+ });
341
+ const pad1Bounds = createPadBounds(p1CenterX, p1CenterY);
342
+ const pad2Bounds = createPadBounds(p2CenterX, p2CenterY);
343
+ const pad3Bounds = createPadBounds(p3CenterX, p3CenterY);
344
+ const pad4Bounds = createPadBounds(p4CenterX, p4CenterY);
345
+ const pad5Bounds = createPadBounds(p5CenterX, p5CenterY);
346
+ const pad6Bounds = createPadBounds(p6CenterX, p6CenterY);
347
+ const pad7Bounds = createPadBounds(p7CenterX, p7CenterY);
348
+ const pad8Bounds = createPadBounds(p8CenterX, p8CenterY);
349
+ const underjumperBounds = {
350
+ minX: pad1Bounds.maxX,
351
+ maxX: pad8Bounds.minX,
352
+ minY: pad4Bounds.minY,
353
+ maxY: pad1Bounds.maxY
354
+ };
355
+ const throughjumperHeight = 0.3;
356
+ const throughjumper1Bounds = {
357
+ minX: p1CenterX,
358
+ maxX: p8CenterX,
359
+ minY: p1CenterY - throughjumperHeight / 2,
360
+ maxY: p1CenterY + throughjumperHeight / 2
361
+ };
362
+ const throughjumper2Bounds = {
363
+ minX: p2CenterX,
364
+ maxX: p7CenterX,
365
+ minY: p2CenterY - throughjumperHeight / 2,
366
+ maxY: p2CenterY + throughjumperHeight / 2
367
+ };
368
+ const throughjumper3Bounds = {
369
+ minX: p3CenterX,
370
+ maxX: p6CenterX,
371
+ minY: p3CenterY - throughjumperHeight / 2,
372
+ maxY: p3CenterY + throughjumperHeight / 2
373
+ };
374
+ const throughjumper4Bounds = {
375
+ minX: p4CenterX,
376
+ maxX: p5CenterX,
377
+ minY: p4CenterY - throughjumperHeight / 2,
378
+ maxY: p4CenterY + throughjumperHeight / 2
379
+ };
380
+ const mainMinX = pad1Bounds.minX;
381
+ const mainMaxX = pad8Bounds.maxX;
382
+ const mainMinY = pad4Bounds.minY;
383
+ const mainMaxY = pad1Bounds.maxY;
384
+ const pad1 = createRegion(`${idPrefix}:pad1`, pad1Bounds, true);
385
+ const pad2 = createRegion(`${idPrefix}:pad2`, pad2Bounds, true);
386
+ const pad3 = createRegion(`${idPrefix}:pad3`, pad3Bounds, true);
387
+ const pad4 = createRegion(`${idPrefix}:pad4`, pad4Bounds, true);
388
+ const pad5 = createRegion(`${idPrefix}:pad5`, pad5Bounds, true);
389
+ const pad6 = createRegion(`${idPrefix}:pad6`, pad6Bounds, true);
390
+ const pad7 = createRegion(`${idPrefix}:pad7`, pad7Bounds, true);
391
+ const pad8 = createRegion(`${idPrefix}:pad8`, pad8Bounds, true);
392
+ const underjumper = createRegion(
393
+ `${idPrefix}:underjumper`,
394
+ underjumperBounds,
395
+ false
396
+ );
397
+ const throughjumper1 = createRegion(
398
+ `${idPrefix}:throughjumper1`,
399
+ throughjumper1Bounds,
400
+ false,
401
+ true
402
+ );
403
+ const throughjumper2 = createRegion(
404
+ `${idPrefix}:throughjumper2`,
405
+ throughjumper2Bounds,
406
+ false,
407
+ true
408
+ );
409
+ const throughjumper3 = createRegion(
410
+ `${idPrefix}:throughjumper3`,
411
+ throughjumper3Bounds,
412
+ false,
413
+ true
414
+ );
415
+ const throughjumper4 = createRegion(
416
+ `${idPrefix}:throughjumper4`,
417
+ throughjumper4Bounds,
418
+ false,
419
+ true
420
+ );
421
+ regions.push(
422
+ pad1,
423
+ pad2,
424
+ pad3,
425
+ pad4,
426
+ pad5,
427
+ pad6,
428
+ pad7,
429
+ pad8,
430
+ underjumper,
431
+ throughjumper1,
432
+ throughjumper2,
433
+ throughjumper3,
434
+ throughjumper4
435
+ );
436
+ let leftBP12 = null;
437
+ let leftBP23 = null;
438
+ let leftBP34 = null;
439
+ let rightBP87 = null;
440
+ let rightBP76 = null;
441
+ let rightBP65 = null;
442
+ if (regionsBetweenPads) {
443
+ leftBP12 = createRegion(
444
+ `${idPrefix}:L-BP12`,
445
+ {
446
+ minX: pad1Bounds.minX,
447
+ maxX: pad1Bounds.maxX,
448
+ minY: pad2Bounds.maxY,
449
+ maxY: pad1Bounds.minY
450
+ },
451
+ false
452
+ );
453
+ leftBP23 = createRegion(
454
+ `${idPrefix}:L-BP23`,
455
+ {
456
+ minX: pad2Bounds.minX,
457
+ maxX: pad2Bounds.maxX,
458
+ minY: pad3Bounds.maxY,
459
+ maxY: pad2Bounds.minY
460
+ },
461
+ false
462
+ );
463
+ leftBP34 = createRegion(
464
+ `${idPrefix}:L-BP34`,
465
+ {
466
+ minX: pad3Bounds.minX,
467
+ maxX: pad3Bounds.maxX,
468
+ minY: pad4Bounds.maxY,
469
+ maxY: pad3Bounds.minY
470
+ },
471
+ false
472
+ );
473
+ rightBP87 = createRegion(
474
+ `${idPrefix}:R-BP87`,
475
+ {
476
+ minX: pad8Bounds.minX,
477
+ maxX: pad8Bounds.maxX,
478
+ minY: pad7Bounds.maxY,
479
+ maxY: pad8Bounds.minY
480
+ },
481
+ false
482
+ );
483
+ rightBP76 = createRegion(
484
+ `${idPrefix}:R-BP76`,
485
+ {
486
+ minX: pad7Bounds.minX,
487
+ maxX: pad7Bounds.maxX,
488
+ minY: pad6Bounds.maxY,
489
+ maxY: pad7Bounds.minY
490
+ },
491
+ false
492
+ );
493
+ rightBP65 = createRegion(
494
+ `${idPrefix}:R-BP65`,
495
+ {
496
+ minX: pad6Bounds.minX,
497
+ maxX: pad6Bounds.maxX,
498
+ minY: pad5Bounds.maxY,
499
+ maxY: pad6Bounds.minY
500
+ },
501
+ false
502
+ );
503
+ regions.push(
504
+ leftBP12,
505
+ leftBP23,
506
+ leftBP34,
507
+ rightBP87,
508
+ rightBP76,
509
+ rightBP65
510
+ );
511
+ }
512
+ const isFirstRow = row === 0;
513
+ const isFirstCol = col === 0;
514
+ const isLastRow = row === rows - 1;
515
+ const isLastCol = col === cols - 1;
516
+ let frameRightEdge;
517
+ if (isLastCol) {
518
+ frameRightEdge = mainMaxX + outerPaddingX;
519
+ } else {
520
+ const nextCenterX = (col + 1) * horizontalSpacing;
521
+ const nextP1CenterX = nextCenterX + leftPadCenterX;
522
+ frameRightEdge = nextP1CenterX - padHalfWidth;
523
+ }
524
+ let top = null;
525
+ if (isFirstRow) {
526
+ top = createRegion(
527
+ `${idPrefix}:T`,
528
+ {
529
+ minX: isFirstCol ? mainMinX - outerPaddingX : mainMinX,
530
+ maxX: frameRightEdge,
531
+ minY: mainMaxY,
532
+ maxY: mainMaxY + outerPaddingY
533
+ },
534
+ false
535
+ );
536
+ regions.push(top);
537
+ }
538
+ let bottom = null;
539
+ const bottomHeight = isLastRow ? outerPaddingY : marginY;
540
+ bottom = createRegion(
541
+ `${idPrefix}:B`,
542
+ {
543
+ minX: isFirstCol ? mainMinX - outerPaddingX : mainMinX,
544
+ maxX: frameRightEdge,
545
+ minY: mainMinY - bottomHeight,
546
+ maxY: mainMinY
547
+ },
548
+ false
549
+ );
550
+ regions.push(bottom);
551
+ let left = null;
552
+ if (isFirstCol) {
553
+ left = createRegion(
554
+ `${idPrefix}:L`,
555
+ {
556
+ minX: mainMinX - outerPaddingX,
557
+ maxX: mainMinX,
558
+ minY: mainMinY,
559
+ maxY: mainMaxY
560
+ },
561
+ false
562
+ );
563
+ regions.push(left);
564
+ }
565
+ const right = createRegion(
566
+ `${idPrefix}:R`,
567
+ {
568
+ minX: mainMaxX,
569
+ maxX: frameRightEdge,
570
+ minY: mainMinY,
571
+ maxY: mainMaxY
572
+ },
573
+ false
574
+ );
575
+ regions.push(right);
576
+ cells[row][col] = {
577
+ pad1,
578
+ pad2,
579
+ pad3,
580
+ pad4,
581
+ pad5,
582
+ pad6,
583
+ pad7,
584
+ pad8,
585
+ underjumper,
586
+ throughjumper1,
587
+ throughjumper2,
588
+ throughjumper3,
589
+ throughjumper4,
590
+ top,
591
+ bottom,
592
+ left,
593
+ right,
594
+ leftBP12,
595
+ leftBP23,
596
+ leftBP34,
597
+ rightBP87,
598
+ rightBP76,
599
+ rightBP65
600
+ };
601
+ if (top) {
602
+ if (left) {
603
+ ports.push(
604
+ ...createMultiplePorts(
605
+ `${idPrefix}:T-L`,
606
+ top,
607
+ left,
608
+ effectiveOuterChannelXPoints
609
+ )
610
+ );
611
+ }
612
+ ports.push(
613
+ ...createMultiplePorts(
614
+ `${idPrefix}:T-R`,
615
+ top,
616
+ right,
617
+ isLastCol ? effectiveOuterChannelXPoints : innerColChannelPointCount
618
+ )
619
+ );
620
+ ports.push(createPort(`${idPrefix}:T-P1`, top, pad1));
621
+ ports.push(createPort(`${idPrefix}:T-P8`, top, pad8));
622
+ if (regionsBetweenPads) {
623
+ const ujBounds = underjumper.d.bounds;
624
+ const ujWidth = ujBounds.maxX - ujBounds.minX;
625
+ const portSpacing = ujWidth / 5;
626
+ for (let i = 1; i <= 4; i++) {
627
+ const portX = ujBounds.minX + portSpacing * i;
628
+ const topUJPort = {
629
+ portId: `${idPrefix}:T-UJ${i}`,
630
+ region1: top,
631
+ region2: underjumper,
632
+ d: { x: portX, y: ujBounds.maxY }
633
+ };
634
+ top.ports.push(topUJPort);
635
+ underjumper.ports.push(topUJPort);
636
+ ports.push(topUJPort);
637
+ }
638
+ } else {
639
+ ports.push(createPort(`${idPrefix}:T-UJ`, top, underjumper));
640
+ }
641
+ }
642
+ if (bottom) {
643
+ if (left) {
644
+ ports.push(
645
+ ...createMultiplePorts(
646
+ `${idPrefix}:B-L`,
647
+ bottom,
648
+ left,
649
+ effectiveOuterChannelXPoints
650
+ )
651
+ );
652
+ }
653
+ ports.push(
654
+ ...createMultiplePorts(
655
+ `${idPrefix}:B-R`,
656
+ bottom,
657
+ right,
658
+ isLastCol ? effectiveOuterChannelXPoints : innerColChannelPointCount
659
+ )
660
+ );
661
+ ports.push(createPort(`${idPrefix}:B-P4`, bottom, pad4));
662
+ ports.push(createPort(`${idPrefix}:B-P5`, bottom, pad5));
663
+ if (regionsBetweenPads) {
664
+ const ujBounds = underjumper.d.bounds;
665
+ const ujWidth = ujBounds.maxX - ujBounds.minX;
666
+ const portSpacing = ujWidth / 5;
667
+ for (let i = 1; i <= 4; i++) {
668
+ const portX = ujBounds.minX + portSpacing * i;
669
+ const bottomUJPort = {
670
+ portId: `${idPrefix}:B-UJ${i}`,
671
+ region1: bottom,
672
+ region2: underjumper,
673
+ d: { x: portX, y: ujBounds.minY }
674
+ };
675
+ bottom.ports.push(bottomUJPort);
676
+ underjumper.ports.push(bottomUJPort);
677
+ ports.push(bottomUJPort);
678
+ }
679
+ } else {
680
+ ports.push(createPort(`${idPrefix}:B-UJ`, bottom, underjumper));
681
+ }
682
+ }
683
+ if (left) {
684
+ ports.push(createPort(`${idPrefix}:L-P1`, left, pad1));
685
+ ports.push(createPort(`${idPrefix}:L-P2`, left, pad2));
686
+ ports.push(createPort(`${idPrefix}:L-P3`, left, pad3));
687
+ ports.push(createPort(`${idPrefix}:L-P4`, left, pad4));
688
+ }
689
+ ports.push(createPort(`${idPrefix}:R-P5`, right, pad5));
690
+ ports.push(createPort(`${idPrefix}:R-P6`, right, pad6));
691
+ ports.push(createPort(`${idPrefix}:R-P7`, right, pad7));
692
+ ports.push(createPort(`${idPrefix}:R-P8`, right, pad8));
693
+ if (regionsBetweenPads) {
694
+ if (left) {
695
+ ports.push(createPort(`${idPrefix}:L-BP12`, left, leftBP12));
696
+ ports.push(createPort(`${idPrefix}:L-BP23`, left, leftBP23));
697
+ ports.push(createPort(`${idPrefix}:L-BP34`, left, leftBP34));
698
+ }
699
+ ports.push(createPort(`${idPrefix}:UJ-LBP12`, leftBP12, underjumper));
700
+ ports.push(createPort(`${idPrefix}:UJ-LBP23`, leftBP23, underjumper));
701
+ ports.push(createPort(`${idPrefix}:UJ-LBP34`, leftBP34, underjumper));
702
+ ports.push(createPort(`${idPrefix}:R-BP87`, right, rightBP87));
703
+ ports.push(createPort(`${idPrefix}:R-BP76`, right, rightBP76));
704
+ ports.push(createPort(`${idPrefix}:R-BP65`, right, rightBP65));
705
+ ports.push(createPort(`${idPrefix}:UJ-RBP87`, rightBP87, underjumper));
706
+ ports.push(createPort(`${idPrefix}:UJ-RBP76`, rightBP76, underjumper));
707
+ ports.push(createPort(`${idPrefix}:UJ-RBP65`, rightBP65, underjumper));
708
+ }
709
+ const tj1LeftPort = {
710
+ portId: `${idPrefix}:TJ1-P1`,
711
+ region1: throughjumper1,
712
+ region2: pad1,
713
+ d: { x: p1CenterX, y: p1CenterY }
714
+ };
715
+ throughjumper1.ports.push(tj1LeftPort);
716
+ pad1.ports.push(tj1LeftPort);
717
+ ports.push(tj1LeftPort);
718
+ const tj1RightPort = {
719
+ portId: `${idPrefix}:TJ1-P8`,
720
+ region1: throughjumper1,
721
+ region2: pad8,
722
+ d: { x: p8CenterX, y: p8CenterY }
723
+ };
724
+ throughjumper1.ports.push(tj1RightPort);
725
+ pad8.ports.push(tj1RightPort);
726
+ ports.push(tj1RightPort);
727
+ const tj2LeftPort = {
728
+ portId: `${idPrefix}:TJ2-P2`,
729
+ region1: throughjumper2,
730
+ region2: pad2,
731
+ d: { x: p2CenterX, y: p2CenterY }
732
+ };
733
+ throughjumper2.ports.push(tj2LeftPort);
734
+ pad2.ports.push(tj2LeftPort);
735
+ ports.push(tj2LeftPort);
736
+ const tj2RightPort = {
737
+ portId: `${idPrefix}:TJ2-P7`,
738
+ region1: throughjumper2,
739
+ region2: pad7,
740
+ d: { x: p7CenterX, y: p7CenterY }
741
+ };
742
+ throughjumper2.ports.push(tj2RightPort);
743
+ pad7.ports.push(tj2RightPort);
744
+ ports.push(tj2RightPort);
745
+ const tj3LeftPort = {
746
+ portId: `${idPrefix}:TJ3-P3`,
747
+ region1: throughjumper3,
748
+ region2: pad3,
749
+ d: { x: p3CenterX, y: p3CenterY }
750
+ };
751
+ throughjumper3.ports.push(tj3LeftPort);
752
+ pad3.ports.push(tj3LeftPort);
753
+ ports.push(tj3LeftPort);
754
+ const tj3RightPort = {
755
+ portId: `${idPrefix}:TJ3-P6`,
756
+ region1: throughjumper3,
757
+ region2: pad6,
758
+ d: { x: p6CenterX, y: p6CenterY }
759
+ };
760
+ throughjumper3.ports.push(tj3RightPort);
761
+ pad6.ports.push(tj3RightPort);
762
+ ports.push(tj3RightPort);
763
+ const tj4LeftPort = {
764
+ portId: `${idPrefix}:TJ4-P4`,
765
+ region1: throughjumper4,
766
+ region2: pad4,
767
+ d: { x: p4CenterX, y: p4CenterY }
768
+ };
769
+ throughjumper4.ports.push(tj4LeftPort);
770
+ pad4.ports.push(tj4LeftPort);
771
+ ports.push(tj4LeftPort);
772
+ const tj4RightPort = {
773
+ portId: `${idPrefix}:TJ4-P5`,
774
+ region1: throughjumper4,
775
+ region2: pad5,
776
+ d: { x: p5CenterX, y: p5CenterY }
777
+ };
778
+ throughjumper4.ports.push(tj4RightPort);
779
+ pad5.ports.push(tj4RightPort);
780
+ ports.push(tj4RightPort);
781
+ if (col > 0) {
782
+ const prevCell = cells[row][col - 1];
783
+ ports.push(
784
+ createPort(
785
+ `cell_${row}_${col - 1}->cell_${row}_${col}:R-P1`,
786
+ prevCell.right,
787
+ pad1
788
+ )
789
+ );
790
+ ports.push(
791
+ createPort(
792
+ `cell_${row}_${col - 1}->cell_${row}_${col}:R-P2`,
793
+ prevCell.right,
794
+ pad2
795
+ )
796
+ );
797
+ ports.push(
798
+ createPort(
799
+ `cell_${row}_${col - 1}->cell_${row}_${col}:R-P3`,
800
+ prevCell.right,
801
+ pad3
802
+ )
803
+ );
804
+ ports.push(
805
+ createPort(
806
+ `cell_${row}_${col - 1}->cell_${row}_${col}:R-P4`,
807
+ prevCell.right,
808
+ pad4
809
+ )
810
+ );
811
+ if (regionsBetweenPads) {
812
+ ports.push(
813
+ createPort(
814
+ `cell_${row}_${col - 1}->cell_${row}_${col}:R-LBP12`,
815
+ prevCell.right,
816
+ leftBP12
817
+ )
818
+ );
819
+ ports.push(
820
+ createPort(
821
+ `cell_${row}_${col - 1}->cell_${row}_${col}:R-LBP23`,
822
+ prevCell.right,
823
+ leftBP23
824
+ )
825
+ );
826
+ ports.push(
827
+ createPort(
828
+ `cell_${row}_${col - 1}->cell_${row}_${col}:R-LBP34`,
829
+ prevCell.right,
830
+ leftBP34
831
+ )
832
+ );
833
+ }
834
+ if (top && prevCell.top) {
835
+ ports.push(
836
+ ...createMultiplePorts(
837
+ `cell_${row}_${col - 1}->cell_${row}_${col}:T-T`,
838
+ prevCell.top,
839
+ top,
840
+ innerRowChannelPointCount
841
+ )
842
+ );
843
+ }
844
+ if (bottom && prevCell.bottom) {
845
+ ports.push(
846
+ ...createMultiplePorts(
847
+ `cell_${row}_${col - 1}->cell_${row}_${col}:B-B`,
848
+ prevCell.bottom,
849
+ bottom,
850
+ innerRowChannelPointCount
851
+ )
852
+ );
853
+ }
854
+ }
855
+ if (row > 0) {
856
+ const aboveCell = cells[row - 1][col];
857
+ if (left) {
858
+ ports.push(
859
+ ...createMultiplePorts(
860
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-L`,
861
+ aboveCell.bottom,
862
+ left,
863
+ effectiveOuterChannelXPoints
864
+ )
865
+ );
866
+ }
867
+ ports.push(
868
+ createPort(
869
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-P1`,
870
+ aboveCell.bottom,
871
+ pad1
872
+ )
873
+ );
874
+ if (regionsBetweenPads) {
875
+ const ujBounds = underjumper.d.bounds;
876
+ const ujWidth = ujBounds.maxX - ujBounds.minX;
877
+ const portSpacing = ujWidth / 5;
878
+ for (let i = 1; i <= 4; i++) {
879
+ const portX = ujBounds.minX + portSpacing * i;
880
+ const aboveUJPort = {
881
+ portId: `cell_${row - 1}_${col}->cell_${row}_${col}:B-UJ${i}`,
882
+ region1: aboveCell.bottom,
883
+ region2: underjumper,
884
+ d: { x: portX, y: ujBounds.maxY }
885
+ };
886
+ aboveCell.bottom.ports.push(aboveUJPort);
887
+ underjumper.ports.push(aboveUJPort);
888
+ ports.push(aboveUJPort);
889
+ }
890
+ } else {
891
+ ports.push(
892
+ createPort(
893
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-UJ`,
894
+ aboveCell.bottom,
895
+ underjumper
896
+ )
897
+ );
898
+ }
899
+ ports.push(
900
+ createPort(
901
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-P8`,
902
+ aboveCell.bottom,
903
+ pad8
904
+ )
905
+ );
906
+ ports.push(
907
+ ...createMultiplePorts(
908
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-R`,
909
+ aboveCell.bottom,
910
+ right,
911
+ isLastCol ? effectiveOuterChannelXPoints : innerColChannelPointCount
912
+ )
913
+ );
914
+ }
915
+ }
916
+ }
917
+ let graph = { regions, ports };
918
+ const needsRotation = orientation2 === "horizontal";
919
+ const needsCentering = center !== void 0;
920
+ if (needsRotation || needsCentering) {
921
+ const currentBounds = calculateGraphBounds(graph.regions);
922
+ const currentCenter = computeBoundsCenter(currentBounds);
923
+ const matrices = [];
924
+ matrices.push(translate(-currentCenter.x, -currentCenter.y));
925
+ if (needsRotation) {
926
+ matrices.push(rotate(-Math.PI / 2));
927
+ }
928
+ const targetCenter = center ?? currentCenter;
929
+ matrices.push(translate(targetCenter.x, targetCenter.y));
930
+ const matrix = compose(...matrices);
931
+ graph = applyTransformToGraph(graph, matrix);
932
+ }
933
+ return graph;
934
+ };
935
+
936
+ // lib/JumperGraphSolver/jumper-graph-generator/generateSingleJumperRegions.ts
937
+ var dims0603 = {
938
+ padToPad: 1.65,
939
+ padLength: 0.8,
940
+ padWidth: 0.95
941
+ };
942
+
943
+ // lib/JumperGraphSolver/jumper-graph-generator/generateJumperGrid.ts
944
+ var generateJumperGrid = ({
945
+ cols,
946
+ rows,
947
+ marginX,
948
+ marginY,
949
+ innerColChannelPointCount = 1,
950
+ innerRowChannelPointCount = 1,
951
+ outerPaddingX = 0.5,
952
+ outerPaddingY = 0.5,
953
+ outerChannelXPoints,
954
+ outerChannelYPoints
955
+ }) => {
956
+ const effectiveOuterChannelXPoints = outerChannelXPoints ?? Math.max(1, Math.floor(outerPaddingX / 0.4));
957
+ const effectiveOuterChannelYPoints = outerChannelYPoints ?? Math.max(1, Math.floor(outerPaddingY / 0.4));
958
+ const regions = [];
959
+ const ports = [];
960
+ const { padToPad, padLength, padWidth } = dims0603;
961
+ const padHalfLength = padLength / 2;
962
+ const padHalfWidth = padWidth / 2;
963
+ const horizontalSpacing = padToPad + padLength + marginX;
964
+ const verticalSpacing = padWidth + marginY;
965
+ const cells = [];
966
+ const createRegion = (id, bounds, isPad, isThroughJumper) => ({
967
+ regionId: id,
968
+ ports: [],
969
+ d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper }
970
+ });
971
+ const createPort = (id, region1, region2) => {
972
+ const b1 = region1.d.bounds;
973
+ const b2 = region2.d.bounds;
974
+ let x;
975
+ let y;
976
+ if (Math.abs(b1.maxX - b2.minX) < 1e-3) {
977
+ x = b1.maxX;
978
+ y = (Math.max(b1.minY, b2.minY) + Math.min(b1.maxY, b2.maxY)) / 2;
979
+ } else if (Math.abs(b1.minX - b2.maxX) < 1e-3) {
980
+ x = b1.minX;
981
+ y = (Math.max(b1.minY, b2.minY) + Math.min(b1.maxY, b2.maxY)) / 2;
982
+ } else if (Math.abs(b1.maxY - b2.minY) < 1e-3) {
983
+ x = (Math.max(b1.minX, b2.minX) + Math.min(b1.maxX, b2.maxX)) / 2;
984
+ y = b1.maxY;
985
+ } else {
986
+ x = (Math.max(b1.minX, b2.minX) + Math.min(b1.maxX, b2.maxX)) / 2;
987
+ y = b1.minY;
988
+ }
989
+ const port = {
990
+ portId: id,
991
+ region1,
992
+ region2,
993
+ d: { x, y }
994
+ };
995
+ region1.ports.push(port);
996
+ region2.ports.push(port);
997
+ return port;
998
+ };
999
+ const createMultiplePorts = (idPrefix, region1, region2, count) => {
1000
+ if (count <= 0) return [];
1001
+ if (count === 1) {
1002
+ return [createPort(idPrefix, region1, region2)];
1003
+ }
1004
+ const b1 = region1.d.bounds;
1005
+ const b2 = region2.d.bounds;
1006
+ const result = [];
1007
+ let isVerticalBoundary;
1008
+ let boundaryCoord;
1009
+ let sharedMin;
1010
+ let sharedMax;
1011
+ if (Math.abs(b1.maxX - b2.minX) < 1e-3) {
1012
+ isVerticalBoundary = true;
1013
+ boundaryCoord = b1.maxX;
1014
+ sharedMin = Math.max(b1.minY, b2.minY);
1015
+ sharedMax = Math.min(b1.maxY, b2.maxY);
1016
+ } else if (Math.abs(b1.minX - b2.maxX) < 1e-3) {
1017
+ isVerticalBoundary = true;
1018
+ boundaryCoord = b1.minX;
1019
+ sharedMin = Math.max(b1.minY, b2.minY);
1020
+ sharedMax = Math.min(b1.maxY, b2.maxY);
1021
+ } else if (Math.abs(b1.maxY - b2.minY) < 1e-3) {
1022
+ isVerticalBoundary = false;
1023
+ boundaryCoord = b1.maxY;
1024
+ sharedMin = Math.max(b1.minX, b2.minX);
1025
+ sharedMax = Math.min(b1.maxX, b2.maxX);
1026
+ } else {
1027
+ isVerticalBoundary = false;
1028
+ boundaryCoord = b1.minY;
1029
+ sharedMin = Math.max(b1.minX, b2.minX);
1030
+ sharedMax = Math.min(b1.maxX, b2.maxX);
1031
+ }
1032
+ for (let i = 0; i < count; i++) {
1033
+ const t = (i + 0.5) / count;
1034
+ const coord = sharedMin + t * (sharedMax - sharedMin);
1035
+ const x = isVerticalBoundary ? boundaryCoord : coord;
1036
+ const y = isVerticalBoundary ? coord : boundaryCoord;
1037
+ const port = {
1038
+ portId: `${idPrefix}:${i}`,
1039
+ region1,
1040
+ region2,
1041
+ d: { x, y }
1042
+ };
1043
+ region1.ports.push(port);
1044
+ region2.ports.push(port);
1045
+ result.push(port);
1046
+ }
1047
+ return result;
1048
+ };
1049
+ for (let row = 0; row < rows; row++) {
1050
+ cells[row] = [];
1051
+ for (let col = 0; col < cols; col++) {
1052
+ const idPrefix = `cell_${row}_${col}`;
1053
+ const centerX = col * horizontalSpacing;
1054
+ const centerY = -row * verticalSpacing;
1055
+ const leftPadCenterX = centerX - padToPad / 2;
1056
+ const rightPadCenterX = centerX + padToPad / 2;
1057
+ const leftPadBounds = {
1058
+ minX: leftPadCenterX - padHalfLength,
1059
+ maxX: leftPadCenterX + padHalfLength,
1060
+ minY: centerY - padHalfWidth,
1061
+ maxY: centerY + padHalfWidth
1062
+ };
1063
+ const rightPadBounds = {
1064
+ minX: rightPadCenterX - padHalfLength,
1065
+ maxX: rightPadCenterX + padHalfLength,
1066
+ minY: centerY - padHalfWidth,
1067
+ maxY: centerY + padHalfWidth
1068
+ };
1069
+ const underjumperBounds = {
1070
+ minX: leftPadBounds.maxX,
1071
+ maxX: rightPadBounds.minX,
1072
+ minY: centerY - padHalfWidth,
1073
+ maxY: centerY + padHalfWidth
1074
+ };
1075
+ const throughjumperHeight = 0.3;
1076
+ const throughjumperBounds = {
1077
+ minX: leftPadCenterX,
1078
+ maxX: rightPadCenterX,
1079
+ minY: centerY - throughjumperHeight / 2,
1080
+ maxY: centerY + throughjumperHeight / 2
1081
+ };
1082
+ const mainMinX = leftPadBounds.minX;
1083
+ const mainMaxX = rightPadBounds.maxX;
1084
+ const mainMinY = leftPadBounds.minY;
1085
+ const mainMaxY = leftPadBounds.maxY;
1086
+ const leftPad = createRegion(`${idPrefix}:leftPad`, leftPadBounds, true);
1087
+ const rightPad = createRegion(
1088
+ `${idPrefix}:rightPad`,
1089
+ rightPadBounds,
1090
+ true
1091
+ );
1092
+ const underjumper = createRegion(
1093
+ `${idPrefix}:underjumper`,
1094
+ underjumperBounds,
1095
+ false
1096
+ );
1097
+ const throughjumper = createRegion(
1098
+ `${idPrefix}:throughjumper`,
1099
+ throughjumperBounds,
1100
+ false,
1101
+ true
1102
+ );
1103
+ regions.push(leftPad, rightPad, underjumper, throughjumper);
1104
+ const isFirstRow = row === 0;
1105
+ const isFirstCol = col === 0;
1106
+ const isLastRow = row === rows - 1;
1107
+ const isLastCol = col === cols - 1;
1108
+ let frameRightEdge;
1109
+ if (isLastCol) {
1110
+ frameRightEdge = mainMaxX + outerPaddingX;
1111
+ } else {
1112
+ const nextCenterX = (col + 1) * horizontalSpacing;
1113
+ const nextLeftPadCenterX = nextCenterX - padToPad / 2;
1114
+ frameRightEdge = nextLeftPadCenterX - padHalfLength;
1115
+ }
1116
+ let top = null;
1117
+ if (isFirstRow) {
1118
+ top = createRegion(
1119
+ `${idPrefix}:T`,
1120
+ {
1121
+ minX: isFirstCol ? mainMinX - outerPaddingX : mainMinX,
1122
+ maxX: frameRightEdge,
1123
+ minY: mainMaxY,
1124
+ maxY: mainMaxY + outerPaddingY
1125
+ },
1126
+ false
1127
+ );
1128
+ regions.push(top);
1129
+ }
1130
+ let bottom = null;
1131
+ const bottomHeight = isLastRow ? outerPaddingY : marginY;
1132
+ bottom = createRegion(
1133
+ `${idPrefix}:B`,
1134
+ {
1135
+ minX: isFirstCol ? mainMinX - outerPaddingX : mainMinX,
1136
+ maxX: frameRightEdge,
1137
+ minY: mainMinY - bottomHeight,
1138
+ maxY: mainMinY
1139
+ },
1140
+ false
1141
+ );
1142
+ regions.push(bottom);
1143
+ let left = null;
1144
+ if (isFirstCol) {
1145
+ left = createRegion(
1146
+ `${idPrefix}:L`,
1147
+ {
1148
+ minX: mainMinX - outerPaddingX,
1149
+ maxX: mainMinX,
1150
+ minY: mainMinY,
1151
+ maxY: mainMaxY
1152
+ },
1153
+ false
1154
+ );
1155
+ regions.push(left);
1156
+ }
1157
+ const right = createRegion(
1158
+ `${idPrefix}:R`,
1159
+ {
1160
+ minX: mainMaxX,
1161
+ maxX: frameRightEdge,
1162
+ minY: mainMinY,
1163
+ maxY: mainMaxY
1164
+ },
1165
+ false
1166
+ );
1167
+ regions.push(right);
1168
+ cells[row][col] = {
1169
+ leftPad,
1170
+ rightPad,
1171
+ underjumper,
1172
+ throughjumper,
1173
+ top,
1174
+ bottom,
1175
+ left,
1176
+ right
1177
+ };
1178
+ if (top) {
1179
+ if (left) {
1180
+ ports.push(
1181
+ ...createMultiplePorts(
1182
+ `${idPrefix}:T-L`,
1183
+ top,
1184
+ left,
1185
+ effectiveOuterChannelXPoints
1186
+ )
1187
+ );
1188
+ }
1189
+ ports.push(
1190
+ ...createMultiplePorts(
1191
+ `${idPrefix}:T-R`,
1192
+ top,
1193
+ right,
1194
+ effectiveOuterChannelXPoints
1195
+ )
1196
+ );
1197
+ ports.push(createPort(`${idPrefix}:T-LP`, top, leftPad));
1198
+ ports.push(createPort(`${idPrefix}:T-RP`, top, rightPad));
1199
+ ports.push(createPort(`${idPrefix}:T-UJ`, top, underjumper));
1200
+ }
1201
+ if (bottom) {
1202
+ if (left) {
1203
+ ports.push(
1204
+ ...createMultiplePorts(
1205
+ `${idPrefix}:B-L`,
1206
+ bottom,
1207
+ left,
1208
+ effectiveOuterChannelXPoints
1209
+ )
1210
+ );
1211
+ }
1212
+ ports.push(
1213
+ ...createMultiplePorts(
1214
+ `${idPrefix}:B-R`,
1215
+ bottom,
1216
+ right,
1217
+ effectiveOuterChannelXPoints
1218
+ )
1219
+ );
1220
+ ports.push(createPort(`${idPrefix}:B-LP`, bottom, leftPad));
1221
+ ports.push(createPort(`${idPrefix}:B-RP`, bottom, rightPad));
1222
+ ports.push(createPort(`${idPrefix}:B-UJ`, bottom, underjumper));
1223
+ }
1224
+ if (left) {
1225
+ ports.push(createPort(`${idPrefix}:L-LP`, left, leftPad));
1226
+ }
1227
+ ports.push(createPort(`${idPrefix}:R-RP`, right, rightPad));
1228
+ const leftThroughPort = {
1229
+ portId: `${idPrefix}:TJ-LP`,
1230
+ region1: throughjumper,
1231
+ region2: leftPad,
1232
+ d: { x: leftPadCenterX, y: centerY }
1233
+ };
1234
+ throughjumper.ports.push(leftThroughPort);
1235
+ leftPad.ports.push(leftThroughPort);
1236
+ ports.push(leftThroughPort);
1237
+ const rightThroughPort = {
1238
+ portId: `${idPrefix}:TJ-RP`,
1239
+ region1: throughjumper,
1240
+ region2: rightPad,
1241
+ d: { x: rightPadCenterX, y: centerY }
1242
+ };
1243
+ throughjumper.ports.push(rightThroughPort);
1244
+ rightPad.ports.push(rightThroughPort);
1245
+ ports.push(rightThroughPort);
1246
+ if (col > 0) {
1247
+ const prevCell = cells[row][col - 1];
1248
+ ports.push(
1249
+ createPort(
1250
+ `cell_${row}_${col - 1}->cell_${row}_${col}:R-LP`,
1251
+ prevCell.right,
1252
+ leftPad
1253
+ )
1254
+ );
1255
+ if (top && prevCell.top) {
1256
+ ports.push(
1257
+ ...createMultiplePorts(
1258
+ `cell_${row}_${col - 1}->cell_${row}_${col}:T-T`,
1259
+ prevCell.top,
1260
+ top,
1261
+ innerRowChannelPointCount
1262
+ )
1263
+ );
1264
+ }
1265
+ if (bottom && prevCell.bottom) {
1266
+ ports.push(
1267
+ ...createMultiplePorts(
1268
+ `cell_${row}_${col - 1}->cell_${row}_${col}:B-B`,
1269
+ prevCell.bottom,
1270
+ bottom,
1271
+ innerRowChannelPointCount
1272
+ )
1273
+ );
1274
+ }
1275
+ }
1276
+ if (row > 0) {
1277
+ const aboveCell = cells[row - 1][col];
1278
+ if (left) {
1279
+ ports.push(
1280
+ ...createMultiplePorts(
1281
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-L`,
1282
+ aboveCell.bottom,
1283
+ left,
1284
+ effectiveOuterChannelXPoints
1285
+ )
1286
+ );
1287
+ }
1288
+ ports.push(
1289
+ createPort(
1290
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-LP`,
1291
+ aboveCell.bottom,
1292
+ leftPad
1293
+ )
1294
+ );
1295
+ ports.push(
1296
+ createPort(
1297
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-UJ`,
1298
+ aboveCell.bottom,
1299
+ underjumper
1300
+ )
1301
+ );
1302
+ ports.push(
1303
+ createPort(
1304
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-RP`,
1305
+ aboveCell.bottom,
1306
+ rightPad
1307
+ )
1308
+ );
1309
+ ports.push(
1310
+ ...createMultiplePorts(
1311
+ `cell_${row - 1}_${col}->cell_${row}_${col}:B-R`,
1312
+ aboveCell.bottom,
1313
+ right,
1314
+ effectiveOuterChannelXPoints
1315
+ )
1316
+ );
1317
+ }
1318
+ }
1319
+ }
1320
+ return {
1321
+ regions,
1322
+ ports
1323
+ };
1324
+ };
1325
+
1326
+ // lib/JumperGraphSolver/jumper-graph-generator/createConnectionPort.ts
1327
+ var createConnectionPort = (portId, connectionRegion, boundaryRegion, portPosition) => {
1328
+ const port = {
1329
+ portId,
1330
+ region1: connectionRegion,
1331
+ region2: boundaryRegion,
1332
+ d: { x: portPosition.x, y: portPosition.y }
1333
+ };
1334
+ connectionRegion.ports.push(port);
1335
+ boundaryRegion.ports.push(port);
1336
+ return port;
1337
+ };
1338
+
1339
+ // lib/JumperGraphSolver/jumper-graph-generator/createConnectionRegion.ts
1340
+ var CONNECTION_REGION_SIZE = 0.4;
1341
+ var createConnectionRegion = (regionId, x, y) => {
1342
+ const halfSize = CONNECTION_REGION_SIZE / 2;
1343
+ return {
1344
+ regionId,
1345
+ ports: [],
1346
+ d: {
1347
+ bounds: {
1348
+ minX: x - halfSize,
1349
+ maxX: x + halfSize,
1350
+ minY: y - halfSize,
1351
+ maxY: y + halfSize
1352
+ },
1353
+ center: { x, y },
1354
+ isPad: false,
1355
+ isConnectionRegion: true
1356
+ }
1357
+ };
1358
+ };
1359
+
1360
+ // lib/JumperGraphSolver/jumper-graph-generator/findBoundaryRegion.ts
1361
+ var findBoundaryRegion = (x, y, regions, graphBounds) => {
1362
+ for (const region of regions) {
1363
+ if (region.d.isPad || region.d.isThroughJumper) continue;
1364
+ const bounds = region.d.bounds;
1365
+ if (Math.abs(x - bounds.minX) < 0.01 && y >= bounds.minY && y <= bounds.maxY) {
1366
+ return { region, portPosition: { x: bounds.minX, y } };
1367
+ }
1368
+ if (Math.abs(x - bounds.maxX) < 0.01 && y >= bounds.minY && y <= bounds.maxY) {
1369
+ return { region, portPosition: { x: bounds.maxX, y } };
1370
+ }
1371
+ if (Math.abs(y - bounds.minY) < 0.01 && x >= bounds.minX && x <= bounds.maxX) {
1372
+ return { region, portPosition: { x, y: bounds.minY } };
1373
+ }
1374
+ if (Math.abs(y - bounds.maxY) < 0.01 && x >= bounds.minX && x <= bounds.maxX) {
1375
+ return { region, portPosition: { x, y: bounds.maxY } };
1376
+ }
1377
+ }
1378
+ let closestRegion = null;
1379
+ let closestDistance = Number.POSITIVE_INFINITY;
1380
+ let closestPortPosition = { x, y };
1381
+ for (const region of regions) {
1382
+ if (region.d.isPad || region.d.isThroughJumper) continue;
1383
+ const bounds = region.d.bounds;
1384
+ const isOuterRegion = Math.abs(bounds.minX - graphBounds.minX) < 0.01 || Math.abs(bounds.maxX - graphBounds.maxX) < 0.01 || Math.abs(bounds.minY - graphBounds.minY) < 0.01 || Math.abs(bounds.maxY - graphBounds.maxY) < 0.01;
1385
+ if (!isOuterRegion) continue;
1386
+ const clampedX = Math.max(bounds.minX, Math.min(bounds.maxX, x));
1387
+ const clampedY = Math.max(bounds.minY, Math.min(bounds.maxY, y));
1388
+ const dist = Math.sqrt((x - clampedX) ** 2 + (y - clampedY) ** 2);
1389
+ if (dist < closestDistance) {
1390
+ closestDistance = dist;
1391
+ closestRegion = region;
1392
+ if (x < bounds.minX) {
1393
+ closestPortPosition = { x: bounds.minX, y: clampedY };
1394
+ } else if (x > bounds.maxX) {
1395
+ closestPortPosition = { x: bounds.maxX, y: clampedY };
1396
+ } else if (y < bounds.minY) {
1397
+ closestPortPosition = { x: clampedX, y: bounds.minY };
1398
+ } else if (y > bounds.maxY) {
1399
+ closestPortPosition = { x: clampedX, y: bounds.maxY };
1400
+ } else {
1401
+ closestPortPosition = { x: clampedX, y: clampedY };
1402
+ }
1403
+ }
1404
+ }
1405
+ if (closestRegion) {
1406
+ return { region: closestRegion, portPosition: closestPortPosition };
1407
+ }
1408
+ return null;
1409
+ };
1410
+
1411
+ // lib/JumperGraphSolver/jumper-graph-generator/createGraphWithConnectionsFromBaseGraph.ts
1412
+ var createGraphWithConnectionsFromBaseGraph = (baseGraph, xyConnections) => {
1413
+ const regions = [...baseGraph.regions];
1414
+ const ports = [...baseGraph.ports];
1415
+ const connections = [];
1416
+ const graphBounds = calculateGraphBounds(baseGraph.regions);
1417
+ for (const xyConn of xyConnections) {
1418
+ const { start, end, connectionId } = xyConn;
1419
+ const startRegion = createConnectionRegion(
1420
+ `conn:${connectionId}:start`,
1421
+ start.x,
1422
+ start.y
1423
+ );
1424
+ regions.push(startRegion);
1425
+ const endRegion = createConnectionRegion(
1426
+ `conn:${connectionId}:end`,
1427
+ end.x,
1428
+ end.y
1429
+ );
1430
+ regions.push(endRegion);
1431
+ const startBoundary = findBoundaryRegion(
1432
+ start.x,
1433
+ start.y,
1434
+ baseGraph.regions,
1435
+ graphBounds
1436
+ );
1437
+ if (startBoundary) {
1438
+ const startPort = createConnectionPort(
1439
+ `conn:${connectionId}:start-port`,
1440
+ startRegion,
1441
+ startBoundary.region,
1442
+ startBoundary.portPosition
1443
+ );
1444
+ ports.push(startPort);
1445
+ }
1446
+ const endBoundary = findBoundaryRegion(
1447
+ end.x,
1448
+ end.y,
1449
+ baseGraph.regions,
1450
+ graphBounds
1451
+ );
1452
+ if (endBoundary) {
1453
+ const endPort = createConnectionPort(
1454
+ `conn:${connectionId}:end-port`,
1455
+ endRegion,
1456
+ endBoundary.region,
1457
+ endBoundary.portPosition
1458
+ );
1459
+ ports.push(endPort);
1460
+ }
1461
+ const connection = {
1462
+ connectionId,
1463
+ mutuallyConnectedNetworkId: connectionId,
1464
+ startRegion,
1465
+ endRegion
1466
+ };
1467
+ connections.push(connection);
1468
+ }
1469
+ return {
1470
+ regions,
1471
+ ports,
1472
+ connections
1473
+ };
1474
+ };
1475
+
1476
+ // lib/HyperGraphSolver.ts
1477
+ import { BaseSolver } from "@tscircuit/solver-utils";
1478
+
1479
+ // lib/convertSerializedHyperGraphToHyperGraph.ts
1480
+ var convertSerializedHyperGraphToHyperGraph = (inputGraph) => {
1481
+ if (inputGraph.ports.length > 0 && "region1" in inputGraph.ports[0] && typeof inputGraph.ports[0].region1 === "object") {
1482
+ return inputGraph;
1483
+ }
1484
+ const portMap = /* @__PURE__ */ new Map();
1485
+ const regionMap = /* @__PURE__ */ new Map();
1486
+ for (const region of inputGraph.regions) {
1487
+ const { assignments: _, ...regionWithoutAssignments } = region;
1488
+ regionMap.set(region.regionId, {
1489
+ ...regionWithoutAssignments,
1490
+ ports: [],
1491
+ assignments: void 0
1492
+ });
1493
+ }
1494
+ for (const port of inputGraph.ports) {
1495
+ const region1 = regionMap.get(port.region1Id ?? port.region1?.regionId);
1496
+ const region2 = regionMap.get(port.region2Id ?? port.region2?.regionId);
1497
+ const hydratedPort = {
1498
+ portId: port.portId,
1499
+ region1,
1500
+ region2,
1501
+ d: port.d
1502
+ };
1503
+ portMap.set(port.portId, hydratedPort);
1504
+ region1.ports.push(hydratedPort);
1505
+ region2.ports.push(hydratedPort);
1506
+ }
1507
+ return {
1508
+ ports: Array.from(portMap.values()),
1509
+ regions: Array.from(regionMap.values())
1510
+ };
1511
+ };
1512
+
1513
+ // lib/convertSerializedConnectionsToConnections.ts
1514
+ var convertSerializedConnectionsToConnections = (inputConnections, graph) => {
1515
+ const connections = [];
1516
+ for (const inputConn of inputConnections) {
1517
+ if ("startRegionId" in inputConn) {
1518
+ connections.push({
1519
+ connectionId: inputConn.connectionId,
1520
+ mutuallyConnectedNetworkId: inputConn.connectionId,
1521
+ startRegion: graph.regions.find(
1522
+ (region) => region.regionId === inputConn.startRegionId
1523
+ ),
1524
+ endRegion: graph.regions.find(
1525
+ (region) => region.regionId === inputConn.endRegionId
1526
+ )
1527
+ });
1528
+ } else {
1529
+ connections.push(inputConn);
1530
+ }
1531
+ }
1532
+ return connections;
1533
+ };
1534
+
1535
+ // lib/PriorityQueue.ts
1536
+ var PriorityQueue = class {
1537
+ // Store the heap as an array. Index 0 is the root (highest priority/smallest 'f').
1538
+ heap = [];
1539
+ maxSize;
1540
+ /**
1541
+ * Creates a new Priority Queue.
1542
+ * @param nodes An optional initial array of nodes to populate the queue.
1543
+ * @param maxSize The maximum number of elements the queue can hold. Defaults to 10,000.
1544
+ */
1545
+ constructor(nodes = [], maxSize = 1e4) {
1546
+ this.maxSize = maxSize;
1547
+ if (nodes.length > 0) {
1548
+ this.heap = [...nodes].sort((a, b) => a.f - b.f).slice(0, this.maxSize);
1549
+ for (let i = Math.floor(this.heap.length / 2) - 1; i >= 0; i--) {
1550
+ this._siftDown(i);
1551
+ }
1552
+ }
1553
+ }
1554
+ /**
1555
+ * Returns the number of elements currently in the queue.
1556
+ */
1557
+ get size() {
1558
+ return this.heap.length;
1559
+ }
1560
+ /**
1561
+ * Checks if the queue is empty.
1562
+ */
1563
+ isEmpty() {
1564
+ return this.heap.length === 0;
1565
+ }
1566
+ /**
1567
+ * Returns the node with the highest priority (smallest 'f') without removing it.
1568
+ * Returns null if the queue is empty.
1569
+ * @returns The highest priority node or null.
1570
+ */
1571
+ peek() {
1572
+ if (this.isEmpty()) {
1573
+ return null;
1574
+ }
1575
+ return this.heap[0] ?? null;
1576
+ }
1577
+ /**
1578
+ * Returns up to N nodes with the highest priority (smallest 'f') without removing them.
1579
+ * Nodes are returned sorted by priority (smallest 'f' first).
1580
+ * @param count Maximum number of nodes to return.
1581
+ * @returns Array of nodes sorted by priority.
1582
+ */
1583
+ peekMany(count) {
1584
+ return [...this.heap].sort((a, b) => a.f - b.f).slice(0, count);
1585
+ }
1586
+ /**
1587
+ * Removes and returns the node with the highest priority (smallest 'f').
1588
+ * Returns null if the queue is empty.
1589
+ * Maintains the heap property.
1590
+ * @returns The highest priority node or null.
1591
+ */
1592
+ dequeue() {
1593
+ if (this.isEmpty()) {
1594
+ return null;
1595
+ }
1596
+ const minNode = this.heap[0];
1597
+ const lastNode = this.heap.pop();
1598
+ if (this.heap.length === 0 && lastNode !== void 0) {
1599
+ return minNode;
1600
+ }
1601
+ if (lastNode !== void 0) {
1602
+ this.heap[0] = lastNode;
1603
+ this._siftDown(0);
1604
+ }
1605
+ return minNode;
1606
+ }
1607
+ /**
1608
+ * Adds a new node to the queue.
1609
+ * Maintains the heap property.
1610
+ * If the queue is full (at maxSize), the node is not added.
1611
+ * @param node The node to add.
1612
+ */
1613
+ enqueue(node) {
1614
+ if (this.heap.length >= this.maxSize) {
1615
+ return;
1616
+ }
1617
+ this.heap.push(node);
1618
+ this._siftUp(this.heap.length - 1);
1619
+ }
1620
+ // --- Private Helper Methods ---
1621
+ /**
1622
+ * Moves the node at the given index up the heap to maintain the heap property.
1623
+ * @param index The index of the node to sift up.
1624
+ */
1625
+ _siftUp(index) {
1626
+ let currentIndex = index;
1627
+ while (currentIndex > 0) {
1628
+ const parentIndex = this._parentIndex(currentIndex);
1629
+ if (this.heap[parentIndex].f <= this.heap[currentIndex].f) {
1630
+ break;
1631
+ }
1632
+ this._swap(currentIndex, parentIndex);
1633
+ currentIndex = parentIndex;
1634
+ }
1635
+ }
1636
+ /**
1637
+ * Moves the node at the given index down the heap to maintain the heap property.
1638
+ * @param index The index of the node to sift down.
1639
+ */
1640
+ _siftDown(index) {
1641
+ let currentIndex = index;
1642
+ const heapSize = this.heap.length;
1643
+ while (true) {
1644
+ const leftChildIndex = this._leftChildIndex(currentIndex);
1645
+ const rightChildIndex = this._rightChildIndex(currentIndex);
1646
+ let smallestIndex = currentIndex;
1647
+ if (leftChildIndex < heapSize && this.heap[leftChildIndex].f < this.heap[smallestIndex].f) {
1648
+ smallestIndex = leftChildIndex;
1649
+ }
1650
+ if (rightChildIndex < heapSize && this.heap[rightChildIndex].f < this.heap[smallestIndex].f) {
1651
+ smallestIndex = rightChildIndex;
1652
+ }
1653
+ if (smallestIndex !== currentIndex) {
1654
+ this._swap(currentIndex, smallestIndex);
1655
+ currentIndex = smallestIndex;
1656
+ } else {
1657
+ break;
1658
+ }
1659
+ }
1660
+ }
1661
+ /**
1662
+ * Swaps two elements in the heap array.
1663
+ * @param i Index of the first element.
1664
+ * @param j Index of the second element.
1665
+ */
1666
+ _swap(i, j) {
1667
+ ;
1668
+ [this.heap[i], this.heap[j]] = [this.heap[j], this.heap[i]];
1669
+ }
1670
+ /** Calculates the parent index of a node. */
1671
+ _parentIndex(index) {
1672
+ return Math.floor((index - 1) / 2);
1673
+ }
1674
+ /** Calculates the left child index of a node. */
1675
+ _leftChildIndex(index) {
1676
+ return 2 * index + 1;
1677
+ }
1678
+ /** Calculates the right child index of a node. */
1679
+ _rightChildIndex(index) {
1680
+ return 2 * index + 2;
1681
+ }
1682
+ };
1683
+
1684
+ // lib/HyperGraphSolver.ts
1685
+ var HyperGraphSolver = class extends BaseSolver {
1686
+ constructor(input) {
1687
+ super();
1688
+ this.input = input;
1689
+ this.graph = convertSerializedHyperGraphToHyperGraph(input.inputGraph);
1690
+ for (const region of this.graph.regions) {
1691
+ region.assignments = [];
1692
+ }
1693
+ this.connections = convertSerializedConnectionsToConnections(
1694
+ input.inputConnections,
1695
+ this.graph
1696
+ );
1697
+ if (input.greedyMultiplier !== void 0)
1698
+ this.greedyMultiplier = input.greedyMultiplier;
1699
+ if (input.rippingEnabled !== void 0)
1700
+ this.rippingEnabled = input.rippingEnabled;
1701
+ if (input.ripCost !== void 0) this.ripCost = input.ripCost;
1702
+ this.unprocessedConnections = [...this.connections];
1703
+ this.candidateQueue = new PriorityQueue();
1704
+ this.beginNewConnection();
1705
+ }
1706
+ graph;
1707
+ connections;
1708
+ candidateQueue;
1709
+ unprocessedConnections;
1710
+ solvedRoutes = [];
1711
+ currentConnection = null;
1712
+ currentEndRegion = null;
1713
+ greedyMultiplier = 1;
1714
+ rippingEnabled = false;
1715
+ ripCost = 0;
1716
+ lastCandidate = null;
1717
+ visitedPointsForCurrentConnection = /* @__PURE__ */ new Map();
1718
+ computeH(candidate) {
1719
+ return this.estimateCostToEnd(candidate.port);
1720
+ }
1721
+ /**
1722
+ * OVERRIDE THIS
1723
+ *
1724
+ * Return the estimated remaining cost to the end of the route. You must
1725
+ * first understand the UNIT of your costs. If it's distance, then this could
1726
+ * be something like distance(port, this.currentEndRegion.d.center)
1727
+ */
1728
+ estimateCostToEnd(port) {
1729
+ return 0;
1730
+ }
1731
+ /**
1732
+ * OPTIONALLY OVERRIDE THIS
1733
+ *
1734
+ * This is a penalty for using a port that is not relative to a connection,
1735
+ * e.g. maybe this port is in a special area of congestion. Use this to
1736
+ * penalize ports that are e.g. likely to block off connections, you may want
1737
+ * to use port.ripCount to help determine this penalty, or you can use port
1738
+ * position, region volume etc.
1739
+ */
1740
+ getPortUsagePenalty(port) {
1741
+ return 0;
1742
+ }
1743
+ /**
1744
+ * OVERRIDE THIS
1745
+ *
1746
+ * Return the cost of using two ports in the region, make sure to consider
1747
+ * existing assignments. You may use this to penalize intersections
1748
+ */
1749
+ computeIncreasedRegionCostIfPortsAreUsed(region, port1, port2) {
1750
+ return 0;
1751
+ }
1752
+ /**
1753
+ * OPTIONALLY OVERRIDE THIS
1754
+ *
1755
+ * Return the assignments that would need to be ripped if the given ports
1756
+ * are used together in the region. This is used to determine if adopting
1757
+ * a route would require ripping other routes due to problematic crossings.
1758
+ */
1759
+ getRipsRequiredForPortUsage(_region, _port1, _port2) {
1760
+ return [];
1761
+ }
1762
+ computeG(candidate) {
1763
+ return candidate.parent.g + this.computeIncreasedRegionCostIfPortsAreUsed(
1764
+ candidate.lastRegion,
1765
+ candidate.lastPort,
1766
+ candidate.port
1767
+ ) + (candidate.ripRequired ? this.ripCost : 0) + this.getPortUsagePenalty(candidate.port);
1768
+ }
1769
+ /**
1770
+ * Return a subset of the candidates for entering a region. These candidates
1771
+ * are all possible ways to enter the region- you can e.g. return the middle
1772
+ * port to make it so that you're not queueing candidates that are likely
1773
+ * redundant.
1774
+ */
1775
+ selectCandidatesForEnteringRegion(candidates) {
1776
+ return candidates;
1777
+ }
1778
+ getNextCandidates(currentCandidate) {
1779
+ const currentRegion = currentCandidate.nextRegion;
1780
+ const currentPort = currentCandidate.port;
1781
+ const nextCandidatesByRegion = {};
1782
+ for (const port of currentRegion.ports) {
1783
+ if (port === currentCandidate.port) continue;
1784
+ const ripRequired = port.assignment && port.assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId;
1785
+ const newCandidate = {
1786
+ port,
1787
+ hops: currentCandidate.hops + 1,
1788
+ parent: currentCandidate,
1789
+ lastRegion: currentRegion,
1790
+ nextRegion: port.region1 === currentRegion ? port.region2 : port.region1,
1791
+ lastPort: currentPort,
1792
+ ripRequired
1793
+ };
1794
+ if (!this.rippingEnabled && newCandidate.ripRequired) {
1795
+ continue;
1796
+ }
1797
+ nextCandidatesByRegion[newCandidate.nextRegion.regionId] ??= [];
1798
+ nextCandidatesByRegion[newCandidate.nextRegion.regionId].push(
1799
+ newCandidate
1800
+ );
1801
+ }
1802
+ const nextCandidates = [];
1803
+ for (const regionId in nextCandidatesByRegion) {
1804
+ const nextCandidatesInRegion = nextCandidatesByRegion[regionId];
1805
+ nextCandidates.push(
1806
+ ...this.selectCandidatesForEnteringRegion(nextCandidatesInRegion)
1807
+ );
1808
+ }
1809
+ for (const nextCandidate of nextCandidates) {
1810
+ nextCandidate.g = this.computeG(nextCandidate);
1811
+ nextCandidate.h = this.computeH(nextCandidate);
1812
+ nextCandidate.f = nextCandidate.g + nextCandidate.h * this.greedyMultiplier;
1813
+ }
1814
+ return nextCandidates;
1815
+ }
1816
+ processSolvedRoute(finalCandidate) {
1817
+ const solvedRoute = {
1818
+ path: [],
1819
+ connection: this.currentConnection,
1820
+ requiredRip: false
1821
+ };
1822
+ let cursorCandidate = finalCandidate;
1823
+ let anyRipsRequired = false;
1824
+ while (cursorCandidate) {
1825
+ anyRipsRequired ||= !!cursorCandidate.ripRequired;
1826
+ solvedRoute.path.unshift(cursorCandidate);
1827
+ cursorCandidate = cursorCandidate.parent;
1828
+ }
1829
+ const routesToRip = /* @__PURE__ */ new Set();
1830
+ if (anyRipsRequired) {
1831
+ solvedRoute.requiredRip = true;
1832
+ for (const candidate of solvedRoute.path) {
1833
+ if (candidate.port.assignment && candidate.port.assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId) {
1834
+ routesToRip.add(candidate.port.assignment.solvedRoute);
1835
+ }
1836
+ }
1837
+ }
1838
+ for (const candidate of solvedRoute.path) {
1839
+ if (!candidate.lastPort || !candidate.lastRegion) continue;
1840
+ const ripsRequired = this.getRipsRequiredForPortUsage(
1841
+ candidate.lastRegion,
1842
+ candidate.lastPort,
1843
+ candidate.port
1844
+ );
1845
+ for (const assignment of ripsRequired) {
1846
+ routesToRip.add(assignment.solvedRoute);
1847
+ }
1848
+ }
1849
+ if (routesToRip.size > 0) {
1850
+ solvedRoute.requiredRip = true;
1851
+ for (const route of routesToRip) {
1852
+ this.ripSolvedRoute(route);
1853
+ }
1854
+ }
1855
+ for (const candidate of solvedRoute.path) {
1856
+ candidate.port.assignment = {
1857
+ solvedRoute,
1858
+ connection: this.currentConnection
1859
+ };
1860
+ if (!candidate.lastPort) continue;
1861
+ const regionPortAssignment = {
1862
+ regionPort1: candidate.lastPort,
1863
+ regionPort2: candidate.port,
1864
+ region: candidate.lastRegion,
1865
+ connection: this.currentConnection,
1866
+ solvedRoute
1867
+ };
1868
+ candidate.lastRegion.assignments?.push(regionPortAssignment);
1869
+ }
1870
+ this.solvedRoutes.push(solvedRoute);
1871
+ this.routeSolvedHook(solvedRoute);
1872
+ }
1873
+ /**
1874
+ * OPTIONALLY OVERRIDE THIS
1875
+ *
1876
+ * You can override this to perform actions after a route is solved, e.g.
1877
+ * you may want to detect if a solvedRoute.requiredRip is true, in which
1878
+ * case you might want to execute a "random rip" to avoid loops or check
1879
+ * if we've exceeded a maximum number of rips.
1880
+ *
1881
+ * You can also use this to shuffle unprocessed routes if a rip occurred, this
1882
+ * can also help avoid loops
1883
+ */
1884
+ routeSolvedHook(solvedRoute) {
1885
+ }
1886
+ ripSolvedRoute(solvedRoute) {
1887
+ for (const port of solvedRoute.path.map((candidate) => candidate.port)) {
1888
+ port.ripCount = (port.ripCount ?? 0) + 1;
1889
+ port.region1.assignments = port.region1.assignments?.filter(
1890
+ (a) => a.regionPort1 !== port && a.regionPort2 !== port
1891
+ );
1892
+ port.region2.assignments = port.region2.assignments?.filter(
1893
+ (a) => a.regionPort1 !== port && a.regionPort2 !== port
1894
+ );
1895
+ port.assignment = void 0;
1896
+ }
1897
+ this.solvedRoutes = this.solvedRoutes.filter((r) => r !== solvedRoute);
1898
+ this.unprocessedConnections.push(solvedRoute.connection);
1899
+ }
1900
+ beginNewConnection() {
1901
+ this.currentConnection = this.unprocessedConnections.shift();
1902
+ this.currentEndRegion = this.currentConnection.endRegion;
1903
+ this.candidateQueue = new PriorityQueue();
1904
+ this.visitedPointsForCurrentConnection.clear();
1905
+ for (const port of this.currentConnection.startRegion.ports) {
1906
+ this.candidateQueue.enqueue({
1907
+ port,
1908
+ g: 0,
1909
+ h: 0,
1910
+ f: 0,
1911
+ hops: 0,
1912
+ ripRequired: false,
1913
+ nextRegion: port.region1 === this.currentConnection.startRegion ? port.region2 : port.region1
1914
+ });
1915
+ }
1916
+ }
1917
+ _step() {
1918
+ let currentCandidate = this.candidateQueue.dequeue();
1919
+ if (!currentCandidate) {
1920
+ this.failed = true;
1921
+ this.error = "Ran out of candidates";
1922
+ return;
1923
+ }
1924
+ let visitedPointGScore = this.visitedPointsForCurrentConnection.get(currentCandidate.port.portId);
1925
+ while (true) {
1926
+ if (!currentCandidate) break;
1927
+ if (visitedPointGScore === void 0) break;
1928
+ if (currentCandidate.g < visitedPointGScore) break;
1929
+ currentCandidate = this.candidateQueue.dequeue();
1930
+ if (!currentCandidate) break;
1931
+ visitedPointGScore = this.visitedPointsForCurrentConnection.get(
1932
+ currentCandidate.port.portId
1933
+ );
1934
+ }
1935
+ if (!currentCandidate) {
1936
+ this.failed = true;
1937
+ this.error = "Ran out of candidates";
1938
+ return;
1939
+ }
1940
+ this.lastCandidate = currentCandidate;
1941
+ this.visitedPointsForCurrentConnection.set(
1942
+ currentCandidate.port.portId,
1943
+ currentCandidate.g
1944
+ );
1945
+ if (currentCandidate.nextRegion === this.currentEndRegion) {
1946
+ this.processSolvedRoute(currentCandidate);
1947
+ if (this.unprocessedConnections.length === 0) {
1948
+ this.solved = true;
1949
+ return;
1950
+ }
1951
+ this.beginNewConnection();
1952
+ return;
1953
+ }
1954
+ const nextCandidates = this.getNextCandidates(currentCandidate);
1955
+ for (const nextCandidate of nextCandidates) {
1956
+ this.candidateQueue.enqueue(nextCandidate);
1957
+ }
1958
+ }
1959
+ };
1960
+
1961
+ // lib/JumperGraphSolver/visualizeJumperGraph.ts
1962
+ var visualizeJumperGraph = (graph, options) => {
1963
+ const graphics = {
1964
+ arrows: [],
1965
+ circles: [],
1966
+ title: "Jumper Graph",
1967
+ lines: [],
1968
+ points: [],
1969
+ rects: [],
1970
+ texts: [],
1971
+ coordinateSystem: "cartesian"
1972
+ };
1973
+ for (const region of graph.regions) {
1974
+ const { bounds, isPad, isThroughJumper, isConnectionRegion } = region.d;
1975
+ const centerX = (bounds.minX + bounds.maxX) / 2;
1976
+ const centerY = (bounds.minY + bounds.maxY) / 2;
1977
+ const width = bounds.maxX - bounds.minX;
1978
+ const height = bounds.maxY - bounds.minY;
1979
+ let fill;
1980
+ if (isConnectionRegion) {
1981
+ fill = "rgba(255, 100, 255, 0.6)";
1982
+ } else if (isThroughJumper) {
1983
+ fill = "rgba(100, 200, 100, 0.5)";
1984
+ } else if (isPad) {
1985
+ fill = "rgba(255, 200, 100, 0.5)";
1986
+ } else {
1987
+ fill = "rgba(200, 200, 255, 0.1)";
1988
+ }
1989
+ graphics.rects.push({
1990
+ center: { x: centerX, y: centerY },
1991
+ width: width - 0.1,
1992
+ height: height - 0.1,
1993
+ fill
1994
+ });
1995
+ }
1996
+ if (!options?.hidePortPoints) {
1997
+ for (const port of graph.ports) {
1998
+ const r1Name = port.region1.regionId.split(":").pop() ?? port.region1.regionId;
1999
+ const r2Name = port.region2.regionId.split(":").pop() ?? port.region2.regionId;
2000
+ graphics.circles.push({
2001
+ center: { x: port.d.x, y: port.d.y },
2002
+ radius: 0.05,
2003
+ fill: "rgba(128, 128, 128, 0.5)",
2004
+ label: `${r1Name}-${r2Name}`
2005
+ });
2006
+ }
2007
+ }
2008
+ if (!options?.hideRegionPortLines) {
2009
+ for (const port of graph.ports) {
2010
+ const r1Center = {
2011
+ x: (port.region1.d.bounds.minX + port.region1.d.bounds.maxX) / 2,
2012
+ y: (port.region1.d.bounds.minY + port.region1.d.bounds.maxY) / 2
2013
+ };
2014
+ const r2Center = {
2015
+ x: (port.region2.d.bounds.minX + port.region2.d.bounds.maxX) / 2,
2016
+ y: (port.region2.d.bounds.minY + port.region2.d.bounds.maxY) / 2
2017
+ };
2018
+ graphics.lines.push({
2019
+ points: [r1Center, { x: port.d.x, y: port.d.y }, r2Center],
2020
+ strokeColor: "rgba(100, 100, 100, 0.3)"
2021
+ });
2022
+ }
2023
+ }
2024
+ if (options?.connections && !options?.hideConnectionLines) {
2025
+ for (const connection of options.connections) {
2026
+ const startRegion = connection.startRegion;
2027
+ const endRegion = connection.endRegion;
2028
+ const startCenter = {
2029
+ x: (startRegion.d.bounds.minX + startRegion.d.bounds.maxX) / 2,
2030
+ y: (startRegion.d.bounds.minY + startRegion.d.bounds.maxY) / 2
2031
+ };
2032
+ const endCenter = {
2033
+ x: (endRegion.d.bounds.minX + endRegion.d.bounds.maxX) / 2,
2034
+ y: (endRegion.d.bounds.minY + endRegion.d.bounds.maxY) / 2
2035
+ };
2036
+ const midX = (startCenter.x + endCenter.x) / 2;
2037
+ const midY = (startCenter.y + endCenter.y) / 2;
2038
+ graphics.lines.push({
2039
+ points: [startCenter, endCenter],
2040
+ strokeColor: "rgba(255, 50, 150, 0.8)",
2041
+ strokeDash: "3 3"
2042
+ });
2043
+ graphics.points.push({
2044
+ x: midX,
2045
+ y: midY,
2046
+ color: "rgba(200, 0, 100, 1)",
2047
+ label: connection.connectionId
2048
+ });
2049
+ }
2050
+ }
2051
+ return graphics;
2052
+ };
2053
+
2054
+ // lib/JumperGraphSolver/visualizeJumperGraphSolver.ts
2055
+ var getConnectionColor = (connectionId, alpha = 0.8) => {
2056
+ let hash = 0;
2057
+ for (let i = 0; i < connectionId.length; i++) {
2058
+ hash = connectionId.charCodeAt(i) * 17777 + ((hash << 5) - hash);
2059
+ }
2060
+ const hue = Math.abs(hash) % 360;
2061
+ return `hsla(${hue}, 70%, 50%, ${alpha})`;
2062
+ };
2063
+ var visualizeJumperGraphSolver = (solver) => {
2064
+ const jumperGraph = {
2065
+ regions: solver.graph.regions,
2066
+ ports: solver.graph.ports
2067
+ };
2068
+ const graphics = visualizeJumperGraph(jumperGraph, {
2069
+ connections: solver.connections,
2070
+ ...solver.iterations > 0 ? {
2071
+ hideRegionPortLines: true,
2072
+ hideConnectionLines: true,
2073
+ hidePortPoints: true
2074
+ } : {}
2075
+ });
2076
+ if (solver.currentConnection && !solver.solved) {
2077
+ const connectionColor = getConnectionColor(
2078
+ solver.currentConnection.connectionId
2079
+ );
2080
+ const startRegion = solver.currentConnection.startRegion;
2081
+ const endRegion = solver.currentConnection.endRegion;
2082
+ const startCenter = {
2083
+ x: (startRegion.d.bounds.minX + startRegion.d.bounds.maxX) / 2,
2084
+ y: (startRegion.d.bounds.minY + startRegion.d.bounds.maxY) / 2
2085
+ };
2086
+ const endCenter = {
2087
+ x: (endRegion.d.bounds.minX + endRegion.d.bounds.maxX) / 2,
2088
+ y: (endRegion.d.bounds.minY + endRegion.d.bounds.maxY) / 2
2089
+ };
2090
+ graphics.lines.push({
2091
+ points: [startCenter, endCenter],
2092
+ strokeColor: connectionColor,
2093
+ strokeDash: "10 5"
2094
+ });
2095
+ graphics.points.push({
2096
+ x: startCenter.x - 0.1,
2097
+ y: startCenter.y + 0.1,
2098
+ color: connectionColor,
2099
+ label: [solver.currentConnection.connectionId, "start"].join("\n")
2100
+ });
2101
+ graphics.points.push({
2102
+ x: endCenter.x - 0.1,
2103
+ y: endCenter.y + 0.1,
2104
+ color: connectionColor,
2105
+ label: [solver.currentConnection.connectionId, "end"].join("\n")
2106
+ });
2107
+ }
2108
+ for (const solvedRoute of solver.solvedRoutes) {
2109
+ const connectionColor = getConnectionColor(
2110
+ solvedRoute.connection.connectionId
2111
+ );
2112
+ const pathPoints = [];
2113
+ for (const candidate of solvedRoute.path) {
2114
+ const port = candidate.port;
2115
+ pathPoints.push({ x: port.d.x, y: port.d.y });
2116
+ }
2117
+ if (pathPoints.length > 0) {
2118
+ graphics.lines.push({
2119
+ points: pathPoints,
2120
+ strokeColor: connectionColor
2121
+ });
2122
+ }
2123
+ }
2124
+ const candidates = solver.candidateQueue.peekMany(10);
2125
+ for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
2126
+ const candidate = candidates[candidateIndex];
2127
+ const port = candidate.port;
2128
+ const isNext = candidateIndex === 0;
2129
+ graphics.points.push({
2130
+ x: port.d.x,
2131
+ y: port.d.y,
2132
+ color: isNext ? "green" : "rgba(128, 128, 128, 0.25)",
2133
+ label: [
2134
+ candidate.port.portId,
2135
+ `g: ${candidate.g.toFixed(2)}`,
2136
+ `h: ${candidate.h.toFixed(2)}`,
2137
+ `f: ${candidate.f.toFixed(2)}`
2138
+ ].join("\n")
2139
+ });
2140
+ }
2141
+ const nextCandidate = candidates[0];
2142
+ if (!solver.solved && nextCandidate && solver.currentConnection) {
2143
+ const connectionColor = getConnectionColor(
2144
+ solver.currentConnection.connectionId
2145
+ );
2146
+ const activePath = [];
2147
+ let cursor = nextCandidate;
2148
+ while (cursor) {
2149
+ const port = cursor.port;
2150
+ activePath.unshift({ x: port.d.x, y: port.d.y });
2151
+ cursor = cursor.parent;
2152
+ }
2153
+ if (activePath.length > 1) {
2154
+ graphics.lines.push({
2155
+ points: activePath,
2156
+ strokeColor: connectionColor
2157
+ });
2158
+ }
2159
+ }
2160
+ return graphics;
2161
+ };
2162
+
2163
+ // node_modules/@tscircuit/math-utils/dist/chunk-EFLPMB4J.js
2164
+ function distance(p1, p2) {
2165
+ const dx = p1.x - p2.x;
2166
+ const dy = p1.y - p2.y;
2167
+ return Math.sqrt(dx * dx + dy * dy);
2168
+ }
2169
+
2170
+ // lib/JumperGraphSolver/perimeterChordUtils.ts
2171
+ function perimeterT(p, xmin, xmax, ymin, ymax) {
2172
+ const W = xmax - xmin;
2173
+ const H = ymax - ymin;
2174
+ const eps = 1e-6;
2175
+ if (Math.abs(p.y - ymax) < eps) {
2176
+ return p.x - xmin;
2177
+ }
2178
+ if (Math.abs(p.x - xmax) < eps) {
2179
+ return W + (ymax - p.y);
2180
+ }
2181
+ if (Math.abs(p.y - ymin) < eps) {
2182
+ return W + H + (xmax - p.x);
2183
+ }
2184
+ if (Math.abs(p.x - xmin) < eps) {
2185
+ return 2 * W + H + (p.y - ymin);
2186
+ }
2187
+ const distTop = Math.abs(p.y - ymax);
2188
+ const distRight = Math.abs(p.x - xmax);
2189
+ const distBottom = Math.abs(p.y - ymin);
2190
+ const distLeft = Math.abs(p.x - xmin);
2191
+ const minDist = Math.min(distTop, distRight, distBottom, distLeft);
2192
+ if (minDist === distTop) {
2193
+ return Math.max(0, Math.min(W, p.x - xmin));
2194
+ }
2195
+ if (minDist === distRight) {
2196
+ return W + Math.max(0, Math.min(H, ymax - p.y));
2197
+ }
2198
+ if (minDist === distBottom) {
2199
+ return W + H + Math.max(0, Math.min(W, xmax - p.x));
2200
+ }
2201
+ return 2 * W + H + Math.max(0, Math.min(H, p.y - ymin));
2202
+ }
2203
+ function areCoincident(t1, t2, eps = 1e-6) {
2204
+ return Math.abs(t1 - t2) < eps;
2205
+ }
2206
+ function chordsCross(chord1, chord2) {
2207
+ const [a, b] = chord1[0] < chord1[1] ? chord1 : [chord1[1], chord1[0]];
2208
+ const [c, d] = chord2[0] < chord2[1] ? chord2 : [chord2[1], chord2[0]];
2209
+ if (areCoincident(a, c) || areCoincident(a, d) || areCoincident(b, c) || areCoincident(b, d)) {
2210
+ return false;
2211
+ }
2212
+ return a < c && c < b && b < d || c < a && a < d && d < b;
2213
+ }
2214
+
2215
+ // lib/JumperGraphSolver/computeDifferentNetCrossings.ts
2216
+ function computeDifferentNetCrossings(region, port1, port2) {
2217
+ const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
2218
+ const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
2219
+ const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
2220
+ const newChord = [t1, t2];
2221
+ let crossings = 0;
2222
+ const assignments = region.assignments ?? [];
2223
+ for (const assignment of assignments) {
2224
+ const existingT1 = perimeterT(
2225
+ assignment.regionPort1.d,
2226
+ xmin,
2227
+ xmax,
2228
+ ymin,
2229
+ ymax
2230
+ );
2231
+ const existingT2 = perimeterT(
2232
+ assignment.regionPort2.d,
2233
+ xmin,
2234
+ xmax,
2235
+ ymin,
2236
+ ymax
2237
+ );
2238
+ const existingChord = [existingT1, existingT2];
2239
+ if (chordsCross(newChord, existingChord)) {
2240
+ crossings++;
2241
+ }
2242
+ }
2243
+ return crossings;
2244
+ }
2245
+
2246
+ // lib/JumperGraphSolver/computeCrossingAssignments.ts
2247
+ function computeCrossingAssignments(region, port1, port2) {
2248
+ const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
2249
+ const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
2250
+ const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
2251
+ const newChord = [t1, t2];
2252
+ const crossingAssignments = [];
2253
+ const assignments = region.assignments ?? [];
2254
+ for (const assignment of assignments) {
2255
+ const existingT1 = perimeterT(
2256
+ assignment.regionPort1.d,
2257
+ xmin,
2258
+ xmax,
2259
+ ymin,
2260
+ ymax
2261
+ );
2262
+ const existingT2 = perimeterT(
2263
+ assignment.regionPort2.d,
2264
+ xmin,
2265
+ xmax,
2266
+ ymin,
2267
+ ymax
2268
+ );
2269
+ const existingChord = [existingT1, existingT2];
2270
+ if (chordsCross(newChord, existingChord)) {
2271
+ crossingAssignments.push(assignment);
2272
+ }
2273
+ }
2274
+ return crossingAssignments;
2275
+ }
2276
+
2277
+ // lib/JumperGraphSolver/JumperGraphSolver.ts
2278
+ var JumperGraphSolver = class extends HyperGraphSolver {
2279
+ UNIT_OF_COST = "distance";
2280
+ constructor(input) {
2281
+ super({
2282
+ ...input,
2283
+ greedyMultiplier: 1.2,
2284
+ rippingEnabled: true,
2285
+ ripCost: 40
2286
+ });
2287
+ this.MAX_ITERATIONS = 400 + input.inputConnections.length * 200;
2288
+ }
2289
+ estimateCostToEnd(port) {
2290
+ return distance(port.d, this.currentEndRegion.d.center);
2291
+ }
2292
+ getPortUsagePenalty(port) {
2293
+ return port.ripCount ?? 0;
2294
+ }
2295
+ computeIncreasedRegionCostIfPortsAreUsed(region, port1, port2) {
2296
+ return computeDifferentNetCrossings(region, port1, port2) * 6;
2297
+ }
2298
+ getRipsRequiredForPortUsage(region, port1, port2) {
2299
+ const crossingAssignments = computeCrossingAssignments(region, port1, port2);
2300
+ return crossingAssignments.filter(
2301
+ (a) => a.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId
2302
+ );
2303
+ }
2304
+ routeSolvedHook(solvedRoute) {
2305
+ }
2306
+ visualize() {
2307
+ return visualizeJumperGraphSolver(this);
2308
+ }
2309
+ };
2310
+ export {
2311
+ HyperGraphSolver,
2312
+ JumperGraphSolver,
2313
+ applyTransformToGraph,
2314
+ createGraphWithConnectionsFromBaseGraph,
2315
+ generateJumperGrid,
2316
+ generateJumperX4Grid,
2317
+ rotateGraph90Degrees
2318
+ };