@tscircuit/hypergraph 0.0.60 → 0.0.62

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 -8
  2. package/dist/index.js +1292 -503
  3. package/package.json +2 -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,28 +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
- this.graph.solvedRoutes = this.solvedRoutes;
1714
- for (const region of this.graph.regions) {
1715
- region.assignments = [];
1716
- }
1792
+ clearAssignmentsFromGraph(this.graph);
1717
1793
  this.connections = convertSerializedConnectionsToConnections(
1718
1794
  input.inputConnections,
1719
1795
  this.graph
1720
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
+ }
1721
1807
  if (input.greedyMultiplier !== void 0)
1722
1808
  this.greedyMultiplier = input.greedyMultiplier;
1723
1809
  if (input.rippingEnabled !== void 0)
1724
1810
  this.rippingEnabled = input.rippingEnabled;
1725
1811
  if (input.ripCost !== void 0) this.ripCost = input.ripCost;
1726
- 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
+ );
1727
1820
  this.candidateQueue = new PriorityQueue();
1728
- this.beginNewConnection();
1821
+ if (this.unprocessedConnections.length === 0) {
1822
+ this.solved = true;
1823
+ } else {
1824
+ this.beginNewConnection();
1825
+ }
1729
1826
  }
1730
1827
  getSolverName() {
1731
1828
  return "HyperGraphSolver";
@@ -1750,9 +1847,13 @@ var HyperGraphSolver = class extends BaseSolver {
1750
1847
  ),
1751
1848
  greedyMultiplier: this.greedyMultiplier,
1752
1849
  rippingEnabled: this.rippingEnabled,
1753
- ripCost: this.ripCost
1850
+ ripCost: this.ripCost,
1851
+ inputSolvedRoutes: this.solvedRoutes
1754
1852
  };
1755
1853
  }
1854
+ getOutput() {
1855
+ return this.solvedRoutes;
1856
+ }
1756
1857
  computeH(candidate) {
1757
1858
  return this.estimateCostToEnd(candidate.port);
1758
1859
  }
@@ -2064,197 +2165,8 @@ var HyperGraphSolver = class extends BaseSolver {
2064
2165
  }
2065
2166
  };
2066
2167
 
2067
- // node_modules/transformation-matrix/src/applyToPoint.js
2068
- function applyToPoint(matrix2, point2) {
2069
- return Array.isArray(point2) ? [
2070
- matrix2.a * point2[0] + matrix2.c * point2[1] + matrix2.e,
2071
- matrix2.b * point2[0] + matrix2.d * point2[1] + matrix2.f
2072
- ] : {
2073
- x: matrix2.a * point2.x + matrix2.c * point2.y + matrix2.e,
2074
- y: matrix2.b * point2.x + matrix2.d * point2.y + matrix2.f
2075
- };
2076
- }
2077
-
2078
- // node_modules/transformation-matrix/src/utils.js
2079
- function isUndefined(val) {
2080
- return typeof val === "undefined";
2081
- }
2082
-
2083
- // node_modules/transformation-matrix/src/translate.js
2084
- function translate(tx, ty = 0) {
2085
- return {
2086
- a: 1,
2087
- c: 0,
2088
- e: tx,
2089
- b: 0,
2090
- d: 1,
2091
- f: ty
2092
- };
2093
- }
2094
-
2095
- // node_modules/transformation-matrix/src/transform.js
2096
- function transform(...matrices) {
2097
- matrices = Array.isArray(matrices[0]) ? matrices[0] : matrices;
2098
- const multiply = (m1, m2) => {
2099
- return {
2100
- a: m1.a * m2.a + m1.c * m2.b,
2101
- c: m1.a * m2.c + m1.c * m2.d,
2102
- e: m1.a * m2.e + m1.c * m2.f + m1.e,
2103
- b: m1.b * m2.a + m1.d * m2.b,
2104
- d: m1.b * m2.c + m1.d * m2.d,
2105
- f: m1.b * m2.e + m1.d * m2.f + m1.f
2106
- };
2107
- };
2108
- switch (matrices.length) {
2109
- case 0:
2110
- throw new Error("no matrices provided");
2111
- case 1:
2112
- return matrices[0];
2113
- case 2:
2114
- return multiply(matrices[0], matrices[1]);
2115
- default: {
2116
- const [m1, m2, ...rest] = matrices;
2117
- const m = multiply(m1, m2);
2118
- return transform(m, ...rest);
2119
- }
2120
- }
2121
- }
2122
- function compose(...matrices) {
2123
- return transform(...matrices);
2124
- }
2125
-
2126
- // node_modules/transformation-matrix/src/rotate.js
2127
- var { cos, sin, PI } = Math;
2128
- function rotate(angle, cx, cy) {
2129
- const cosAngle = cos(angle);
2130
- const sinAngle = sin(angle);
2131
- const rotationMatrix = {
2132
- a: cosAngle,
2133
- c: -sinAngle,
2134
- e: 0,
2135
- b: sinAngle,
2136
- d: cosAngle,
2137
- f: 0
2138
- };
2139
- if (isUndefined(cx) || isUndefined(cy)) {
2140
- return rotationMatrix;
2141
- }
2142
- return transform([
2143
- translate(cx, cy),
2144
- rotationMatrix,
2145
- translate(-cx, -cy)
2146
- ]);
2147
- }
2148
-
2149
- // node_modules/transformation-matrix/src/skew.js
2150
- var { tan } = Math;
2151
-
2152
- // lib/JumperGraphSolver/jumper-graph-generator/calculateGraphBounds.ts
2153
- var calculateGraphBounds = (regions) => {
2154
- let minX = Number.POSITIVE_INFINITY;
2155
- let maxX = Number.NEGATIVE_INFINITY;
2156
- let minY = Number.POSITIVE_INFINITY;
2157
- let maxY = Number.NEGATIVE_INFINITY;
2158
- for (const region of regions) {
2159
- const { bounds } = region.d;
2160
- minX = Math.min(minX, bounds.minX);
2161
- maxX = Math.max(maxX, bounds.maxX);
2162
- minY = Math.min(minY, bounds.minY);
2163
- maxY = Math.max(maxY, bounds.maxY);
2164
- }
2165
- return { minX, maxX, minY, maxY };
2166
- };
2167
-
2168
- // lib/JumperGraphSolver/geometry/getBoundsCenter.ts
2169
- var computeBoundsCenter = (bounds) => {
2170
- return {
2171
- x: (bounds.minX + bounds.maxX) / 2,
2172
- y: (bounds.minY + bounds.maxY) / 2
2173
- };
2174
- };
2175
-
2176
- // lib/JumperGraphSolver/geometry/applyTransformToGraph.ts
2177
- var applyTransformToGraph = (graph, matrix2) => {
2178
- const transformedRegions = graph.regions.map((region) => {
2179
- const { bounds, center, ...rest } = region.d;
2180
- const corners = [
2181
- { x: bounds.minX, y: bounds.minY },
2182
- { x: bounds.maxX, y: bounds.minY },
2183
- { x: bounds.minX, y: bounds.maxY },
2184
- { x: bounds.maxX, y: bounds.maxY }
2185
- ].map((corner) => applyToPoint(matrix2, corner));
2186
- const newBounds = {
2187
- minX: Math.min(...corners.map((c) => c.x)),
2188
- maxX: Math.max(...corners.map((c) => c.x)),
2189
- minY: Math.min(...corners.map((c) => c.y)),
2190
- maxY: Math.max(...corners.map((c) => c.y))
2191
- };
2192
- const newCenter = applyToPoint(matrix2, center);
2193
- return {
2194
- ...region,
2195
- // Clear ports array - will be rebuilt with new port objects
2196
- ports: [],
2197
- d: {
2198
- ...rest,
2199
- bounds: newBounds,
2200
- center: newCenter
2201
- }
2202
- };
2203
- });
2204
- const regionMap = /* @__PURE__ */ new Map();
2205
- for (let i = 0; i < graph.regions.length; i++) {
2206
- regionMap.set(graph.regions[i], transformedRegions[i]);
2207
- }
2208
- const transformedPorts = graph.ports.map((port) => {
2209
- const newPosition = applyToPoint(matrix2, port.d);
2210
- const newRegion1 = regionMap.get(port.region1);
2211
- const newRegion2 = regionMap.get(port.region2);
2212
- const newPort = {
2213
- ...port,
2214
- region1: newRegion1,
2215
- region2: newRegion2,
2216
- d: newPosition
2217
- };
2218
- newRegion1.ports.push(newPort);
2219
- newRegion2.ports.push(newPort);
2220
- return newPort;
2221
- });
2222
- const transformedJumperLocations = graph.jumperLocations?.map((loc) => {
2223
- const newCenter = applyToPoint(matrix2, loc.center);
2224
- const newPadRegions = loc.padRegions.map(
2225
- (region) => regionMap.get(region)
2226
- );
2227
- const unitX = applyToPoint(matrix2, { x: 1, y: 0 });
2228
- const origin = applyToPoint(matrix2, { x: 0, y: 0 });
2229
- const dx = unitX.x - origin.x;
2230
- const dy = unitX.y - origin.y;
2231
- const isRotated90 = Math.abs(dy) > Math.abs(dx);
2232
- const newOrientation = isRotated90 ? loc.orientation === "horizontal" ? "vertical" : "horizontal" : loc.orientation;
2233
- return {
2234
- center: newCenter,
2235
- orientation: newOrientation,
2236
- padRegions: newPadRegions
2237
- };
2238
- });
2239
- return {
2240
- regions: transformedRegions,
2241
- ports: transformedPorts,
2242
- ...transformedJumperLocations && {
2243
- jumperLocations: transformedJumperLocations
2244
- }
2245
- };
2246
- };
2247
- var rotateGraph90Degrees = (graph) => {
2248
- const bounds = calculateGraphBounds(graph.regions);
2249
- const center = computeBoundsCenter(bounds);
2250
- const matrix2 = compose(
2251
- translate(center.x, center.y),
2252
- rotate(-Math.PI / 2),
2253
- // -90 degrees (clockwise)
2254
- translate(-center.x, -center.y)
2255
- );
2256
- return applyTransformToGraph(graph, matrix2);
2257
- };
2168
+ // lib/HyperGraphSectionOptimizer/HyperGraphSectionOptimizer.ts
2169
+ import { BaseSolver as BaseSolver2 } from "@tscircuit/solver-utils";
2258
2170
 
2259
2171
  // lib/JumperGraphSolver/perimeterChordUtils.ts
2260
2172
  function clamp2(value, min, max) {
@@ -2415,24 +2327,1119 @@ function chordsCross(chord1, chord2, perimeter) {
2415
2327
  return a < c && c < b && b < d || c < a && a < d && d < b;
2416
2328
  }
2417
2329
 
2418
- // lib/JumperGraphSolver/computeCrossingAssignments.ts
2419
- function computeCrossingAssignments(region, port1, port2) {
2420
- const perimeter = getRegionPerimeter(region);
2421
- const t1 = getPortPerimeterTInRegion(port1, region);
2422
- const t2 = getPortPerimeterTInRegion(port2, region);
2423
- const newChord = [t1, t2];
2424
- const crossingAssignments = [];
2425
- const assignments = region.assignments ?? [];
2426
- for (const assignment of assignments) {
2427
- const existingT1 = getPortPerimeterTInRegion(
2428
- assignment.regionPort1,
2429
- region
2430
- );
2431
- const existingT2 = getPortPerimeterTInRegion(
2432
- assignment.regionPort2,
2433
- region
2434
- );
2435
- 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];
2436
3443
  if (chordsCross(newChord, existingChord, perimeter)) {
2437
3444
  crossingAssignments.push(assignment);
2438
3445
  }
@@ -2638,31 +3645,74 @@ var getConnectionColor = (connectionId, alpha = 0.8) => {
2638
3645
  const hue = Math.abs(hash) % 360;
2639
3646
  return `hsla(${hue}, 70%, 50%, ${alpha})`;
2640
3647
  };
2641
- var visualizeJumperGraphSolver = (solver) => {
2642
- const jumperGraph = {
2643
- regions: solver.graph.regions,
2644
- ports: solver.graph.ports
2645
- };
2646
- const graphics = visualizeJumperGraph(jumperGraph, {
2647
- connections: solver.connections,
2648
- ...solver.iterations > 0 ? {
3648
+ var visualizeJumperGraphWithSolvedRoutes = (input) => {
3649
+ const graphics = visualizeJumperGraph(input.graph, {
3650
+ connections: input.connections,
3651
+ ...input.hideInitialGeometry ? {
2649
3652
  hideRegionPortLines: true,
2650
3653
  hideConnectionLines: true,
2651
3654
  hidePortPoints: true
2652
3655
  } : {}
2653
3656
  });
2654
- if (solver.iterations === 0) {
3657
+ if (!input.hideInitialGeometry) {
2655
3658
  for (const polygon2 of graphics.polygons) {
2656
3659
  polygon2.stroke = "rgba(128, 128, 128, 0.5)";
2657
3660
  polygon2.strokeWidth = 0.03;
2658
3661
  }
2659
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
+ });
2660
3707
  if (solver.currentConnection && !solver.solved) {
2661
3708
  const connectionColor = getConnectionColor(
2662
3709
  solver.currentConnection.connectionId
2663
3710
  );
2664
3711
  const startRegion = solver.currentConnection.startRegion;
2665
3712
  const endRegion = solver.currentConnection.endRegion;
3713
+ if (!startRegion?.d || !endRegion?.d) {
3714
+ return graphics;
3715
+ }
2666
3716
  const startCenter = {
2667
3717
  x: (startRegion.d.bounds.minX + startRegion.d.bounds.maxX) / 2,
2668
3718
  y: (startRegion.d.bounds.minY + startRegion.d.bounds.maxY) / 2
@@ -2678,32 +3728,16 @@ var visualizeJumperGraphSolver = (solver) => {
2678
3728
  });
2679
3729
  graphics.points.push({
2680
3730
  x: startCenter.x - 0.1,
2681
- y: startCenter.y + 0.1,
2682
- color: connectionColor,
2683
- label: [solver.currentConnection.connectionId, "start"].join("\n")
2684
- });
2685
- graphics.points.push({
2686
- x: endCenter.x - 0.1,
2687
- y: endCenter.y + 0.1,
2688
- color: connectionColor,
2689
- label: [solver.currentConnection.connectionId, "end"].join("\n")
2690
- });
2691
- }
2692
- for (const solvedRoute of solver.solvedRoutes) {
2693
- const connectionColor = getConnectionColor(
2694
- solvedRoute.connection.connectionId
2695
- );
2696
- const pathPoints = [];
2697
- for (const candidate of solvedRoute.path) {
2698
- const port = candidate.port;
2699
- pathPoints.push({ x: port.d.x, y: port.d.y });
2700
- }
2701
- if (pathPoints.length > 0) {
2702
- graphics.lines.push({
2703
- points: pathPoints,
2704
- strokeColor: connectionColor
2705
- });
2706
- }
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
+ });
2707
3741
  }
2708
3742
  const candidates = solver.candidateQueue.peekMany(10);
2709
3743
  for (let candidateIndex = 0; candidateIndex < candidates.length; candidateIndex++) {
@@ -2882,252 +3916,6 @@ var JumperGraphSolver = class extends HyperGraphSolver {
2882
3916
  }
2883
3917
  };
2884
3918
 
2885
- // lib/JumperGraphSolver/jumper-graph-generator/createConnectionPort.ts
2886
- var createConnectionPort = (portId, connectionRegion, boundaryRegion, portPosition) => {
2887
- const port = {
2888
- portId,
2889
- region1: connectionRegion,
2890
- region2: boundaryRegion,
2891
- d: { x: portPosition.x, y: portPosition.y }
2892
- };
2893
- connectionRegion.ports.push(port);
2894
- boundaryRegion.ports.push(port);
2895
- return port;
2896
- };
2897
-
2898
- // lib/JumperGraphSolver/jumper-graph-generator/createConnectionRegion.ts
2899
- var CONNECTION_REGION_SIZE = 0.4;
2900
- var createConnectionRegion = (regionId, x, y) => {
2901
- const halfSize = CONNECTION_REGION_SIZE / 2;
2902
- return {
2903
- regionId,
2904
- ports: [],
2905
- d: {
2906
- bounds: {
2907
- minX: x - halfSize,
2908
- maxX: x + halfSize,
2909
- minY: y - halfSize,
2910
- maxY: y + halfSize
2911
- },
2912
- center: { x, y },
2913
- isPad: false,
2914
- isConnectionRegion: true
2915
- }
2916
- };
2917
- };
2918
-
2919
- // lib/JumperGraphSolver/jumper-graph-generator/findBoundaryRegion.ts
2920
- var EPS = 0.01;
2921
- var clamp3 = (value, min, max) => {
2922
- return Math.max(min, Math.min(max, value));
2923
- };
2924
- var getBoundarySidesForPoint = (x, y, graphBounds) => {
2925
- const sides = [];
2926
- if (Math.abs(x - graphBounds.minX) < EPS) sides.push("left");
2927
- if (Math.abs(x - graphBounds.maxX) < EPS) sides.push("right");
2928
- if (Math.abs(y - graphBounds.maxY) < EPS) sides.push("top");
2929
- if (Math.abs(y - graphBounds.minY) < EPS) sides.push("bottom");
2930
- return sides;
2931
- };
2932
- var isPointOnSide = (p, side, b) => {
2933
- if (side === "left") return Math.abs(p.x - b.minX) < EPS;
2934
- if (side === "right") return Math.abs(p.x - b.maxX) < EPS;
2935
- if (side === "top") return Math.abs(p.y - b.maxY) < EPS;
2936
- return Math.abs(p.y - b.minY) < EPS;
2937
- };
2938
- var projectToSegment2 = (x, y, a, b) => {
2939
- const abx = b.x - a.x;
2940
- const aby = b.y - a.y;
2941
- const apx = x - a.x;
2942
- const apy = y - a.y;
2943
- const ab2 = abx * abx + aby * aby;
2944
- const t = ab2 > 0 ? clamp3((apx * abx + apy * aby) / ab2, 0, 1) : 0;
2945
- const px = a.x + t * abx;
2946
- const py = a.y + t * aby;
2947
- const dx = x - px;
2948
- const dy = y - py;
2949
- return {
2950
- x: px,
2951
- y: py,
2952
- d2: dx * dx + dy * dy
2953
- };
2954
- };
2955
- var getRegionBoundaryProjection = (x, y, region, graphBounds, preferredSides) => {
2956
- const polygon2 = region.d.polygon;
2957
- if (polygon2 && polygon2.length >= 3) {
2958
- const sideSet = new Set(preferredSides);
2959
- let best2 = null;
2960
- for (let i = 0; i < polygon2.length; i++) {
2961
- const a = polygon2[i];
2962
- const b = polygon2[(i + 1) % polygon2.length];
2963
- if (preferredSides.length > 0) {
2964
- const edgeOnPreferredSide = preferredSides.some(
2965
- (side) => isPointOnSide(a, side, graphBounds) && isPointOnSide(b, side, graphBounds) && sideSet.has(side)
2966
- );
2967
- if (!edgeOnPreferredSide) continue;
2968
- }
2969
- const p = projectToSegment2(x, y, a, b);
2970
- if (!best2 || p.d2 < best2.d2) {
2971
- best2 = p;
2972
- }
2973
- }
2974
- if (best2) return best2;
2975
- }
2976
- const bounds = region.d.bounds;
2977
- const sideCandidates = [];
2978
- if (preferredSides.length > 0) {
2979
- for (const side of preferredSides) {
2980
- if (side === "left") {
2981
- sideCandidates.push({
2982
- side,
2983
- x: bounds.minX,
2984
- y: clamp3(y, bounds.minY, bounds.maxY)
2985
- });
2986
- } else if (side === "right") {
2987
- sideCandidates.push({
2988
- side,
2989
- x: bounds.maxX,
2990
- y: clamp3(y, bounds.minY, bounds.maxY)
2991
- });
2992
- } else if (side === "top") {
2993
- sideCandidates.push({
2994
- side,
2995
- x: clamp3(x, bounds.minX, bounds.maxX),
2996
- y: bounds.maxY
2997
- });
2998
- } else {
2999
- sideCandidates.push({
3000
- side,
3001
- x: clamp3(x, bounds.minX, bounds.maxX),
3002
- y: bounds.minY
3003
- });
3004
- }
3005
- }
3006
- }
3007
- if (sideCandidates.length === 0) {
3008
- sideCandidates.push(
3009
- { side: "left", x: bounds.minX, y: clamp3(y, bounds.minY, bounds.maxY) },
3010
- {
3011
- side: "right",
3012
- x: bounds.maxX,
3013
- y: clamp3(y, bounds.minY, bounds.maxY)
3014
- },
3015
- { side: "top", x: clamp3(x, bounds.minX, bounds.maxX), y: bounds.maxY },
3016
- {
3017
- side: "bottom",
3018
- x: clamp3(x, bounds.minX, bounds.maxX),
3019
- y: bounds.minY
3020
- }
3021
- );
3022
- }
3023
- let best = null;
3024
- for (const c of sideCandidates) {
3025
- if (preferredSides.length > 0 && !preferredSides.includes(c.side)) continue;
3026
- const dx = x - c.x;
3027
- const dy = y - c.y;
3028
- const d2 = dx * dx + dy * dy;
3029
- if (!best || d2 < best.d2) {
3030
- best = { x: c.x, y: c.y, d2 };
3031
- }
3032
- }
3033
- return best;
3034
- };
3035
- var findBoundaryRegion = (x, y, regions, graphBounds) => {
3036
- const preferredSides = getBoundarySidesForPoint(x, y, graphBounds);
3037
- let closestRegion = null;
3038
- let closestDistance = Number.POSITIVE_INFINITY;
3039
- let closestPortPosition = { x, y };
3040
- for (const region of regions) {
3041
- if (region.d.isPad || region.d.isThroughJumper) continue;
3042
- const bounds = region.d.bounds;
3043
- 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;
3044
- if (!isOuterRegion) continue;
3045
- const projection = getRegionBoundaryProjection(
3046
- x,
3047
- y,
3048
- region,
3049
- graphBounds,
3050
- preferredSides
3051
- );
3052
- if (!projection) continue;
3053
- const dist = Math.sqrt(projection.d2);
3054
- if (dist < closestDistance) {
3055
- closestDistance = dist;
3056
- closestRegion = region;
3057
- closestPortPosition = { x: projection.x, y: projection.y };
3058
- }
3059
- }
3060
- if (closestRegion) {
3061
- return { region: closestRegion, portPosition: closestPortPosition };
3062
- }
3063
- return null;
3064
- };
3065
-
3066
- // lib/JumperGraphSolver/jumper-graph-generator/createGraphWithConnectionsFromBaseGraph.ts
3067
- var createGraphWithConnectionsFromBaseGraph = (baseGraph, xyConnections) => {
3068
- const regions = [...baseGraph.regions];
3069
- const ports = [...baseGraph.ports];
3070
- const connections = [];
3071
- const graphBounds = calculateGraphBounds(baseGraph.regions);
3072
- for (const xyConn of xyConnections) {
3073
- const { start, end, connectionId } = xyConn;
3074
- const startRegion = createConnectionRegion(
3075
- `conn:${connectionId}:start`,
3076
- start.x,
3077
- start.y
3078
- );
3079
- regions.push(startRegion);
3080
- const endRegion = createConnectionRegion(
3081
- `conn:${connectionId}:end`,
3082
- end.x,
3083
- end.y
3084
- );
3085
- regions.push(endRegion);
3086
- const startBoundary = findBoundaryRegion(
3087
- start.x,
3088
- start.y,
3089
- baseGraph.regions,
3090
- graphBounds
3091
- );
3092
- if (startBoundary) {
3093
- const startPort = createConnectionPort(
3094
- `conn:${connectionId}:start-port`,
3095
- startRegion,
3096
- startBoundary.region,
3097
- startBoundary.portPosition
3098
- );
3099
- ports.push(startPort);
3100
- }
3101
- const endBoundary = findBoundaryRegion(
3102
- end.x,
3103
- end.y,
3104
- baseGraph.regions,
3105
- graphBounds
3106
- );
3107
- if (endBoundary) {
3108
- const endPort = createConnectionPort(
3109
- `conn:${connectionId}:end-port`,
3110
- endRegion,
3111
- endBoundary.region,
3112
- endBoundary.portPosition
3113
- );
3114
- ports.push(endPort);
3115
- }
3116
- const connection = {
3117
- connectionId,
3118
- mutuallyConnectedNetworkId: connectionId,
3119
- startRegion,
3120
- endRegion
3121
- };
3122
- connections.push(connection);
3123
- }
3124
- return {
3125
- regions,
3126
- ports,
3127
- connections
3128
- };
3129
- };
3130
-
3131
3919
  // lib/JumperGraphSolver/jumper-graph-generator/generateSingleJumperRegions.ts
3132
3920
  var dims0603 = {
3133
3921
  padToPad: 1.65,
@@ -14383,7 +15171,7 @@ function setStepOfAllObjects(graphics, step) {
14383
15171
  }
14384
15172
  return graphics;
14385
15173
  }
14386
- var BaseSolver2 = class {
15174
+ var BaseSolver3 = class {
14387
15175
  MAX_ITERATIONS = 1e5;
14388
15176
  solved = false;
14389
15177
  failed = false;
@@ -14490,7 +15278,7 @@ function definePipelineStep(solverName, solverClass, getConstructorParams, opts
14490
15278
  onSolved: opts.onSolved
14491
15279
  };
14492
15280
  }
14493
- var BasePipelineSolver = class extends BaseSolver2 {
15281
+ var BasePipelineSolver = class extends BaseSolver3 {
14494
15282
  startTimeOfStage = {};
14495
15283
  endTimeOfStage = {};
14496
15284
  timeSpentOnStage = {};
@@ -14705,7 +15493,7 @@ var clampPointToBounds = (point4, bounds) => ({
14705
15493
  x: Math.min(bounds.maxX, Math.max(bounds.minX, point4.x)),
14706
15494
  y: Math.min(bounds.maxY, Math.max(bounds.minY, point4.y))
14707
15495
  });
14708
- var BuildRegionsSolver = class extends BaseSolver2 {
15496
+ var BuildRegionsSolver = class extends BaseSolver3 {
14709
15497
  input;
14710
15498
  output = null;
14711
15499
  constructor(input) {
@@ -15126,7 +15914,7 @@ var generateBoundaryPointsWithEdges = (params) => {
15126
15914
  hadCrossings: resolved.hadCrossings
15127
15915
  };
15128
15916
  };
15129
- var GeneratePointsSolver = class extends BaseSolver2 {
15917
+ var GeneratePointsSolver = class extends BaseSolver3 {
15130
15918
  input;
15131
15919
  output = null;
15132
15920
  constructor(input) {
@@ -15581,7 +16369,7 @@ var mergeCellsPolyanya = (params) => {
15581
16369
  const depths = liveCells.map(() => 0);
15582
16370
  return { cells: liveCells, depths };
15583
16371
  };
15584
- var MergeCellsSolver = class extends BaseSolver2 {
16372
+ var MergeCellsSolver = class extends BaseSolver3 {
15585
16373
  input;
15586
16374
  output = null;
15587
16375
  constructor(input) {
@@ -15839,7 +16627,7 @@ var filterTris = (params) => {
15839
16627
  return true;
15840
16628
  });
15841
16629
  };
15842
- var TriangulateSolver = class extends BaseSolver2 {
16630
+ var TriangulateSolver = class extends BaseSolver3 {
15843
16631
  input;
15844
16632
  output = null;
15845
16633
  constructor(input) {
@@ -23796,6 +24584,7 @@ function createConvexViaGraphFromXYConnections(xyConnections, viaTileOrProblem,
23796
24584
  }
23797
24585
  export {
23798
24586
  ConnectBuilder,
24587
+ HyperGraphSectionOptimizer,
23799
24588
  HyperGraphSolver,
23800
24589
  JUMPER_GRAPH_SOLVER_DEFAULTS,
23801
24590
  JumperGraphSolver,