@tscircuit/hypergraph 0.0.59 → 0.0.61

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +106 -7
  2. package/dist/index.js +1292 -502
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1532,6 +1532,7 @@ var convertSerializedHyperGraphToHyperGraph = (inputGraph) => {
1532
1532
  const { assignments: _, ...regionWithoutAssignments } = region;
1533
1533
  regionMap.set(region.regionId, {
1534
1534
  ...regionWithoutAssignments,
1535
+ d: regionWithoutAssignments.d ? structuredClone(regionWithoutAssignments.d) : regionWithoutAssignments.d,
1535
1536
  ports: [],
1536
1537
  assignments: void 0
1537
1538
  });
@@ -1704,27 +1705,124 @@ var PriorityQueue = class {
1704
1705
  }
1705
1706
  };
1706
1707
 
1708
+ // lib/solvedRoutes.ts
1709
+ var clearAssignmentsFromGraph = (graph) => {
1710
+ for (const region of graph.regions) {
1711
+ region.assignments = [];
1712
+ }
1713
+ for (const port of graph.ports) {
1714
+ port.assignment = void 0;
1715
+ }
1716
+ };
1717
+ var commitSolvedRoutes = ({
1718
+ graph,
1719
+ connections,
1720
+ solvedRoutes
1721
+ }) => {
1722
+ const portMap = new Map(graph.ports.map((port) => [port.portId, port]));
1723
+ const regionMap = new Map(
1724
+ graph.regions.map((region) => [region.regionId, region])
1725
+ );
1726
+ const connectionMap = new Map(
1727
+ connections.map((connection) => [connection.connectionId, connection])
1728
+ );
1729
+ const committedSolvedRoutes = solvedRoutes.map((solvedRoute) => {
1730
+ const path = [];
1731
+ for (const originalCandidate of solvedRoute.path) {
1732
+ const candidate = {
1733
+ port: portMap.get(originalCandidate.port.portId),
1734
+ g: originalCandidate.g,
1735
+ h: originalCandidate.h,
1736
+ f: originalCandidate.f,
1737
+ hops: originalCandidate.hops,
1738
+ ripRequired: originalCandidate.ripRequired
1739
+ };
1740
+ if (originalCandidate.lastPort) {
1741
+ candidate.lastPort = portMap.get(originalCandidate.lastPort.portId);
1742
+ }
1743
+ if (originalCandidate.lastRegion) {
1744
+ candidate.lastRegion = regionMap.get(
1745
+ originalCandidate.lastRegion.regionId
1746
+ );
1747
+ }
1748
+ if (originalCandidate.nextRegion) {
1749
+ candidate.nextRegion = regionMap.get(
1750
+ originalCandidate.nextRegion.regionId
1751
+ );
1752
+ }
1753
+ const parent = path[path.length - 1];
1754
+ if (parent) candidate.parent = parent;
1755
+ path.push(candidate);
1756
+ }
1757
+ return {
1758
+ path,
1759
+ connection: connectionMap.get(solvedRoute.connection.connectionId),
1760
+ requiredRip: solvedRoute.requiredRip
1761
+ };
1762
+ });
1763
+ clearAssignmentsFromGraph(graph);
1764
+ graph.solvedRoutes = committedSolvedRoutes;
1765
+ for (const solvedRoute of committedSolvedRoutes) {
1766
+ for (const candidate of solvedRoute.path) {
1767
+ candidate.port.assignment = {
1768
+ solvedRoute,
1769
+ connection: solvedRoute.connection
1770
+ };
1771
+ if (!candidate.lastPort || !candidate.lastRegion) continue;
1772
+ const regionPortAssignment = {
1773
+ regionPort1: candidate.lastPort,
1774
+ regionPort2: candidate.port,
1775
+ region: candidate.lastRegion,
1776
+ connection: solvedRoute.connection,
1777
+ solvedRoute
1778
+ };
1779
+ candidate.lastRegion.assignments ??= [];
1780
+ candidate.lastRegion.assignments.push(regionPortAssignment);
1781
+ }
1782
+ }
1783
+ return committedSolvedRoutes;
1784
+ };
1785
+
1707
1786
  // lib/HyperGraphSolver.ts
1708
1787
  var HyperGraphSolver = class extends BaseSolver {
1709
1788
  constructor(input) {
1710
1789
  super();
1711
1790
  this.input = input;
1712
1791
  this.graph = convertSerializedHyperGraphToHyperGraph(input.inputGraph);
1713
- for (const region of this.graph.regions) {
1714
- region.assignments = [];
1715
- }
1792
+ clearAssignmentsFromGraph(this.graph);
1716
1793
  this.connections = convertSerializedConnectionsToConnections(
1717
1794
  input.inputConnections,
1718
1795
  this.graph
1719
1796
  );
1797
+ if (input.inputSolvedRoutes) {
1798
+ this.solvedRoutes = commitSolvedRoutes({
1799
+ graph: this.graph,
1800
+ connections: this.connections,
1801
+ solvedRoutes: input.inputSolvedRoutes
1802
+ });
1803
+ } else {
1804
+ ;
1805
+ this.graph.solvedRoutes = this.solvedRoutes;
1806
+ }
1720
1807
  if (input.greedyMultiplier !== void 0)
1721
1808
  this.greedyMultiplier = input.greedyMultiplier;
1722
1809
  if (input.rippingEnabled !== void 0)
1723
1810
  this.rippingEnabled = input.rippingEnabled;
1724
1811
  if (input.ripCost !== void 0) this.ripCost = input.ripCost;
1725
- this.unprocessedConnections = [...this.connections];
1812
+ const preSolvedConnectionIds = new Set(
1813
+ this.solvedRoutes.map(
1814
+ (solvedRoute) => solvedRoute.connection.connectionId
1815
+ )
1816
+ );
1817
+ this.unprocessedConnections = this.connections.filter(
1818
+ (connection) => !preSolvedConnectionIds.has(connection.connectionId)
1819
+ );
1726
1820
  this.candidateQueue = new PriorityQueue();
1727
- this.beginNewConnection();
1821
+ if (this.unprocessedConnections.length === 0) {
1822
+ this.solved = true;
1823
+ } else {
1824
+ this.beginNewConnection();
1825
+ }
1728
1826
  }
1729
1827
  getSolverName() {
1730
1828
  return "HyperGraphSolver";
@@ -1749,9 +1847,13 @@ var HyperGraphSolver = class extends BaseSolver {
1749
1847
  ),
1750
1848
  greedyMultiplier: this.greedyMultiplier,
1751
1849
  rippingEnabled: this.rippingEnabled,
1752
- ripCost: this.ripCost
1850
+ ripCost: this.ripCost,
1851
+ inputSolvedRoutes: this.solvedRoutes
1753
1852
  };
1754
1853
  }
1854
+ getOutput() {
1855
+ return this.solvedRoutes;
1856
+ }
1755
1857
  computeH(candidate) {
1756
1858
  return this.estimateCostToEnd(candidate.port);
1757
1859
  }
@@ -2063,197 +2165,8 @@ var HyperGraphSolver = class extends BaseSolver {
2063
2165
  }
2064
2166
  };
2065
2167
 
2066
- // node_modules/transformation-matrix/src/applyToPoint.js
2067
- function applyToPoint(matrix2, point2) {
2068
- return Array.isArray(point2) ? [
2069
- matrix2.a * point2[0] + matrix2.c * point2[1] + matrix2.e,
2070
- matrix2.b * point2[0] + matrix2.d * point2[1] + matrix2.f
2071
- ] : {
2072
- x: matrix2.a * point2.x + matrix2.c * point2.y + matrix2.e,
2073
- y: matrix2.b * point2.x + matrix2.d * point2.y + matrix2.f
2074
- };
2075
- }
2076
-
2077
- // node_modules/transformation-matrix/src/utils.js
2078
- function isUndefined(val) {
2079
- return typeof val === "undefined";
2080
- }
2081
-
2082
- // node_modules/transformation-matrix/src/translate.js
2083
- function translate(tx, ty = 0) {
2084
- return {
2085
- a: 1,
2086
- c: 0,
2087
- e: tx,
2088
- b: 0,
2089
- d: 1,
2090
- f: ty
2091
- };
2092
- }
2093
-
2094
- // node_modules/transformation-matrix/src/transform.js
2095
- function transform(...matrices) {
2096
- matrices = Array.isArray(matrices[0]) ? matrices[0] : matrices;
2097
- const multiply = (m1, m2) => {
2098
- return {
2099
- a: m1.a * m2.a + m1.c * m2.b,
2100
- c: m1.a * m2.c + m1.c * m2.d,
2101
- e: m1.a * m2.e + m1.c * m2.f + m1.e,
2102
- b: m1.b * m2.a + m1.d * m2.b,
2103
- d: m1.b * m2.c + m1.d * m2.d,
2104
- f: m1.b * m2.e + m1.d * m2.f + m1.f
2105
- };
2106
- };
2107
- switch (matrices.length) {
2108
- case 0:
2109
- throw new Error("no matrices provided");
2110
- case 1:
2111
- return matrices[0];
2112
- case 2:
2113
- return multiply(matrices[0], matrices[1]);
2114
- default: {
2115
- const [m1, m2, ...rest] = matrices;
2116
- const m = multiply(m1, m2);
2117
- return transform(m, ...rest);
2118
- }
2119
- }
2120
- }
2121
- function compose(...matrices) {
2122
- return transform(...matrices);
2123
- }
2124
-
2125
- // node_modules/transformation-matrix/src/rotate.js
2126
- var { cos, sin, PI } = Math;
2127
- function rotate(angle, cx, cy) {
2128
- const cosAngle = cos(angle);
2129
- const sinAngle = sin(angle);
2130
- const rotationMatrix = {
2131
- a: cosAngle,
2132
- c: -sinAngle,
2133
- e: 0,
2134
- b: sinAngle,
2135
- d: cosAngle,
2136
- f: 0
2137
- };
2138
- if (isUndefined(cx) || isUndefined(cy)) {
2139
- return rotationMatrix;
2140
- }
2141
- return transform([
2142
- translate(cx, cy),
2143
- rotationMatrix,
2144
- translate(-cx, -cy)
2145
- ]);
2146
- }
2147
-
2148
- // node_modules/transformation-matrix/src/skew.js
2149
- var { tan } = Math;
2150
-
2151
- // lib/JumperGraphSolver/jumper-graph-generator/calculateGraphBounds.ts
2152
- var calculateGraphBounds = (regions) => {
2153
- let minX = Number.POSITIVE_INFINITY;
2154
- let maxX = Number.NEGATIVE_INFINITY;
2155
- let minY = Number.POSITIVE_INFINITY;
2156
- let maxY = Number.NEGATIVE_INFINITY;
2157
- for (const region of regions) {
2158
- const { bounds } = region.d;
2159
- minX = Math.min(minX, bounds.minX);
2160
- maxX = Math.max(maxX, bounds.maxX);
2161
- minY = Math.min(minY, bounds.minY);
2162
- maxY = Math.max(maxY, bounds.maxY);
2163
- }
2164
- return { minX, maxX, minY, maxY };
2165
- };
2166
-
2167
- // lib/JumperGraphSolver/geometry/getBoundsCenter.ts
2168
- var computeBoundsCenter = (bounds) => {
2169
- return {
2170
- x: (bounds.minX + bounds.maxX) / 2,
2171
- y: (bounds.minY + bounds.maxY) / 2
2172
- };
2173
- };
2174
-
2175
- // lib/JumperGraphSolver/geometry/applyTransformToGraph.ts
2176
- var applyTransformToGraph = (graph, matrix2) => {
2177
- const transformedRegions = graph.regions.map((region) => {
2178
- const { bounds, center, ...rest } = region.d;
2179
- const corners = [
2180
- { x: bounds.minX, y: bounds.minY },
2181
- { x: bounds.maxX, y: bounds.minY },
2182
- { x: bounds.minX, y: bounds.maxY },
2183
- { x: bounds.maxX, y: bounds.maxY }
2184
- ].map((corner) => applyToPoint(matrix2, corner));
2185
- const newBounds = {
2186
- minX: Math.min(...corners.map((c) => c.x)),
2187
- maxX: Math.max(...corners.map((c) => c.x)),
2188
- minY: Math.min(...corners.map((c) => c.y)),
2189
- maxY: Math.max(...corners.map((c) => c.y))
2190
- };
2191
- const newCenter = applyToPoint(matrix2, center);
2192
- return {
2193
- ...region,
2194
- // Clear ports array - will be rebuilt with new port objects
2195
- ports: [],
2196
- d: {
2197
- ...rest,
2198
- bounds: newBounds,
2199
- center: newCenter
2200
- }
2201
- };
2202
- });
2203
- const regionMap = /* @__PURE__ */ new Map();
2204
- for (let i = 0; i < graph.regions.length; i++) {
2205
- regionMap.set(graph.regions[i], transformedRegions[i]);
2206
- }
2207
- const transformedPorts = graph.ports.map((port) => {
2208
- const newPosition = applyToPoint(matrix2, port.d);
2209
- const newRegion1 = regionMap.get(port.region1);
2210
- const newRegion2 = regionMap.get(port.region2);
2211
- const newPort = {
2212
- ...port,
2213
- region1: newRegion1,
2214
- region2: newRegion2,
2215
- d: newPosition
2216
- };
2217
- newRegion1.ports.push(newPort);
2218
- newRegion2.ports.push(newPort);
2219
- return newPort;
2220
- });
2221
- const transformedJumperLocations = graph.jumperLocations?.map((loc) => {
2222
- const newCenter = applyToPoint(matrix2, loc.center);
2223
- const newPadRegions = loc.padRegions.map(
2224
- (region) => regionMap.get(region)
2225
- );
2226
- const unitX = applyToPoint(matrix2, { x: 1, y: 0 });
2227
- const origin = applyToPoint(matrix2, { x: 0, y: 0 });
2228
- const dx = unitX.x - origin.x;
2229
- const dy = unitX.y - origin.y;
2230
- const isRotated90 = Math.abs(dy) > Math.abs(dx);
2231
- const newOrientation = isRotated90 ? loc.orientation === "horizontal" ? "vertical" : "horizontal" : loc.orientation;
2232
- return {
2233
- center: newCenter,
2234
- orientation: newOrientation,
2235
- padRegions: newPadRegions
2236
- };
2237
- });
2238
- return {
2239
- regions: transformedRegions,
2240
- ports: transformedPorts,
2241
- ...transformedJumperLocations && {
2242
- jumperLocations: transformedJumperLocations
2243
- }
2244
- };
2245
- };
2246
- var rotateGraph90Degrees = (graph) => {
2247
- const bounds = calculateGraphBounds(graph.regions);
2248
- const center = computeBoundsCenter(bounds);
2249
- const matrix2 = compose(
2250
- translate(center.x, center.y),
2251
- rotate(-Math.PI / 2),
2252
- // -90 degrees (clockwise)
2253
- translate(-center.x, -center.y)
2254
- );
2255
- return applyTransformToGraph(graph, matrix2);
2256
- };
2168
+ // lib/HyperGraphSectionOptimizer/HyperGraphSectionOptimizer.ts
2169
+ import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
2257
2170
 
2258
2171
  // lib/JumperGraphSolver/perimeterChordUtils.ts
2259
2172
  function clamp2(value, min, max) {
@@ -2414,24 +2327,1119 @@ function chordsCross(chord1, chord2, perimeter) {
2414
2327
  return a < c && c < b && b < d || c < a && a < d && d < b;
2415
2328
  }
2416
2329
 
2417
- // lib/JumperGraphSolver/computeCrossingAssignments.ts
2418
- function computeCrossingAssignments(region, port1, port2) {
2419
- const perimeter = getRegionPerimeter(region);
2420
- const t1 = getPortPerimeterTInRegion(port1, region);
2421
- const t2 = getPortPerimeterTInRegion(port2, region);
2422
- const newChord = [t1, t2];
2423
- const crossingAssignments = [];
2424
- const assignments = region.assignments ?? [];
2425
- for (const assignment of assignments) {
2426
- const existingT1 = getPortPerimeterTInRegion(
2427
- assignment.regionPort1,
2428
- region
2429
- );
2430
- const existingT2 = getPortPerimeterTInRegion(
2431
- assignment.regionPort2,
2432
- region
2433
- );
2434
- const existingChord = [existingT1, existingT2];
2330
+ // lib/JumperGraphSolver/jumper-graph-generator/calculateGraphBounds.ts
2331
+ var calculateGraphBounds = (regions) => {
2332
+ let minX = Number.POSITIVE_INFINITY;
2333
+ let maxX = Number.NEGATIVE_INFINITY;
2334
+ let minY = Number.POSITIVE_INFINITY;
2335
+ let maxY = Number.NEGATIVE_INFINITY;
2336
+ for (const region of regions) {
2337
+ const { bounds } = region.d;
2338
+ minX = Math.min(minX, bounds.minX);
2339
+ maxX = Math.max(maxX, bounds.maxX);
2340
+ minY = Math.min(minY, bounds.minY);
2341
+ maxY = Math.max(maxY, bounds.maxY);
2342
+ }
2343
+ return { minX, maxX, minY, maxY };
2344
+ };
2345
+
2346
+ // lib/JumperGraphSolver/jumper-graph-generator/createConnectionPort.ts
2347
+ var createConnectionPort = (portId, connectionRegion, boundaryRegion, portPosition) => {
2348
+ const port = {
2349
+ portId,
2350
+ region1: connectionRegion,
2351
+ region2: boundaryRegion,
2352
+ d: { x: portPosition.x, y: portPosition.y }
2353
+ };
2354
+ connectionRegion.ports.push(port);
2355
+ boundaryRegion.ports.push(port);
2356
+ return port;
2357
+ };
2358
+
2359
+ // lib/JumperGraphSolver/jumper-graph-generator/createConnectionRegion.ts
2360
+ var CONNECTION_REGION_SIZE = 0.4;
2361
+ var createConnectionRegion = (regionId, x, y) => {
2362
+ const halfSize = CONNECTION_REGION_SIZE / 2;
2363
+ return {
2364
+ regionId,
2365
+ ports: [],
2366
+ d: {
2367
+ bounds: {
2368
+ minX: x - halfSize,
2369
+ maxX: x + halfSize,
2370
+ minY: y - halfSize,
2371
+ maxY: y + halfSize
2372
+ },
2373
+ center: { x, y },
2374
+ isPad: false,
2375
+ isConnectionRegion: true
2376
+ }
2377
+ };
2378
+ };
2379
+
2380
+ // lib/JumperGraphSolver/jumper-graph-generator/findBoundaryRegion.ts
2381
+ var EPS = 0.01;
2382
+ var clamp3 = (value, min, max) => {
2383
+ return Math.max(min, Math.min(max, value));
2384
+ };
2385
+ var getBoundarySidesForPoint = (x, y, graphBounds) => {
2386
+ const sides = [];
2387
+ if (Math.abs(x - graphBounds.minX) < EPS) sides.push("left");
2388
+ if (Math.abs(x - graphBounds.maxX) < EPS) sides.push("right");
2389
+ if (Math.abs(y - graphBounds.maxY) < EPS) sides.push("top");
2390
+ if (Math.abs(y - graphBounds.minY) < EPS) sides.push("bottom");
2391
+ return sides;
2392
+ };
2393
+ var isPointOnSide = (p, side, b) => {
2394
+ if (side === "left") return Math.abs(p.x - b.minX) < EPS;
2395
+ if (side === "right") return Math.abs(p.x - b.maxX) < EPS;
2396
+ if (side === "top") return Math.abs(p.y - b.maxY) < EPS;
2397
+ return Math.abs(p.y - b.minY) < EPS;
2398
+ };
2399
+ var projectToSegment2 = (x, y, a, b) => {
2400
+ const abx = b.x - a.x;
2401
+ const aby = b.y - a.y;
2402
+ const apx = x - a.x;
2403
+ const apy = y - a.y;
2404
+ const ab2 = abx * abx + aby * aby;
2405
+ const t = ab2 > 0 ? clamp3((apx * abx + apy * aby) / ab2, 0, 1) : 0;
2406
+ const px = a.x + t * abx;
2407
+ const py = a.y + t * aby;
2408
+ const dx = x - px;
2409
+ const dy = y - py;
2410
+ return {
2411
+ x: px,
2412
+ y: py,
2413
+ d2: dx * dx + dy * dy
2414
+ };
2415
+ };
2416
+ var getRegionBoundaryProjection = (x, y, region, graphBounds, preferredSides) => {
2417
+ const polygon2 = region.d.polygon;
2418
+ if (polygon2 && polygon2.length >= 3) {
2419
+ const sideSet = new Set(preferredSides);
2420
+ let best2 = null;
2421
+ for (let i = 0; i < polygon2.length; i++) {
2422
+ const a = polygon2[i];
2423
+ const b = polygon2[(i + 1) % polygon2.length];
2424
+ if (preferredSides.length > 0) {
2425
+ const edgeOnPreferredSide = preferredSides.some(
2426
+ (side) => isPointOnSide(a, side, graphBounds) && isPointOnSide(b, side, graphBounds) && sideSet.has(side)
2427
+ );
2428
+ if (!edgeOnPreferredSide) continue;
2429
+ }
2430
+ const p = projectToSegment2(x, y, a, b);
2431
+ if (!best2 || p.d2 < best2.d2) {
2432
+ best2 = p;
2433
+ }
2434
+ }
2435
+ if (best2) return best2;
2436
+ }
2437
+ const bounds = region.d.bounds;
2438
+ const sideCandidates = [];
2439
+ if (preferredSides.length > 0) {
2440
+ for (const side of preferredSides) {
2441
+ if (side === "left") {
2442
+ sideCandidates.push({
2443
+ side,
2444
+ x: bounds.minX,
2445
+ y: clamp3(y, bounds.minY, bounds.maxY)
2446
+ });
2447
+ } else if (side === "right") {
2448
+ sideCandidates.push({
2449
+ side,
2450
+ x: bounds.maxX,
2451
+ y: clamp3(y, bounds.minY, bounds.maxY)
2452
+ });
2453
+ } else if (side === "top") {
2454
+ sideCandidates.push({
2455
+ side,
2456
+ x: clamp3(x, bounds.minX, bounds.maxX),
2457
+ y: bounds.maxY
2458
+ });
2459
+ } else {
2460
+ sideCandidates.push({
2461
+ side,
2462
+ x: clamp3(x, bounds.minX, bounds.maxX),
2463
+ y: bounds.minY
2464
+ });
2465
+ }
2466
+ }
2467
+ }
2468
+ if (sideCandidates.length === 0) {
2469
+ sideCandidates.push(
2470
+ { side: "left", x: bounds.minX, y: clamp3(y, bounds.minY, bounds.maxY) },
2471
+ {
2472
+ side: "right",
2473
+ x: bounds.maxX,
2474
+ y: clamp3(y, bounds.minY, bounds.maxY)
2475
+ },
2476
+ { side: "top", x: clamp3(x, bounds.minX, bounds.maxX), y: bounds.maxY },
2477
+ {
2478
+ side: "bottom",
2479
+ x: clamp3(x, bounds.minX, bounds.maxX),
2480
+ y: bounds.minY
2481
+ }
2482
+ );
2483
+ }
2484
+ let best = null;
2485
+ for (const c of sideCandidates) {
2486
+ if (preferredSides.length > 0 && !preferredSides.includes(c.side)) continue;
2487
+ const dx = x - c.x;
2488
+ const dy = y - c.y;
2489
+ const d2 = dx * dx + dy * dy;
2490
+ if (!best || d2 < best.d2) {
2491
+ best = { x: c.x, y: c.y, d2 };
2492
+ }
2493
+ }
2494
+ return best;
2495
+ };
2496
+ var findBoundaryRegion = (x, y, regions, graphBounds) => {
2497
+ const preferredSides = getBoundarySidesForPoint(x, y, graphBounds);
2498
+ let closestRegion = null;
2499
+ let closestDistance = Number.POSITIVE_INFINITY;
2500
+ let closestPortPosition = { x, y };
2501
+ for (const region of regions) {
2502
+ if (region.d.isPad || region.d.isThroughJumper) continue;
2503
+ const bounds = region.d.bounds;
2504
+ 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;
2505
+ if (!isOuterRegion) continue;
2506
+ const projection = getRegionBoundaryProjection(
2507
+ x,
2508
+ y,
2509
+ region,
2510
+ graphBounds,
2511
+ preferredSides
2512
+ );
2513
+ if (!projection) continue;
2514
+ const dist = Math.sqrt(projection.d2);
2515
+ if (dist < closestDistance) {
2516
+ closestDistance = dist;
2517
+ closestRegion = region;
2518
+ closestPortPosition = { x: projection.x, y: projection.y };
2519
+ }
2520
+ }
2521
+ if (closestRegion) {
2522
+ return { region: closestRegion, portPosition: closestPortPosition };
2523
+ }
2524
+ return null;
2525
+ };
2526
+
2527
+ // lib/JumperGraphSolver/jumper-graph-generator/createGraphWithConnectionsFromBaseGraph.ts
2528
+ var createGraphWithConnectionsFromBaseGraph = (baseGraph, xyConnections) => {
2529
+ const regions = [...baseGraph.regions];
2530
+ const ports = [...baseGraph.ports];
2531
+ const connections = [];
2532
+ const graphBounds = calculateGraphBounds(baseGraph.regions);
2533
+ for (const xyConn of xyConnections) {
2534
+ const { start, end, connectionId } = xyConn;
2535
+ const startRegion = createConnectionRegion(
2536
+ `conn:${connectionId}:start`,
2537
+ start.x,
2538
+ start.y
2539
+ );
2540
+ regions.push(startRegion);
2541
+ const endRegion = createConnectionRegion(
2542
+ `conn:${connectionId}:end`,
2543
+ end.x,
2544
+ end.y
2545
+ );
2546
+ regions.push(endRegion);
2547
+ const startBoundary = findBoundaryRegion(
2548
+ start.x,
2549
+ start.y,
2550
+ baseGraph.regions,
2551
+ graphBounds
2552
+ );
2553
+ if (startBoundary) {
2554
+ const startPort = createConnectionPort(
2555
+ `conn:${connectionId}:start-port`,
2556
+ startRegion,
2557
+ startBoundary.region,
2558
+ startBoundary.portPosition
2559
+ );
2560
+ ports.push(startPort);
2561
+ }
2562
+ const endBoundary = findBoundaryRegion(
2563
+ end.x,
2564
+ end.y,
2565
+ baseGraph.regions,
2566
+ graphBounds
2567
+ );
2568
+ if (endBoundary) {
2569
+ const endPort = createConnectionPort(
2570
+ `conn:${connectionId}:end-port`,
2571
+ endRegion,
2572
+ endBoundary.region,
2573
+ endBoundary.portPosition
2574
+ );
2575
+ ports.push(endPort);
2576
+ }
2577
+ const connection = {
2578
+ connectionId,
2579
+ mutuallyConnectedNetworkId: connectionId,
2580
+ startRegion,
2581
+ endRegion
2582
+ };
2583
+ connections.push(connection);
2584
+ }
2585
+ return {
2586
+ regions,
2587
+ ports,
2588
+ connections
2589
+ };
2590
+ };
2591
+
2592
+ // lib/JumperGraphSolver/jumper-graph-generator/createProblemFromBaseGraph.ts
2593
+ var createSeededRandom = (seed) => {
2594
+ let state = seed;
2595
+ return () => {
2596
+ state = state * 1664525 + 1013904223 >>> 0;
2597
+ return state / 4294967295;
2598
+ };
2599
+ };
2600
+
2601
+ // lib/HyperGraphSectionOptimizer/routes/previewSectionReplacement.ts
2602
+ var previewSectionReplacement = (input) => {
2603
+ const { solvedRoutes, section, replacementSolvedRoutes } = input;
2604
+ const replacementByConnectionId = new Map(
2605
+ replacementSolvedRoutes.map((route) => [
2606
+ route.connection.connectionId,
2607
+ route
2608
+ ])
2609
+ );
2610
+ return solvedRoutes.map((solvedRoute) => {
2611
+ const sectionRoute = section.sectionRoutes.find(
2612
+ (route) => route.globalConnection.connectionId === solvedRoute.connection.connectionId
2613
+ );
2614
+ if (!sectionRoute) return solvedRoute;
2615
+ const replacementSolvedRoute = replacementByConnectionId.get(
2616
+ solvedRoute.connection.connectionId
2617
+ );
2618
+ if (!replacementSolvedRoute) return solvedRoute;
2619
+ const path = [
2620
+ ...solvedRoute.path.slice(0, sectionRoute.sectionStartIndex),
2621
+ ...replacementSolvedRoute.path,
2622
+ ...solvedRoute.path.slice(sectionRoute.sectionEndIndex + 1)
2623
+ ];
2624
+ const copiedPath = path.map((candidate) => ({
2625
+ port: candidate.port,
2626
+ g: candidate.g,
2627
+ h: candidate.h,
2628
+ f: candidate.f,
2629
+ hops: candidate.hops,
2630
+ ripRequired: candidate.ripRequired,
2631
+ lastPort: candidate.lastPort,
2632
+ lastRegion: candidate.lastRegion,
2633
+ nextRegion: candidate.nextRegion
2634
+ }));
2635
+ for (let i = 0; i < copiedPath.length; i++) {
2636
+ copiedPath[i].parent = i > 0 ? copiedPath[i - 1] : void 0;
2637
+ }
2638
+ return {
2639
+ connection: solvedRoute.connection,
2640
+ path: copiedPath,
2641
+ requiredRip: solvedRoute.requiredRip || replacementSolvedRoute.requiredRip
2642
+ };
2643
+ });
2644
+ };
2645
+
2646
+ // lib/HyperGraphSectionOptimizer/getOrCreateBoundaryRegion.ts
2647
+ var getOrCreateBoundaryRegion = ({
2648
+ port,
2649
+ boundaryRegionMap
2650
+ }) => {
2651
+ let boundaryRegion = boundaryRegionMap.get(port.portId);
2652
+ if (!boundaryRegion) {
2653
+ const x = typeof port.d?.x === "number" ? port.d.x : 0;
2654
+ const y = typeof port.d?.y === "number" ? port.d.y : 0;
2655
+ boundaryRegion = {
2656
+ regionId: `__section_boundary__${port.portId}`,
2657
+ ports: [],
2658
+ d: {
2659
+ isBoundaryRegion: true,
2660
+ boundaryPortId: port.portId,
2661
+ isPad: false,
2662
+ isThroughJumper: false,
2663
+ isConnectionRegion: true,
2664
+ center: { x, y },
2665
+ bounds: {
2666
+ minX: x - 0.05,
2667
+ maxX: x + 0.05,
2668
+ minY: y - 0.05,
2669
+ maxY: y + 0.05
2670
+ }
2671
+ },
2672
+ assignments: []
2673
+ };
2674
+ boundaryRegionMap.set(port.portId, boundaryRegion);
2675
+ }
2676
+ return boundaryRegion;
2677
+ };
2678
+
2679
+ // lib/HyperGraphSectionOptimizer/routes/sliceSolvedRouteIntoLocalSection.ts
2680
+ var sliceSolvedRouteIntoLocalSection = (input) => {
2681
+ const { sectionRoute, graph } = input;
2682
+ const localPortIds = new Set(graph.ports.map((port) => port.portId));
2683
+ const originalLocalPath = sectionRoute.globalRoute.path.slice(sectionRoute.sectionStartIndex, sectionRoute.sectionEndIndex + 1).filter((candidate) => localPortIds.has(candidate.port.portId));
2684
+ const path = [];
2685
+ let currentRegion = sectionRoute.sectionConnection.startRegion;
2686
+ for (let index = 0; index < originalLocalPath.length; index++) {
2687
+ const originalCandidate = originalLocalPath[index];
2688
+ const port = graph.ports.find(
2689
+ (candidatePort) => candidatePort.portId === originalCandidate.port.portId
2690
+ );
2691
+ const nextRegion = port.region1 === currentRegion ? port.region2 : port.region1;
2692
+ path.push({
2693
+ port,
2694
+ g: originalCandidate.g,
2695
+ h: originalCandidate.h,
2696
+ f: originalCandidate.f,
2697
+ hops: index,
2698
+ ripRequired: originalCandidate.ripRequired,
2699
+ parent: index > 0 ? path[index - 1] : void 0,
2700
+ lastPort: index > 0 ? path[index - 1].port : void 0,
2701
+ lastRegion: index > 0 ? currentRegion : void 0,
2702
+ nextRegion
2703
+ });
2704
+ currentRegion = nextRegion;
2705
+ }
2706
+ return {
2707
+ connection: sectionRoute.sectionConnection,
2708
+ path,
2709
+ requiredRip: sectionRoute.globalRoute.requiredRip
2710
+ };
2711
+ };
2712
+
2713
+ // lib/HyperGraphSectionOptimizer/sections/getRouteSectionSpan.ts
2714
+ var getRouteSectionSpan = (route, sectionRegionIds) => {
2715
+ let startIndex = -1;
2716
+ let endIndex = -1;
2717
+ for (let i = 0; i < route.path.length; i++) {
2718
+ const candidate = route.path[i];
2719
+ const touchesSection = candidate.lastRegion && sectionRegionIds.has(candidate.lastRegion.regionId) || candidate.nextRegion && sectionRegionIds.has(candidate.nextRegion.regionId);
2720
+ if (!touchesSection) continue;
2721
+ if (startIndex === -1) startIndex = i;
2722
+ endIndex = i;
2723
+ }
2724
+ if (startIndex === -1) return null;
2725
+ return { startIndex, endIndex };
2726
+ };
2727
+
2728
+ // lib/HyperGraphSectionOptimizer/sections/getSectionRegionIds.ts
2729
+ var getSectionRegionIds = ({
2730
+ centralRegion,
2731
+ expansionHopsFromCentralRegion
2732
+ }) => {
2733
+ const sectionRegionIds = /* @__PURE__ */ new Set([centralRegion.regionId]);
2734
+ const queue = [
2735
+ { region: centralRegion, hops: 0 }
2736
+ ];
2737
+ while (queue.length > 0) {
2738
+ const { region, hops } = queue.shift();
2739
+ if (hops >= expansionHopsFromCentralRegion + 1) continue;
2740
+ for (const port of region.ports) {
2741
+ const nextRegion = port.region1 === region ? port.region2 : port.region1;
2742
+ if (sectionRegionIds.has(nextRegion.regionId)) continue;
2743
+ sectionRegionIds.add(nextRegion.regionId);
2744
+ queue.push({ region: nextRegion, hops: hops + 1 });
2745
+ }
2746
+ }
2747
+ return sectionRegionIds;
2748
+ };
2749
+
2750
+ // lib/HyperGraphSectionOptimizer/sections/getSectionOfHyperGraphAsHyperGraph.ts
2751
+ var getSectionOfHyperGraphAsHyperGraph = (input) => {
2752
+ const { graph, solvedRoutes, centralRegion, expansionHopsFromCentralRegion } = input;
2753
+ const sectionRegionIds = getSectionRegionIds({
2754
+ graph,
2755
+ centralRegion,
2756
+ expansionHopsFromCentralRegion
2757
+ });
2758
+ const clonedRegionMap = /* @__PURE__ */ new Map();
2759
+ const boundaryRegionMap = /* @__PURE__ */ new Map();
2760
+ const clonedPorts = [];
2761
+ for (const region of graph.regions) {
2762
+ if (!sectionRegionIds.has(region.regionId)) continue;
2763
+ clonedRegionMap.set(region.regionId, {
2764
+ regionId: region.regionId,
2765
+ ports: [],
2766
+ d: region.d ? structuredClone(region.d) : region.d,
2767
+ assignments: []
2768
+ });
2769
+ }
2770
+ for (const port of graph.ports) {
2771
+ const region1InSection = sectionRegionIds.has(port.region1.regionId);
2772
+ const region2InSection = sectionRegionIds.has(port.region2.regionId);
2773
+ if (!region1InSection && !region2InSection) continue;
2774
+ if (region1InSection && region2InSection) {
2775
+ const clonedPort2 = {
2776
+ portId: port.portId,
2777
+ region1: clonedRegionMap.get(port.region1.regionId),
2778
+ region2: clonedRegionMap.get(port.region2.regionId),
2779
+ d: port.d
2780
+ };
2781
+ clonedPort2.region1.ports.push(clonedPort2);
2782
+ clonedPort2.region2.ports.push(clonedPort2);
2783
+ clonedPorts.push(clonedPort2);
2784
+ continue;
2785
+ }
2786
+ const insideRegion = region1InSection ? port.region1 : port.region2;
2787
+ const boundaryRegion = getOrCreateBoundaryRegion({
2788
+ port,
2789
+ boundaryRegionMap
2790
+ });
2791
+ const clonedPort = {
2792
+ portId: port.portId,
2793
+ region1: clonedRegionMap.get(insideRegion.regionId),
2794
+ region2: boundaryRegion,
2795
+ d: port.d
2796
+ };
2797
+ clonedPort.region1.ports.push(clonedPort);
2798
+ clonedPort.region2.ports.push(clonedPort);
2799
+ clonedPorts.push(clonedPort);
2800
+ }
2801
+ const sectionGraph = {
2802
+ regions: [...clonedRegionMap.values(), ...boundaryRegionMap.values()],
2803
+ ports: clonedPorts
2804
+ };
2805
+ const sectionRoutes = [];
2806
+ const sectionConnections = [];
2807
+ const sectionRegionMap = new Map(
2808
+ sectionGraph.regions.map((region) => [region.regionId, region])
2809
+ );
2810
+ for (const solvedRoute of solvedRoutes) {
2811
+ const span = getRouteSectionSpan(solvedRoute, sectionRegionIds);
2812
+ if (!span) continue;
2813
+ const startCandidate = solvedRoute.path[span.startIndex];
2814
+ const endCandidate = solvedRoute.path[span.endIndex];
2815
+ let startRegionId;
2816
+ let startRegion;
2817
+ if (sectionRegionIds.has(solvedRoute.connection.startRegion.regionId)) {
2818
+ startRegionId = solvedRoute.connection.startRegion.regionId;
2819
+ startRegion = sectionRegionMap.get(startRegionId);
2820
+ } else {
2821
+ const boundaryRegion = getOrCreateBoundaryRegion({
2822
+ port: startCandidate.port,
2823
+ boundaryRegionMap
2824
+ });
2825
+ startRegionId = boundaryRegion.regionId;
2826
+ startRegion = boundaryRegion;
2827
+ if (!sectionRegionMap.has(startRegionId)) {
2828
+ sectionRegionMap.set(startRegionId, boundaryRegion);
2829
+ }
2830
+ }
2831
+ let endRegionId;
2832
+ let endRegion;
2833
+ if (sectionRegionIds.has(solvedRoute.connection.endRegion.regionId)) {
2834
+ endRegionId = solvedRoute.connection.endRegion.regionId;
2835
+ endRegion = sectionRegionMap.get(endRegionId);
2836
+ } else {
2837
+ const boundaryRegion = getOrCreateBoundaryRegion({
2838
+ port: endCandidate.port,
2839
+ boundaryRegionMap
2840
+ });
2841
+ endRegionId = boundaryRegion.regionId;
2842
+ endRegion = boundaryRegion;
2843
+ if (!sectionRegionMap.has(endRegionId)) {
2844
+ sectionRegionMap.set(endRegionId, boundaryRegion);
2845
+ }
2846
+ }
2847
+ if (!startRegion) {
2848
+ console.error(
2849
+ `[getSectionOfHyperGraphAsHyperGraph] CRITICAL ERROR: startRegion not found!`
2850
+ );
2851
+ console.error(` Looking for: ${startRegionId}`);
2852
+ console.error(
2853
+ ` Route connection: ${solvedRoute.connection.connectionId}`
2854
+ );
2855
+ console.error(
2856
+ ` Original startRegion: ${solvedRoute.connection.startRegion.regionId}`
2857
+ );
2858
+ console.error(` startCandidate port: ${startCandidate.port.portId}`);
2859
+ console.error(
2860
+ ` Available regions in sectionRegionMap:`,
2861
+ Array.from(sectionRegionMap.keys())
2862
+ );
2863
+ throw new Error(
2864
+ `startRegion ${startRegionId} not found in sectionRegionMap`
2865
+ );
2866
+ }
2867
+ if (!endRegion) {
2868
+ console.error(
2869
+ `[getSectionOfHyperGraphAsHyperGraph] CRITICAL ERROR: endRegion not found!`
2870
+ );
2871
+ console.error(` Looking for: ${endRegionId}`);
2872
+ console.error(
2873
+ ` Route connection: ${solvedRoute.connection.connectionId}`
2874
+ );
2875
+ console.error(
2876
+ ` Original endRegion: ${solvedRoute.connection.endRegion.regionId}`
2877
+ );
2878
+ console.error(` endCandidate port: ${endCandidate.port.portId}`);
2879
+ console.error(
2880
+ ` Available regions in sectionRegionMap:`,
2881
+ Array.from(sectionRegionMap.keys())
2882
+ );
2883
+ throw new Error(`endRegion ${endRegionId} not found in sectionRegionMap`);
2884
+ }
2885
+ const sectionConnection = {
2886
+ connectionId: solvedRoute.connection.connectionId,
2887
+ mutuallyConnectedNetworkId: solvedRoute.connection.mutuallyConnectedNetworkId,
2888
+ startRegion,
2889
+ endRegion
2890
+ };
2891
+ const rawPath = solvedRoute.path.slice(span.startIndex, span.endIndex + 1);
2892
+ const sectionRouteBase = {
2893
+ globalRoute: solvedRoute,
2894
+ globalConnection: solvedRoute.connection,
2895
+ sectionConnection,
2896
+ sectionStartIndex: span.startIndex,
2897
+ sectionEndIndex: span.endIndex
2898
+ };
2899
+ sectionRoutes.push({
2900
+ ...sectionRouteBase,
2901
+ canRemainFixedInSectionSolve: rawPath.every(
2902
+ (candidate) => sectionGraph.ports.some(
2903
+ (port) => port.portId === candidate.port.portId
2904
+ )
2905
+ ),
2906
+ sectionRoute: sliceSolvedRouteIntoLocalSection({
2907
+ sectionRoute: sectionRouteBase,
2908
+ graph: sectionGraph
2909
+ })
2910
+ });
2911
+ sectionConnections.push(sectionConnection);
2912
+ }
2913
+ return {
2914
+ centralRegionId: centralRegion.regionId,
2915
+ sectionRegionIds,
2916
+ graph: sectionGraph,
2917
+ connections: sectionConnections,
2918
+ sectionRoutes
2919
+ };
2920
+ };
2921
+
2922
+ // lib/HyperGraphSectionOptimizer/HyperGraphSectionOptimizer.ts
2923
+ var HyperGraphSectionOptimizer = class extends BaseSolver2 {
2924
+ constructor(input) {
2925
+ super();
2926
+ this.input = input;
2927
+ this.graph = input.hyperGraphSolver.graph;
2928
+ const initialSolvedRoutes = input.inputSolvedRoutes;
2929
+ const inputConnections = input.hyperGraphSolver.connections;
2930
+ this.connections = inputConnections;
2931
+ this.solvedRoutes = commitSolvedRoutes({
2932
+ graph: this.graph,
2933
+ connections: this.connections,
2934
+ solvedRoutes: initialSolvedRoutes
2935
+ });
2936
+ this.maxAttemptsPerRegion = input.MAX_ATTEMPTS_PER_REGION;
2937
+ this.maxSectionAttempts = input.MAX_ATTEMPTS_PER_SECTION ?? 500;
2938
+ this.iterations += this.iterations * (input.effort ?? 1);
2939
+ this.fractionToReplace = input.FRACTION_TO_REPLACE ?? 0.2;
2940
+ this.alwaysRipConflicts = input.alwaysRipConflicts ?? true;
2941
+ }
2942
+ graph;
2943
+ connections;
2944
+ solvedRoutes;
2945
+ activeSection = null;
2946
+ baselineSectionCost = Infinity;
2947
+ baselineGlobalCost = Infinity;
2948
+ regionAttemptCounts = /* @__PURE__ */ new Map();
2949
+ sectionAttempts = 0;
2950
+ maxAttemptsPerRegion;
2951
+ maxSectionAttempts;
2952
+ fractionToReplace;
2953
+ alwaysRipConflicts;
2954
+ random = createSeededRandom(31337);
2955
+ getSolverName() {
2956
+ return "HyperGraphSectionOptimizer";
2957
+ }
2958
+ getConstructorParams() {
2959
+ return {
2960
+ inputGraph: convertHyperGraphToSerializedHyperGraph(this.graph),
2961
+ inputConnections: convertConnectionsToSerializedConnections(
2962
+ this.connections
2963
+ ),
2964
+ inputSolvedRoutes: this.solvedRoutes,
2965
+ expansionHopsFromCentralRegion: this.input.expansionHopsFromCentralRegion,
2966
+ maxAttemptsPerRegion: this.maxAttemptsPerRegion,
2967
+ maxSectionAttempts: this.maxSectionAttempts,
2968
+ effort: this.input.effort,
2969
+ fractionToReplace: this.fractionToReplace,
2970
+ alwaysRipConflicts: this.alwaysRipConflicts,
2971
+ ACCEPTABLE_COST: this.input.ACCEPTABLE_REGION_COST
2972
+ };
2973
+ }
2974
+ getOutput() {
2975
+ return this.solvedRoutes;
2976
+ }
2977
+ _setup() {
2978
+ this.beginSectionSolve();
2979
+ }
2980
+ visualize() {
2981
+ if (this.activeSubSolver) {
2982
+ return this.activeSubSolver.visualize();
2983
+ }
2984
+ return {
2985
+ title: "HyperGraphSectionOptimizer",
2986
+ points: [],
2987
+ lines: [],
2988
+ rects: [],
2989
+ circles: [],
2990
+ texts: [],
2991
+ polygons: [],
2992
+ arrows: []
2993
+ };
2994
+ }
2995
+ getCostOfRegionWithAttempts(region) {
2996
+ const attempts = this.regionAttemptCounts.get(region.regionId) ?? 0;
2997
+ return this.input.regionCost(region) + attempts * 1e4;
2998
+ }
2999
+ computeCostOfSection({
3000
+ section,
3001
+ solvedRoutes
3002
+ }) {
3003
+ if (this.input.computeSolvedGraphCost) {
3004
+ return this.input.computeSolvedGraphCost(solvedRoutes);
3005
+ }
3006
+ const solver = this.input.createHyperGraphSolver({
3007
+ inputGraph: section.graph,
3008
+ inputConnections: section.connections,
3009
+ inputSolvedRoutes: solvedRoutes
3010
+ });
3011
+ let totalCost = 0;
3012
+ for (const region of solver.graph.regions) {
3013
+ totalCost += this.input.regionCost(region);
3014
+ }
3015
+ return totalCost;
3016
+ }
3017
+ computeSolvedGraphCost(solvedRoutes) {
3018
+ if (this.input.computeSolvedGraphCost) {
3019
+ return this.input.computeSolvedGraphCost(solvedRoutes);
3020
+ }
3021
+ const solver = this.input.createHyperGraphSolver({
3022
+ inputGraph: this.graph,
3023
+ inputConnections: this.connections,
3024
+ inputSolvedRoutes: solvedRoutes
3025
+ });
3026
+ let totalCost = 0;
3027
+ for (const region of solver.graph.regions) {
3028
+ totalCost += this.input.regionCost(region);
3029
+ }
3030
+ return totalCost;
3031
+ }
3032
+ /**
3033
+ * TODO default behavior should be to rip entire section
3034
+ */
3035
+ determineConnectionsToRip(section, evaluationSolver) {
3036
+ const allConnectionIds = section.sectionRoutes.map(
3037
+ (route) => route.globalConnection.connectionId
3038
+ );
3039
+ if (this.fractionToReplace >= 1) {
3040
+ return new Set(allConnectionIds);
3041
+ }
3042
+ const shuffledConnectionIds = [...allConnectionIds];
3043
+ for (let index = shuffledConnectionIds.length - 1; index > 0; index--) {
3044
+ const swapIndex = Math.floor(this.random() * (index + 1));
3045
+ [shuffledConnectionIds[index], shuffledConnectionIds[swapIndex]] = [
3046
+ shuffledConnectionIds[swapIndex],
3047
+ shuffledConnectionIds[index]
3048
+ ];
3049
+ }
3050
+ const ripCount = Math.max(
3051
+ 1,
3052
+ Math.ceil(shuffledConnectionIds.length * this.fractionToReplace)
3053
+ );
3054
+ const connectionsToReroute = new Set(
3055
+ shuffledConnectionIds.slice(0, ripCount)
3056
+ );
3057
+ if (!this.alwaysRipConflicts) {
3058
+ return connectionsToReroute;
3059
+ }
3060
+ const localRegionMap = new Map(
3061
+ section.graph.regions.map((region) => [region.regionId, region])
3062
+ );
3063
+ for (const route of section.sectionRoutes) {
3064
+ for (const candidate of route.sectionRoute.path) {
3065
+ if (!candidate.lastPort || !candidate.lastRegion) continue;
3066
+ const sectionRegion = localRegionMap.get(candidate.lastRegion.regionId);
3067
+ if (!sectionRegion) continue;
3068
+ evaluationSolver.currentConnection = route.globalConnection;
3069
+ const conflictingAssignments = evaluationSolver.getRipsRequiredForPortUsage(
3070
+ sectionRegion,
3071
+ candidate.lastPort,
3072
+ candidate.port
3073
+ );
3074
+ for (const conflict of conflictingAssignments) {
3075
+ const firstId = route.globalConnection.connectionId;
3076
+ const secondId = conflict.connection.connectionId;
3077
+ if (connectionsToReroute.has(firstId) || connectionsToReroute.has(secondId)) {
3078
+ continue;
3079
+ }
3080
+ connectionsToReroute.add(this.random() < 0.5 ? firstId : secondId);
3081
+ }
3082
+ }
3083
+ }
3084
+ return connectionsToReroute;
3085
+ }
3086
+ getNextCentralRegion() {
3087
+ let bestRegion = null;
3088
+ let bestCost = Infinity;
3089
+ for (const region of this.graph.regions) {
3090
+ if ((region.assignments?.length ?? 0) === 0) continue;
3091
+ if ((this.regionAttemptCounts.get(region.regionId) ?? 0) >= this.maxAttemptsPerRegion) {
3092
+ continue;
3093
+ }
3094
+ const regionCost = this.input.regionCost(region);
3095
+ if (regionCost < this.input.ACCEPTABLE_REGION_COST) continue;
3096
+ const cost = this.getCostOfRegionWithAttempts(region);
3097
+ if (cost >= bestCost) continue;
3098
+ bestCost = cost;
3099
+ bestRegion = region;
3100
+ }
3101
+ return bestRegion;
3102
+ }
3103
+ beginSectionSolve() {
3104
+ if (this.sectionAttempts >= this.maxSectionAttempts) {
3105
+ console.log(
3106
+ `Reached max section attempts (${this.maxSectionAttempts}), stopping optimization`
3107
+ );
3108
+ this.solved = true;
3109
+ return;
3110
+ }
3111
+ const centralRegion = this.getNextCentralRegion();
3112
+ if (!centralRegion) {
3113
+ this.solved = true;
3114
+ return;
3115
+ }
3116
+ this.sectionAttempts++;
3117
+ this.activeSection = getSectionOfHyperGraphAsHyperGraph({
3118
+ graph: this.graph,
3119
+ solvedRoutes: this.solvedRoutes,
3120
+ centralRegion,
3121
+ expansionHopsFromCentralRegion: this.input.expansionHopsFromCentralRegion
3122
+ });
3123
+ if (!this.activeSection) {
3124
+ return;
3125
+ }
3126
+ if (this.activeSection.connections.length === 0) {
3127
+ this.regionAttemptCounts.set(
3128
+ centralRegion.regionId,
3129
+ (this.regionAttemptCounts.get(centralRegion.regionId) ?? 0) + 1
3130
+ );
3131
+ this.activeSection = null;
3132
+ return;
3133
+ }
3134
+ const fixedSectionRoutes = this.activeSection.sectionRoutes.filter(
3135
+ (route) => route.canRemainFixedInSectionSolve
3136
+ );
3137
+ const baselineSolver = this.input.createHyperGraphSolver({
3138
+ inputGraph: this.activeSection.graph,
3139
+ inputConnections: this.activeSection.connections,
3140
+ inputSolvedRoutes: fixedSectionRoutes.map((route) => route.sectionRoute)
3141
+ });
3142
+ const connectionsToReroute = this.determineConnectionsToRip(
3143
+ this.activeSection,
3144
+ baselineSolver
3145
+ );
3146
+ for (const route of this.activeSection.sectionRoutes) {
3147
+ if (!route.canRemainFixedInSectionSolve) {
3148
+ connectionsToReroute.add(route.globalConnection.connectionId);
3149
+ }
3150
+ }
3151
+ const remainingSectionRoutes = this.activeSection.sectionRoutes.filter(
3152
+ (route) => route.canRemainFixedInSectionSolve && !connectionsToReroute.has(route.globalConnection.connectionId)
3153
+ );
3154
+ const fixedSectionSolvedRoutes = remainingSectionRoutes.map(
3155
+ (route) => route.sectionRoute
3156
+ );
3157
+ const baselineSectionSolvedRoutes = [
3158
+ ...fixedSectionSolvedRoutes,
3159
+ ...this.activeSection.sectionRoutes.filter(
3160
+ (route) => connectionsToReroute.has(route.globalConnection.connectionId)
3161
+ ).map((route) => route.sectionRoute)
3162
+ ];
3163
+ this.baselineSectionCost = this.computeCostOfSection({
3164
+ section: this.activeSection,
3165
+ solvedRoutes: baselineSectionSolvedRoutes
3166
+ });
3167
+ this.baselineGlobalCost = this.computeSolvedGraphCost(this.solvedRoutes);
3168
+ this.activeSubSolver = this.input.createHyperGraphSolver({
3169
+ inputGraph: this.activeSection.graph,
3170
+ inputConnections: this.activeSection.connections,
3171
+ inputSolvedRoutes: fixedSectionSolvedRoutes
3172
+ });
3173
+ for (const conn of this.activeSubSolver.connections) {
3174
+ if (!conn.startRegion || !conn.endRegion) {
3175
+ console.error({
3176
+ startRegion: conn.startRegion?.regionId,
3177
+ endRegion: conn.endRegion?.regionId
3178
+ });
3179
+ }
3180
+ }
3181
+ }
3182
+ _step() {
3183
+ if (!this.activeSubSolver) {
3184
+ this.beginSectionSolve();
3185
+ return;
3186
+ }
3187
+ this.activeSubSolver.step();
3188
+ if (!this.activeSection) {
3189
+ return;
3190
+ }
3191
+ if (this.activeSubSolver.failed) {
3192
+ this.failedSubSolvers ??= [];
3193
+ this.failedSubSolvers.push(this.activeSubSolver);
3194
+ const attempts = this.regionAttemptCounts.get(this.activeSection.centralRegionId) ?? 0;
3195
+ this.regionAttemptCounts.set(
3196
+ this.activeSection.centralRegionId,
3197
+ attempts + 1
3198
+ );
3199
+ this.activeSubSolver = null;
3200
+ this.activeSection = null;
3201
+ this.baselineSectionCost = Infinity;
3202
+ this.baselineGlobalCost = Infinity;
3203
+ return;
3204
+ }
3205
+ if (!this.activeSubSolver.solved) return;
3206
+ const candidateSectionSolvedRoutes = this.activeSubSolver.solvedRoutes;
3207
+ const candidateCost = this.computeCostOfSection({
3208
+ section: this.activeSection,
3209
+ solvedRoutes: candidateSectionSolvedRoutes
3210
+ });
3211
+ const replacementAppliedSolvedRoutes = previewSectionReplacement({
3212
+ solvedRoutes: this.solvedRoutes,
3213
+ section: this.activeSection,
3214
+ replacementSolvedRoutes: candidateSectionSolvedRoutes
3215
+ });
3216
+ const candidateGlobalCost = this.computeSolvedGraphCost(
3217
+ replacementAppliedSolvedRoutes
3218
+ );
3219
+ const sectionNotWorse = candidateCost <= this.baselineSectionCost;
3220
+ const globalImproved = candidateGlobalCost < this.baselineGlobalCost;
3221
+ if (sectionNotWorse && globalImproved) {
3222
+ this.solvedRoutes = replacementAppliedSolvedRoutes;
3223
+ const sourceSolver = this.input.hyperGraphSolver;
3224
+ if (!sourceSolver) return;
3225
+ sourceSolver.solvedRoutes = commitSolvedRoutes({
3226
+ graph: sourceSolver.graph,
3227
+ connections: sourceSolver.connections,
3228
+ solvedRoutes: this.solvedRoutes
3229
+ });
3230
+ for (const regionId of this.activeSection.sectionRegionIds) {
3231
+ this.regionAttemptCounts.set(regionId, 0);
3232
+ }
3233
+ this.baselineSectionCost = candidateCost;
3234
+ this.baselineGlobalCost = candidateGlobalCost;
3235
+ } else {
3236
+ const attempts = this.regionAttemptCounts.get(this.activeSection.centralRegionId) ?? 0;
3237
+ this.regionAttemptCounts.set(
3238
+ this.activeSection.centralRegionId,
3239
+ attempts + 1
3240
+ );
3241
+ }
3242
+ this.activeSubSolver = null;
3243
+ this.activeSection = null;
3244
+ this.baselineSectionCost = Infinity;
3245
+ this.baselineGlobalCost = Infinity;
3246
+ }
3247
+ };
3248
+
3249
+ // node_modules/transformation-matrix/src/applyToPoint.js
3250
+ function applyToPoint(matrix2, point2) {
3251
+ return Array.isArray(point2) ? [
3252
+ matrix2.a * point2[0] + matrix2.c * point2[1] + matrix2.e,
3253
+ matrix2.b * point2[0] + matrix2.d * point2[1] + matrix2.f
3254
+ ] : {
3255
+ x: matrix2.a * point2.x + matrix2.c * point2.y + matrix2.e,
3256
+ y: matrix2.b * point2.x + matrix2.d * point2.y + matrix2.f
3257
+ };
3258
+ }
3259
+
3260
+ // node_modules/transformation-matrix/src/utils.js
3261
+ function isUndefined(val) {
3262
+ return typeof val === "undefined";
3263
+ }
3264
+
3265
+ // node_modules/transformation-matrix/src/translate.js
3266
+ function translate(tx, ty = 0) {
3267
+ return {
3268
+ a: 1,
3269
+ c: 0,
3270
+ e: tx,
3271
+ b: 0,
3272
+ d: 1,
3273
+ f: ty
3274
+ };
3275
+ }
3276
+
3277
+ // node_modules/transformation-matrix/src/transform.js
3278
+ function transform(...matrices) {
3279
+ matrices = Array.isArray(matrices[0]) ? matrices[0] : matrices;
3280
+ const multiply = (m1, m2) => {
3281
+ return {
3282
+ a: m1.a * m2.a + m1.c * m2.b,
3283
+ c: m1.a * m2.c + m1.c * m2.d,
3284
+ e: m1.a * m2.e + m1.c * m2.f + m1.e,
3285
+ b: m1.b * m2.a + m1.d * m2.b,
3286
+ d: m1.b * m2.c + m1.d * m2.d,
3287
+ f: m1.b * m2.e + m1.d * m2.f + m1.f
3288
+ };
3289
+ };
3290
+ switch (matrices.length) {
3291
+ case 0:
3292
+ throw new Error("no matrices provided");
3293
+ case 1:
3294
+ return matrices[0];
3295
+ case 2:
3296
+ return multiply(matrices[0], matrices[1]);
3297
+ default: {
3298
+ const [m1, m2, ...rest] = matrices;
3299
+ const m = multiply(m1, m2);
3300
+ return transform(m, ...rest);
3301
+ }
3302
+ }
3303
+ }
3304
+ function compose(...matrices) {
3305
+ return transform(...matrices);
3306
+ }
3307
+
3308
+ // node_modules/transformation-matrix/src/rotate.js
3309
+ var { cos, sin, PI } = Math;
3310
+ function rotate(angle, cx, cy) {
3311
+ const cosAngle = cos(angle);
3312
+ const sinAngle = sin(angle);
3313
+ const rotationMatrix = {
3314
+ a: cosAngle,
3315
+ c: -sinAngle,
3316
+ e: 0,
3317
+ b: sinAngle,
3318
+ d: cosAngle,
3319
+ f: 0
3320
+ };
3321
+ if (isUndefined(cx) || isUndefined(cy)) {
3322
+ return rotationMatrix;
3323
+ }
3324
+ return transform([
3325
+ translate(cx, cy),
3326
+ rotationMatrix,
3327
+ translate(-cx, -cy)
3328
+ ]);
3329
+ }
3330
+
3331
+ // node_modules/transformation-matrix/src/skew.js
3332
+ var { tan } = Math;
3333
+
3334
+ // lib/JumperGraphSolver/geometry/getBoundsCenter.ts
3335
+ var computeBoundsCenter = (bounds) => {
3336
+ return {
3337
+ x: (bounds.minX + bounds.maxX) / 2,
3338
+ y: (bounds.minY + bounds.maxY) / 2
3339
+ };
3340
+ };
3341
+
3342
+ // lib/JumperGraphSolver/geometry/applyTransformToGraph.ts
3343
+ var applyTransformToGraph = (graph, matrix2) => {
3344
+ const transformedRegions = graph.regions.map((region) => {
3345
+ const { bounds, center, ...rest } = region.d;
3346
+ const corners = [
3347
+ { x: bounds.minX, y: bounds.minY },
3348
+ { x: bounds.maxX, y: bounds.minY },
3349
+ { x: bounds.minX, y: bounds.maxY },
3350
+ { x: bounds.maxX, y: bounds.maxY }
3351
+ ].map((corner) => applyToPoint(matrix2, corner));
3352
+ const newBounds = {
3353
+ minX: Math.min(...corners.map((c) => c.x)),
3354
+ maxX: Math.max(...corners.map((c) => c.x)),
3355
+ minY: Math.min(...corners.map((c) => c.y)),
3356
+ maxY: Math.max(...corners.map((c) => c.y))
3357
+ };
3358
+ const newCenter = applyToPoint(matrix2, center);
3359
+ return {
3360
+ ...region,
3361
+ // Clear ports array - will be rebuilt with new port objects
3362
+ ports: [],
3363
+ d: {
3364
+ ...rest,
3365
+ bounds: newBounds,
3366
+ center: newCenter
3367
+ }
3368
+ };
3369
+ });
3370
+ const regionMap = /* @__PURE__ */ new Map();
3371
+ for (let i = 0; i < graph.regions.length; i++) {
3372
+ regionMap.set(graph.regions[i], transformedRegions[i]);
3373
+ }
3374
+ const transformedPorts = graph.ports.map((port) => {
3375
+ const newPosition = applyToPoint(matrix2, port.d);
3376
+ const newRegion1 = regionMap.get(port.region1);
3377
+ const newRegion2 = regionMap.get(port.region2);
3378
+ const newPort = {
3379
+ ...port,
3380
+ region1: newRegion1,
3381
+ region2: newRegion2,
3382
+ d: newPosition
3383
+ };
3384
+ newRegion1.ports.push(newPort);
3385
+ newRegion2.ports.push(newPort);
3386
+ return newPort;
3387
+ });
3388
+ const transformedJumperLocations = graph.jumperLocations?.map((loc) => {
3389
+ const newCenter = applyToPoint(matrix2, loc.center);
3390
+ const newPadRegions = loc.padRegions.map(
3391
+ (region) => regionMap.get(region)
3392
+ );
3393
+ const unitX = applyToPoint(matrix2, { x: 1, y: 0 });
3394
+ const origin = applyToPoint(matrix2, { x: 0, y: 0 });
3395
+ const dx = unitX.x - origin.x;
3396
+ const dy = unitX.y - origin.y;
3397
+ const isRotated90 = Math.abs(dy) > Math.abs(dx);
3398
+ const newOrientation = isRotated90 ? loc.orientation === "horizontal" ? "vertical" : "horizontal" : loc.orientation;
3399
+ return {
3400
+ center: newCenter,
3401
+ orientation: newOrientation,
3402
+ padRegions: newPadRegions
3403
+ };
3404
+ });
3405
+ return {
3406
+ regions: transformedRegions,
3407
+ ports: transformedPorts,
3408
+ ...transformedJumperLocations && {
3409
+ jumperLocations: transformedJumperLocations
3410
+ }
3411
+ };
3412
+ };
3413
+ var rotateGraph90Degrees = (graph) => {
3414
+ const bounds = calculateGraphBounds(graph.regions);
3415
+ const center = computeBoundsCenter(bounds);
3416
+ const matrix2 = compose(
3417
+ translate(center.x, center.y),
3418
+ rotate(-Math.PI / 2),
3419
+ // -90 degrees (clockwise)
3420
+ translate(-center.x, -center.y)
3421
+ );
3422
+ return applyTransformToGraph(graph, matrix2);
3423
+ };
3424
+
3425
+ // lib/JumperGraphSolver/computeCrossingAssignments.ts
3426
+ function computeCrossingAssignments(region, port1, port2) {
3427
+ const perimeter = getRegionPerimeter(region);
3428
+ const t1 = getPortPerimeterTInRegion(port1, region);
3429
+ const t2 = getPortPerimeterTInRegion(port2, region);
3430
+ const newChord = [t1, t2];
3431
+ const crossingAssignments = [];
3432
+ const assignments = region.assignments ?? [];
3433
+ for (const assignment of assignments) {
3434
+ const existingT1 = getPortPerimeterTInRegion(
3435
+ assignment.regionPort1,
3436
+ region
3437
+ );
3438
+ const existingT2 = getPortPerimeterTInRegion(
3439
+ assignment.regionPort2,
3440
+ region
3441
+ );
3442
+ const existingChord = [existingT1, existingT2];
2435
3443
  if (chordsCross(newChord, existingChord, perimeter)) {
2436
3444
  crossingAssignments.push(assignment);
2437
3445
  }
@@ -2637,31 +3645,74 @@ var getConnectionColor = (connectionId, alpha = 0.8) => {
2637
3645
  const hue = Math.abs(hash) % 360;
2638
3646
  return `hsla(${hue}, 70%, 50%, ${alpha})`;
2639
3647
  };
2640
- var visualizeJumperGraphSolver = (solver) => {
2641
- const jumperGraph = {
2642
- regions: solver.graph.regions,
2643
- ports: solver.graph.ports
2644
- };
2645
- const graphics = visualizeJumperGraph(jumperGraph, {
2646
- connections: solver.connections,
2647
- ...solver.iterations > 0 ? {
3648
+ var visualizeJumperGraphWithSolvedRoutes = (input) => {
3649
+ const graphics = visualizeJumperGraph(input.graph, {
3650
+ connections: input.connections,
3651
+ ...input.hideInitialGeometry ? {
2648
3652
  hideRegionPortLines: true,
2649
3653
  hideConnectionLines: true,
2650
3654
  hidePortPoints: true
2651
3655
  } : {}
2652
3656
  });
2653
- if (solver.iterations === 0) {
3657
+ if (!input.hideInitialGeometry) {
2654
3658
  for (const polygon2 of graphics.polygons) {
2655
3659
  polygon2.stroke = "rgba(128, 128, 128, 0.5)";
2656
3660
  polygon2.strokeWidth = 0.03;
2657
3661
  }
2658
3662
  }
3663
+ if (input.title) {
3664
+ graphics.title = input.title;
3665
+ }
3666
+ for (const solvedRoute of input.priorSolvedRoutes ?? []) {
3667
+ const pathPoints = solvedRoute.path.map((candidate) => {
3668
+ const port = candidate.port;
3669
+ return { x: port.d.x, y: port.d.y };
3670
+ });
3671
+ if (pathPoints.length === 0) continue;
3672
+ graphics.lines.push({
3673
+ points: pathPoints,
3674
+ strokeColor: "rgba(80, 80, 80, 0.45)",
3675
+ strokeDash: "4 4"
3676
+ });
3677
+ }
3678
+ for (const solvedRoute of input.solvedRoutes) {
3679
+ const connectionColor = getConnectionColor(
3680
+ solvedRoute.connection.connectionId
3681
+ );
3682
+ const pathPoints = [];
3683
+ for (const candidate of solvedRoute.path) {
3684
+ const port = candidate.port;
3685
+ pathPoints.push({ x: port.d.x, y: port.d.y });
3686
+ }
3687
+ if (pathPoints.length > 0) {
3688
+ graphics.lines.push({
3689
+ points: pathPoints,
3690
+ strokeColor: connectionColor
3691
+ });
3692
+ }
3693
+ }
3694
+ return graphics;
3695
+ };
3696
+ var visualizeJumperGraphSolver = (solver) => {
3697
+ const jumperGraph = {
3698
+ regions: solver.graph.regions,
3699
+ ports: solver.graph.ports
3700
+ };
3701
+ const graphics = visualizeJumperGraphWithSolvedRoutes({
3702
+ graph: jumperGraph,
3703
+ connections: solver.connections,
3704
+ solvedRoutes: solver.solvedRoutes,
3705
+ hideInitialGeometry: solver.iterations > 0
3706
+ });
2659
3707
  if (solver.currentConnection && !solver.solved) {
2660
3708
  const connectionColor = getConnectionColor(
2661
3709
  solver.currentConnection.connectionId
2662
3710
  );
2663
3711
  const startRegion = solver.currentConnection.startRegion;
2664
3712
  const endRegion = solver.currentConnection.endRegion;
3713
+ if (!startRegion?.d || !endRegion?.d) {
3714
+ return graphics;
3715
+ }
2665
3716
  const startCenter = {
2666
3717
  x: (startRegion.d.bounds.minX + startRegion.d.bounds.maxX) / 2,
2667
3718
  y: (startRegion.d.bounds.minY + startRegion.d.bounds.maxY) / 2
@@ -2677,32 +3728,16 @@ var visualizeJumperGraphSolver = (solver) => {
2677
3728
  });
2678
3729
  graphics.points.push({
2679
3730
  x: startCenter.x - 0.1,
2680
- y: startCenter.y + 0.1,
2681
- color: connectionColor,
2682
- label: [solver.currentConnection.connectionId, "start"].join("\n")
2683
- });
2684
- graphics.points.push({
2685
- x: endCenter.x - 0.1,
2686
- y: endCenter.y + 0.1,
2687
- color: connectionColor,
2688
- label: [solver.currentConnection.connectionId, "end"].join("\n")
2689
- });
2690
- }
2691
- for (const solvedRoute of solver.solvedRoutes) {
2692
- const connectionColor = getConnectionColor(
2693
- solvedRoute.connection.connectionId
2694
- );
2695
- const pathPoints = [];
2696
- for (const candidate of solvedRoute.path) {
2697
- const port = candidate.port;
2698
- pathPoints.push({ x: port.d.x, y: port.d.y });
2699
- }
2700
- if (pathPoints.length > 0) {
2701
- graphics.lines.push({
2702
- points: pathPoints,
2703
- strokeColor: connectionColor
2704
- });
2705
- }
3731
+ y: startCenter.y + 0.1,
3732
+ color: connectionColor,
3733
+ label: [solver.currentConnection.connectionId, "start"].join("\n")
3734
+ });
3735
+ graphics.points.push({
3736
+ x: endCenter.x - 0.1,
3737
+ y: endCenter.y + 0.1,
3738
+ color: connectionColor,
3739
+ label: [solver.currentConnection.connectionId, "end"].join("\n")
3740
+ });
2706
3741
  }
2707
3742
  const candidates = solver.candidateQueue.peekMany(10);
2708
3743
  for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
@@ -2881,252 +3916,6 @@ var JumperGraphSolver = class extends HyperGraphSolver {
2881
3916
  }
2882
3917
  };
2883
3918
 
2884
- // lib/JumperGraphSolver/jumper-graph-generator/createConnectionPort.ts
2885
- var createConnectionPort = (portId, connectionRegion, boundaryRegion, portPosition) => {
2886
- const port = {
2887
- portId,
2888
- region1: connectionRegion,
2889
- region2: boundaryRegion,
2890
- d: { x: portPosition.x, y: portPosition.y }
2891
- };
2892
- connectionRegion.ports.push(port);
2893
- boundaryRegion.ports.push(port);
2894
- return port;
2895
- };
2896
-
2897
- // lib/JumperGraphSolver/jumper-graph-generator/createConnectionRegion.ts
2898
- var CONNECTION_REGION_SIZE = 0.4;
2899
- var createConnectionRegion = (regionId, x, y) => {
2900
- const halfSize = CONNECTION_REGION_SIZE / 2;
2901
- return {
2902
- regionId,
2903
- ports: [],
2904
- d: {
2905
- bounds: {
2906
- minX: x - halfSize,
2907
- maxX: x + halfSize,
2908
- minY: y - halfSize,
2909
- maxY: y + halfSize
2910
- },
2911
- center: { x, y },
2912
- isPad: false,
2913
- isConnectionRegion: true
2914
- }
2915
- };
2916
- };
2917
-
2918
- // lib/JumperGraphSolver/jumper-graph-generator/findBoundaryRegion.ts
2919
- var EPS = 0.01;
2920
- var clamp3 = (value, min, max) => {
2921
- return Math.max(min, Math.min(max, value));
2922
- };
2923
- var getBoundarySidesForPoint = (x, y, graphBounds) => {
2924
- const sides = [];
2925
- if (Math.abs(x - graphBounds.minX) < EPS) sides.push("left");
2926
- if (Math.abs(x - graphBounds.maxX) < EPS) sides.push("right");
2927
- if (Math.abs(y - graphBounds.maxY) < EPS) sides.push("top");
2928
- if (Math.abs(y - graphBounds.minY) < EPS) sides.push("bottom");
2929
- return sides;
2930
- };
2931
- var isPointOnSide = (p, side, b) => {
2932
- if (side === "left") return Math.abs(p.x - b.minX) < EPS;
2933
- if (side === "right") return Math.abs(p.x - b.maxX) < EPS;
2934
- if (side === "top") return Math.abs(p.y - b.maxY) < EPS;
2935
- return Math.abs(p.y - b.minY) < EPS;
2936
- };
2937
- var projectToSegment2 = (x, y, a, b) => {
2938
- const abx = b.x - a.x;
2939
- const aby = b.y - a.y;
2940
- const apx = x - a.x;
2941
- const apy = y - a.y;
2942
- const ab2 = abx * abx + aby * aby;
2943
- const t = ab2 > 0 ? clamp3((apx * abx + apy * aby) / ab2, 0, 1) : 0;
2944
- const px = a.x + t * abx;
2945
- const py = a.y + t * aby;
2946
- const dx = x - px;
2947
- const dy = y - py;
2948
- return {
2949
- x: px,
2950
- y: py,
2951
- d2: dx * dx + dy * dy
2952
- };
2953
- };
2954
- var getRegionBoundaryProjection = (x, y, region, graphBounds, preferredSides) => {
2955
- const polygon2 = region.d.polygon;
2956
- if (polygon2 && polygon2.length >= 3) {
2957
- const sideSet = new Set(preferredSides);
2958
- let best2 = null;
2959
- for (let i = 0; i < polygon2.length; i++) {
2960
- const a = polygon2[i];
2961
- const b = polygon2[(i + 1) % polygon2.length];
2962
- if (preferredSides.length > 0) {
2963
- const edgeOnPreferredSide = preferredSides.some(
2964
- (side) => isPointOnSide(a, side, graphBounds) && isPointOnSide(b, side, graphBounds) && sideSet.has(side)
2965
- );
2966
- if (!edgeOnPreferredSide) continue;
2967
- }
2968
- const p = projectToSegment2(x, y, a, b);
2969
- if (!best2 || p.d2 < best2.d2) {
2970
- best2 = p;
2971
- }
2972
- }
2973
- if (best2) return best2;
2974
- }
2975
- const bounds = region.d.bounds;
2976
- const sideCandidates = [];
2977
- if (preferredSides.length > 0) {
2978
- for (const side of preferredSides) {
2979
- if (side === "left") {
2980
- sideCandidates.push({
2981
- side,
2982
- x: bounds.minX,
2983
- y: clamp3(y, bounds.minY, bounds.maxY)
2984
- });
2985
- } else if (side === "right") {
2986
- sideCandidates.push({
2987
- side,
2988
- x: bounds.maxX,
2989
- y: clamp3(y, bounds.minY, bounds.maxY)
2990
- });
2991
- } else if (side === "top") {
2992
- sideCandidates.push({
2993
- side,
2994
- x: clamp3(x, bounds.minX, bounds.maxX),
2995
- y: bounds.maxY
2996
- });
2997
- } else {
2998
- sideCandidates.push({
2999
- side,
3000
- x: clamp3(x, bounds.minX, bounds.maxX),
3001
- y: bounds.minY
3002
- });
3003
- }
3004
- }
3005
- }
3006
- if (sideCandidates.length === 0) {
3007
- sideCandidates.push(
3008
- { side: "left", x: bounds.minX, y: clamp3(y, bounds.minY, bounds.maxY) },
3009
- {
3010
- side: "right",
3011
- x: bounds.maxX,
3012
- y: clamp3(y, bounds.minY, bounds.maxY)
3013
- },
3014
- { side: "top", x: clamp3(x, bounds.minX, bounds.maxX), y: bounds.maxY },
3015
- {
3016
- side: "bottom",
3017
- x: clamp3(x, bounds.minX, bounds.maxX),
3018
- y: bounds.minY
3019
- }
3020
- );
3021
- }
3022
- let best = null;
3023
- for (const c of sideCandidates) {
3024
- if (preferredSides.length > 0 && !preferredSides.includes(c.side)) continue;
3025
- const dx = x - c.x;
3026
- const dy = y - c.y;
3027
- const d2 = dx * dx + dy * dy;
3028
- if (!best || d2 < best.d2) {
3029
- best = { x: c.x, y: c.y, d2 };
3030
- }
3031
- }
3032
- return best;
3033
- };
3034
- var findBoundaryRegion = (x, y, regions, graphBounds) => {
3035
- const preferredSides = getBoundarySidesForPoint(x, y, graphBounds);
3036
- let closestRegion = null;
3037
- let closestDistance = Number.POSITIVE_INFINITY;
3038
- let closestPortPosition = { x, y };
3039
- for (const region of regions) {
3040
- if (region.d.isPad || region.d.isThroughJumper) continue;
3041
- const bounds = region.d.bounds;
3042
- 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;
3043
- if (!isOuterRegion) continue;
3044
- const projection = getRegionBoundaryProjection(
3045
- x,
3046
- y,
3047
- region,
3048
- graphBounds,
3049
- preferredSides
3050
- );
3051
- if (!projection) continue;
3052
- const dist = Math.sqrt(projection.d2);
3053
- if (dist < closestDistance) {
3054
- closestDistance = dist;
3055
- closestRegion = region;
3056
- closestPortPosition = { x: projection.x, y: projection.y };
3057
- }
3058
- }
3059
- if (closestRegion) {
3060
- return { region: closestRegion, portPosition: closestPortPosition };
3061
- }
3062
- return null;
3063
- };
3064
-
3065
- // lib/JumperGraphSolver/jumper-graph-generator/createGraphWithConnectionsFromBaseGraph.ts
3066
- var createGraphWithConnectionsFromBaseGraph = (baseGraph, xyConnections) => {
3067
- const regions = [...baseGraph.regions];
3068
- const ports = [...baseGraph.ports];
3069
- const connections = [];
3070
- const graphBounds = calculateGraphBounds(baseGraph.regions);
3071
- for (const xyConn of xyConnections) {
3072
- const { start, end, connectionId } = xyConn;
3073
- const startRegion = createConnectionRegion(
3074
- `conn:${connectionId}:start`,
3075
- start.x,
3076
- start.y
3077
- );
3078
- regions.push(startRegion);
3079
- const endRegion = createConnectionRegion(
3080
- `conn:${connectionId}:end`,
3081
- end.x,
3082
- end.y
3083
- );
3084
- regions.push(endRegion);
3085
- const startBoundary = findBoundaryRegion(
3086
- start.x,
3087
- start.y,
3088
- baseGraph.regions,
3089
- graphBounds
3090
- );
3091
- if (startBoundary) {
3092
- const startPort = createConnectionPort(
3093
- `conn:${connectionId}:start-port`,
3094
- startRegion,
3095
- startBoundary.region,
3096
- startBoundary.portPosition
3097
- );
3098
- ports.push(startPort);
3099
- }
3100
- const endBoundary = findBoundaryRegion(
3101
- end.x,
3102
- end.y,
3103
- baseGraph.regions,
3104
- graphBounds
3105
- );
3106
- if (endBoundary) {
3107
- const endPort = createConnectionPort(
3108
- `conn:${connectionId}:end-port`,
3109
- endRegion,
3110
- endBoundary.region,
3111
- endBoundary.portPosition
3112
- );
3113
- ports.push(endPort);
3114
- }
3115
- const connection = {
3116
- connectionId,
3117
- mutuallyConnectedNetworkId: connectionId,
3118
- startRegion,
3119
- endRegion
3120
- };
3121
- connections.push(connection);
3122
- }
3123
- return {
3124
- regions,
3125
- ports,
3126
- connections
3127
- };
3128
- };
3129
-
3130
3919
  // lib/JumperGraphSolver/jumper-graph-generator/generateSingleJumperRegions.ts
3131
3920
  var dims0603 = {
3132
3921
  padToPad: 1.65,
@@ -14382,7 +15171,7 @@ function setStepOfAllObjects(graphics, step) {
14382
15171
  }
14383
15172
  return graphics;
14384
15173
  }
14385
- var BaseSolver2 = class {
15174
+ var BaseSolver3 = class {
14386
15175
  MAX_ITERATIONS = 1e5;
14387
15176
  solved = false;
14388
15177
  failed = false;
@@ -14489,7 +15278,7 @@ function definePipelineStep(solverName, solverClass, getConstructorParams, opts
14489
15278
  onSolved: opts.onSolved
14490
15279
  };
14491
15280
  }
14492
- var BasePipelineSolver = class extends BaseSolver2 {
15281
+ var BasePipelineSolver = class extends BaseSolver3 {
14493
15282
  startTimeOfStage = {};
14494
15283
  endTimeOfStage = {};
14495
15284
  timeSpentOnStage = {};
@@ -14704,7 +15493,7 @@ var clampPointToBounds = (point4, bounds) => ({
14704
15493
  x: Math.min(bounds.maxX, Math.max(bounds.minX, point4.x)),
14705
15494
  y: Math.min(bounds.maxY, Math.max(bounds.minY, point4.y))
14706
15495
  });
14707
- var BuildRegionsSolver = class extends BaseSolver2 {
15496
+ var BuildRegionsSolver = class extends BaseSolver3 {
14708
15497
  input;
14709
15498
  output = null;
14710
15499
  constructor(input) {
@@ -15125,7 +15914,7 @@ var generateBoundaryPointsWithEdges = (params) => {
15125
15914
  hadCrossings: resolved.hadCrossings
15126
15915
  };
15127
15916
  };
15128
- var GeneratePointsSolver = class extends BaseSolver2 {
15917
+ var GeneratePointsSolver = class extends BaseSolver3 {
15129
15918
  input;
15130
15919
  output = null;
15131
15920
  constructor(input) {
@@ -15580,7 +16369,7 @@ var mergeCellsPolyanya = (params) => {
15580
16369
  const depths = liveCells.map(() => 0);
15581
16370
  return { cells: liveCells, depths };
15582
16371
  };
15583
- var MergeCellsSolver = class extends BaseSolver2 {
16372
+ var MergeCellsSolver = class extends BaseSolver3 {
15584
16373
  input;
15585
16374
  output = null;
15586
16375
  constructor(input) {
@@ -15838,7 +16627,7 @@ var filterTris = (params) => {
15838
16627
  return true;
15839
16628
  });
15840
16629
  };
15841
- var TriangulateSolver = class extends BaseSolver2 {
16630
+ var TriangulateSolver = class extends BaseSolver3 {
15842
16631
  input;
15843
16632
  output = null;
15844
16633
  constructor(input) {
@@ -23795,6 +24584,7 @@ function createConvexViaGraphFromXYConnections(xyConnections, viaTileOrProblem,
23795
24584
  }
23796
24585
  export {
23797
24586
  ConnectBuilder,
24587
+ HyperGraphSectionOptimizer,
23798
24588
  HyperGraphSolver,
23799
24589
  JUMPER_GRAPH_SOLVER_DEFAULTS,
23800
24590
  JumperGraphSolver,