@tscircuit/hypergraph 0.0.32 → 0.0.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -241,6 +241,13 @@ declare class HyperGraphSolver<RegionType extends Region = Region, RegionPortTyp
241
241
  * a route would require ripping other routes due to problematic crossings.
242
242
  */
243
243
  getRipsRequiredForPortUsage(_region: RegionType, _port1: RegionPortType, _port2: RegionPortType): RegionPortAssignment[];
244
+ /**
245
+ * OPTIONALLY OVERRIDE THIS
246
+ *
247
+ * Return true if using the candidate transition should incur ripCost even
248
+ * when there is no direct port-assignment conflict.
249
+ */
250
+ isRipRequiredForPortUsage(_region: RegionType, _port1: RegionPortType, _port2: RegionPortType): boolean;
244
251
  computeG(candidate: CandidateType): number;
245
252
  /**
246
253
  * Return a subset of the candidates for entering a region. These candidates
@@ -378,6 +385,7 @@ declare class JumperGraphSolver extends HyperGraphSolver<JRegion, JPort> {
378
385
  getPortUsagePenalty(port: JPort): number;
379
386
  computeIncreasedRegionCostIfPortsAreUsed(region: JRegion, port1: JPort, port2: JPort): number;
380
387
  getRipsRequiredForPortUsage(region: JRegion, port1: JPort, port2: JPort): RegionPortAssignment[];
388
+ isRipRequiredForPortUsage(region: JRegion, _port1: JPort, _port2: JPort): boolean;
381
389
  routeSolvedHook(solvedRoute: SolvedRoute): void;
382
390
  routeStartedHook(connection: Connection): void;
383
391
  visualize(): GraphicsObject;
package/dist/index.js CHANGED
@@ -31,6 +31,28 @@ var convertHyperGraphToSerializedHyperGraph = (graph) => {
31
31
  // lib/HyperGraphSolver.ts
32
32
  import { BaseSolver } from "@tscircuit/solver-utils";
33
33
 
34
+ // lib/convertSerializedConnectionsToConnections.ts
35
+ var convertSerializedConnectionsToConnections = (inputConnections, graph) => {
36
+ const connections = [];
37
+ for (const inputConn of inputConnections) {
38
+ if ("startRegionId" in inputConn) {
39
+ connections.push({
40
+ connectionId: inputConn.connectionId,
41
+ mutuallyConnectedNetworkId: inputConn.connectionId,
42
+ startRegion: graph.regions.find(
43
+ (region) => region.regionId === inputConn.startRegionId
44
+ ),
45
+ endRegion: graph.regions.find(
46
+ (region) => region.regionId === inputConn.endRegionId
47
+ )
48
+ });
49
+ } else {
50
+ connections.push(inputConn);
51
+ }
52
+ }
53
+ return connections;
54
+ };
55
+
34
56
  // lib/convertSerializedHyperGraphToHyperGraph.ts
35
57
  var convertSerializedHyperGraphToHyperGraph = (inputGraph) => {
36
58
  if (inputGraph.ports.length > 0 && "region1" in inputGraph.ports[0] && typeof inputGraph.ports[0].region1 === "object") {
@@ -65,28 +87,6 @@ var convertSerializedHyperGraphToHyperGraph = (inputGraph) => {
65
87
  };
66
88
  };
67
89
 
68
- // lib/convertSerializedConnectionsToConnections.ts
69
- var convertSerializedConnectionsToConnections = (inputConnections, graph) => {
70
- const connections = [];
71
- for (const inputConn of inputConnections) {
72
- if ("startRegionId" in inputConn) {
73
- connections.push({
74
- connectionId: inputConn.connectionId,
75
- mutuallyConnectedNetworkId: inputConn.connectionId,
76
- startRegion: graph.regions.find(
77
- (region) => region.regionId === inputConn.startRegionId
78
- ),
79
- endRegion: graph.regions.find(
80
- (region) => region.regionId === inputConn.endRegionId
81
- )
82
- });
83
- } else {
84
- connections.push(inputConn);
85
- }
86
- }
87
- return connections;
88
- };
89
-
90
90
  // lib/PriorityQueue.ts
91
91
  var PriorityQueue = class {
92
92
  // Store the heap as an array. Index 0 is the root (highest priority/smallest 'f').
@@ -328,6 +328,15 @@ var HyperGraphSolver = class extends BaseSolver {
328
328
  getRipsRequiredForPortUsage(_region, _port1, _port2) {
329
329
  return [];
330
330
  }
331
+ /**
332
+ * OPTIONALLY OVERRIDE THIS
333
+ *
334
+ * Return true if using the candidate transition should incur ripCost even
335
+ * when there is no direct port-assignment conflict.
336
+ */
337
+ isRipRequiredForPortUsage(_region, _port1, _port2) {
338
+ return false;
339
+ }
331
340
  computeG(candidate) {
332
341
  return candidate.parent.g + this.computeIncreasedRegionCostIfPortsAreUsed(
333
342
  candidate.lastRegion,
@@ -396,7 +405,11 @@ var HyperGraphSolver = class extends BaseSolver {
396
405
  const nextCandidatesByRegion = {};
397
406
  for (const port of currentRegion.ports) {
398
407
  if (port === currentCandidate.port) continue;
399
- const ripRequired = port.assignment && port.assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId;
408
+ const ripRequired = port.assignment && port.assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId || this.isRipRequiredForPortUsage(
409
+ currentRegion,
410
+ currentPort,
411
+ port
412
+ );
400
413
  const newCandidate = {
401
414
  port,
402
415
  hops: currentCandidate.hops + 1,
@@ -758,6 +771,174 @@ var rotateGraph90Degrees = (graph) => {
758
771
  return applyTransformToGraph(graph, matrix2);
759
772
  };
760
773
 
774
+ // lib/JumperGraphSolver/perimeterChordUtils.ts
775
+ function perimeterT(p, xmin, xmax, ymin, ymax) {
776
+ const W = xmax - xmin;
777
+ const H = ymax - ymin;
778
+ const eps = 1e-6;
779
+ if (Math.abs(p.y - ymax) < eps) {
780
+ return p.x - xmin;
781
+ }
782
+ if (Math.abs(p.x - xmax) < eps) {
783
+ return W + (ymax - p.y);
784
+ }
785
+ if (Math.abs(p.y - ymin) < eps) {
786
+ return W + H + (xmax - p.x);
787
+ }
788
+ if (Math.abs(p.x - xmin) < eps) {
789
+ return 2 * W + H + (p.y - ymin);
790
+ }
791
+ const distTop = Math.abs(p.y - ymax);
792
+ const distRight = Math.abs(p.x - xmax);
793
+ const distBottom = Math.abs(p.y - ymin);
794
+ const distLeft = Math.abs(p.x - xmin);
795
+ const minDist = Math.min(distTop, distRight, distBottom, distLeft);
796
+ if (minDist === distTop) {
797
+ return Math.max(0, Math.min(W, p.x - xmin));
798
+ }
799
+ if (minDist === distRight) {
800
+ return W + Math.max(0, Math.min(H, ymax - p.y));
801
+ }
802
+ if (minDist === distBottom) {
803
+ return W + H + Math.max(0, Math.min(W, xmax - p.x));
804
+ }
805
+ return 2 * W + H + Math.max(0, Math.min(H, p.y - ymin));
806
+ }
807
+ function areCoincident(t1, t2, eps = 1e-6) {
808
+ return Math.abs(t1 - t2) < eps;
809
+ }
810
+ function chordsCross(chord1, chord2) {
811
+ const [a, b] = chord1[0] < chord1[1] ? chord1 : [chord1[1], chord1[0]];
812
+ const [c, d] = chord2[0] < chord2[1] ? chord2 : [chord2[1], chord2[0]];
813
+ if (areCoincident(a, c) || areCoincident(a, d) || areCoincident(b, c) || areCoincident(b, d)) {
814
+ return false;
815
+ }
816
+ return a < c && c < b && b < d || c < a && a < d && d < b;
817
+ }
818
+
819
+ // lib/JumperGraphSolver/computeCrossingAssignments.ts
820
+ function computeCrossingAssignments(region, port1, port2) {
821
+ const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
822
+ const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
823
+ const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
824
+ const newChord = [t1, t2];
825
+ const crossingAssignments = [];
826
+ const assignments = region.assignments ?? [];
827
+ for (const assignment of assignments) {
828
+ const existingT1 = perimeterT(
829
+ assignment.regionPort1.d,
830
+ xmin,
831
+ xmax,
832
+ ymin,
833
+ ymax
834
+ );
835
+ const existingT2 = perimeterT(
836
+ assignment.regionPort2.d,
837
+ xmin,
838
+ xmax,
839
+ ymin,
840
+ ymax
841
+ );
842
+ const existingChord = [existingT1, existingT2];
843
+ if (chordsCross(newChord, existingChord)) {
844
+ crossingAssignments.push(assignment);
845
+ }
846
+ }
847
+ return crossingAssignments;
848
+ }
849
+
850
+ // lib/JumperGraphSolver/computeDifferentNetCrossings.ts
851
+ function computeDifferentNetCrossings(region, port1, port2) {
852
+ const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
853
+ const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
854
+ const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
855
+ const newChord = [t1, t2];
856
+ let crossings = 0;
857
+ const assignments = region.assignments ?? [];
858
+ for (const assignment of assignments) {
859
+ const existingT1 = perimeterT(
860
+ assignment.regionPort1.d,
861
+ xmin,
862
+ xmax,
863
+ ymin,
864
+ ymax
865
+ );
866
+ const existingT2 = perimeterT(
867
+ assignment.regionPort2.d,
868
+ xmin,
869
+ xmax,
870
+ ymin,
871
+ ymax
872
+ );
873
+ const existingChord = [existingT1, existingT2];
874
+ if (chordsCross(newChord, existingChord)) {
875
+ crossings++;
876
+ }
877
+ }
878
+ return crossings;
879
+ }
880
+
881
+ // lib/JumperGraphSolver/countInputConnectionCrossings.ts
882
+ function countInputConnectionCrossings(graph, connections) {
883
+ if (connections.length < 2) {
884
+ return 0;
885
+ }
886
+ let minX = Infinity;
887
+ let maxX = -Infinity;
888
+ let minY = Infinity;
889
+ let maxY = -Infinity;
890
+ for (const region of graph.regions) {
891
+ const jRegion = region;
892
+ if (jRegion.d?.bounds) {
893
+ minX = Math.min(minX, jRegion.d.bounds.minX);
894
+ maxX = Math.max(maxX, jRegion.d.bounds.maxX);
895
+ minY = Math.min(minY, jRegion.d.bounds.minY);
896
+ maxY = Math.max(maxY, jRegion.d.bounds.maxY);
897
+ } else if (jRegion.d?.center) {
898
+ minX = Math.min(minX, jRegion.d.center.x);
899
+ maxX = Math.max(maxX, jRegion.d.center.x);
900
+ minY = Math.min(minY, jRegion.d.center.y);
901
+ maxY = Math.max(maxY, jRegion.d.center.y);
902
+ }
903
+ }
904
+ const regionCenterMap = /* @__PURE__ */ new Map();
905
+ for (const region of graph.regions) {
906
+ const jRegion = region;
907
+ if (jRegion.d?.center) {
908
+ regionCenterMap.set(region.regionId, jRegion.d.center);
909
+ }
910
+ }
911
+ const chords = [];
912
+ for (const conn of connections) {
913
+ let startCenter;
914
+ let endCenter;
915
+ if ("startRegion" in conn && conn.startRegion) {
916
+ const startRegion = conn.startRegion;
917
+ const endRegion = conn.endRegion;
918
+ startCenter = startRegion.d?.center;
919
+ endCenter = endRegion.d?.center;
920
+ } else if ("startRegionId" in conn) {
921
+ startCenter = regionCenterMap.get(conn.startRegionId);
922
+ endCenter = regionCenterMap.get(conn.endRegionId);
923
+ }
924
+ if (!startCenter || !endCenter) {
925
+ continue;
926
+ }
927
+ const t1 = perimeterT(startCenter, minX, maxX, minY, maxY);
928
+ const t2 = perimeterT(endCenter, minX, maxX, minY, maxY);
929
+ chords.push([t1, t2]);
930
+ }
931
+ let crossings = 0;
932
+ for (let i = 0; i < chords.length; i++) {
933
+ for (let j = i + 1; j < chords.length; j++) {
934
+ if (chordsCross(chords[i], chords[j])) {
935
+ crossings++;
936
+ }
937
+ }
938
+ }
939
+ return crossings;
940
+ }
941
+
761
942
  // lib/JumperGraphSolver/visualizeJumperGraph.ts
762
943
  var visualizeJumperGraph = (graph, options) => {
763
944
  const graphics = {
@@ -971,174 +1152,6 @@ var visualizeJumperGraphSolver = (solver) => {
971
1152
  return graphics;
972
1153
  };
973
1154
 
974
- // lib/JumperGraphSolver/perimeterChordUtils.ts
975
- function perimeterT(p, xmin, xmax, ymin, ymax) {
976
- const W = xmax - xmin;
977
- const H = ymax - ymin;
978
- const eps = 1e-6;
979
- if (Math.abs(p.y - ymax) < eps) {
980
- return p.x - xmin;
981
- }
982
- if (Math.abs(p.x - xmax) < eps) {
983
- return W + (ymax - p.y);
984
- }
985
- if (Math.abs(p.y - ymin) < eps) {
986
- return W + H + (xmax - p.x);
987
- }
988
- if (Math.abs(p.x - xmin) < eps) {
989
- return 2 * W + H + (p.y - ymin);
990
- }
991
- const distTop = Math.abs(p.y - ymax);
992
- const distRight = Math.abs(p.x - xmax);
993
- const distBottom = Math.abs(p.y - ymin);
994
- const distLeft = Math.abs(p.x - xmin);
995
- const minDist = Math.min(distTop, distRight, distBottom, distLeft);
996
- if (minDist === distTop) {
997
- return Math.max(0, Math.min(W, p.x - xmin));
998
- }
999
- if (minDist === distRight) {
1000
- return W + Math.max(0, Math.min(H, ymax - p.y));
1001
- }
1002
- if (minDist === distBottom) {
1003
- return W + H + Math.max(0, Math.min(W, xmax - p.x));
1004
- }
1005
- return 2 * W + H + Math.max(0, Math.min(H, p.y - ymin));
1006
- }
1007
- function areCoincident(t1, t2, eps = 1e-6) {
1008
- return Math.abs(t1 - t2) < eps;
1009
- }
1010
- function chordsCross(chord1, chord2) {
1011
- const [a, b] = chord1[0] < chord1[1] ? chord1 : [chord1[1], chord1[0]];
1012
- const [c, d] = chord2[0] < chord2[1] ? chord2 : [chord2[1], chord2[0]];
1013
- if (areCoincident(a, c) || areCoincident(a, d) || areCoincident(b, c) || areCoincident(b, d)) {
1014
- return false;
1015
- }
1016
- return a < c && c < b && b < d || c < a && a < d && d < b;
1017
- }
1018
-
1019
- // lib/JumperGraphSolver/computeDifferentNetCrossings.ts
1020
- function computeDifferentNetCrossings(region, port1, port2) {
1021
- const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
1022
- const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
1023
- const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
1024
- const newChord = [t1, t2];
1025
- let crossings = 0;
1026
- const assignments = region.assignments ?? [];
1027
- for (const assignment of assignments) {
1028
- const existingT1 = perimeterT(
1029
- assignment.regionPort1.d,
1030
- xmin,
1031
- xmax,
1032
- ymin,
1033
- ymax
1034
- );
1035
- const existingT2 = perimeterT(
1036
- assignment.regionPort2.d,
1037
- xmin,
1038
- xmax,
1039
- ymin,
1040
- ymax
1041
- );
1042
- const existingChord = [existingT1, existingT2];
1043
- if (chordsCross(newChord, existingChord)) {
1044
- crossings++;
1045
- }
1046
- }
1047
- return crossings;
1048
- }
1049
-
1050
- // lib/JumperGraphSolver/computeCrossingAssignments.ts
1051
- function computeCrossingAssignments(region, port1, port2) {
1052
- const { minX: xmin, maxX: xmax, minY: ymin, maxY: ymax } = region.d.bounds;
1053
- const t1 = perimeterT(port1.d, xmin, xmax, ymin, ymax);
1054
- const t2 = perimeterT(port2.d, xmin, xmax, ymin, ymax);
1055
- const newChord = [t1, t2];
1056
- const crossingAssignments = [];
1057
- const assignments = region.assignments ?? [];
1058
- for (const assignment of assignments) {
1059
- const existingT1 = perimeterT(
1060
- assignment.regionPort1.d,
1061
- xmin,
1062
- xmax,
1063
- ymin,
1064
- ymax
1065
- );
1066
- const existingT2 = perimeterT(
1067
- assignment.regionPort2.d,
1068
- xmin,
1069
- xmax,
1070
- ymin,
1071
- ymax
1072
- );
1073
- const existingChord = [existingT1, existingT2];
1074
- if (chordsCross(newChord, existingChord)) {
1075
- crossingAssignments.push(assignment);
1076
- }
1077
- }
1078
- return crossingAssignments;
1079
- }
1080
-
1081
- // lib/JumperGraphSolver/countInputConnectionCrossings.ts
1082
- function countInputConnectionCrossings(graph, connections) {
1083
- if (connections.length < 2) {
1084
- return 0;
1085
- }
1086
- let minX = Infinity;
1087
- let maxX = -Infinity;
1088
- let minY = Infinity;
1089
- let maxY = -Infinity;
1090
- for (const region of graph.regions) {
1091
- const jRegion = region;
1092
- if (jRegion.d?.bounds) {
1093
- minX = Math.min(minX, jRegion.d.bounds.minX);
1094
- maxX = Math.max(maxX, jRegion.d.bounds.maxX);
1095
- minY = Math.min(minY, jRegion.d.bounds.minY);
1096
- maxY = Math.max(maxY, jRegion.d.bounds.maxY);
1097
- } else if (jRegion.d?.center) {
1098
- minX = Math.min(minX, jRegion.d.center.x);
1099
- maxX = Math.max(maxX, jRegion.d.center.x);
1100
- minY = Math.min(minY, jRegion.d.center.y);
1101
- maxY = Math.max(maxY, jRegion.d.center.y);
1102
- }
1103
- }
1104
- const regionCenterMap = /* @__PURE__ */ new Map();
1105
- for (const region of graph.regions) {
1106
- const jRegion = region;
1107
- if (jRegion.d?.center) {
1108
- regionCenterMap.set(region.regionId, jRegion.d.center);
1109
- }
1110
- }
1111
- const chords = [];
1112
- for (const conn of connections) {
1113
- let startCenter;
1114
- let endCenter;
1115
- if ("startRegion" in conn && conn.startRegion) {
1116
- const startRegion = conn.startRegion;
1117
- const endRegion = conn.endRegion;
1118
- startCenter = startRegion.d?.center;
1119
- endCenter = endRegion.d?.center;
1120
- } else if ("startRegionId" in conn) {
1121
- startCenter = regionCenterMap.get(conn.startRegionId);
1122
- endCenter = regionCenterMap.get(conn.endRegionId);
1123
- }
1124
- if (!startCenter || !endCenter) {
1125
- continue;
1126
- }
1127
- const t1 = perimeterT(startCenter, minX, maxX, minY, maxY);
1128
- const t2 = perimeterT(endCenter, minX, maxX, minY, maxY);
1129
- chords.push([t1, t2]);
1130
- }
1131
- let crossings = 0;
1132
- for (let i = 0; i < chords.length; i++) {
1133
- for (let j = i + 1; j < chords.length; j++) {
1134
- if (chordsCross(chords[i], chords[j])) {
1135
- crossings++;
1136
- }
1137
- }
1138
- }
1139
- return crossings;
1140
- }
1141
-
1142
1155
  // lib/JumperGraphSolver/JumperGraphSolver.ts
1143
1156
  var JUMPER_GRAPH_SOLVER_DEFAULTS = {
1144
1157
  portUsagePenalty: 0.034685181009478865,
@@ -1223,9 +1236,26 @@ var JumperGraphSolver = class extends HyperGraphSolver {
1223
1236
  }
1224
1237
  getRipsRequiredForPortUsage(region, port1, port2) {
1225
1238
  const crossingAssignments = computeCrossingAssignments(region, port1, port2);
1226
- return crossingAssignments.filter(
1239
+ const conflictingAssignments = crossingAssignments.filter(
1227
1240
  (a) => a.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId
1228
1241
  );
1242
+ if (!region.d.isThroughJumper) return conflictingAssignments;
1243
+ for (const assignment of region.assignments ?? []) {
1244
+ if (assignment.connection.mutuallyConnectedNetworkId === this.currentConnection.mutuallyConnectedNetworkId) {
1245
+ continue;
1246
+ }
1247
+ conflictingAssignments.push(assignment);
1248
+ }
1249
+ return conflictingAssignments;
1250
+ }
1251
+ isRipRequiredForPortUsage(region, _port1, _port2) {
1252
+ if (!region.d.isThroughJumper) return false;
1253
+ for (const assignment of region.assignments ?? []) {
1254
+ if (assignment.connection.mutuallyConnectedNetworkId !== this.currentConnection.mutuallyConnectedNetworkId) {
1255
+ return true;
1256
+ }
1257
+ }
1258
+ return false;
1229
1259
  }
1230
1260
  routeSolvedHook(solvedRoute) {
1231
1261
  }
@@ -14760,12 +14790,13 @@ function generateConvexViaTopologyRegions(opts) {
14760
14790
  const obstaclePolygons = viaRegions.map((r) => ({
14761
14791
  points: r.d.polygon
14762
14792
  }));
14763
- const solver = new ConvexRegionsSolver({
14793
+ const solverInput = {
14764
14794
  bounds,
14765
14795
  polygons: obstaclePolygons,
14766
14796
  clearance,
14767
14797
  concavityTolerance
14768
- });
14798
+ };
14799
+ const solver = new ConvexRegionsSolver(solverInput);
14769
14800
  solver.solve();
14770
14801
  const solverOutput = solver.getOutput();
14771
14802
  if (!solverOutput) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tscircuit/hypergraph",
3
3
  "main": "dist/index.js",
4
- "version": "0.0.32",
4
+ "version": "0.0.34",
5
5
  "type": "module",
6
6
  "scripts": {
7
7
  "start": "cosmos",
@@ -17,6 +17,7 @@
17
17
  "devDependencies": {
18
18
  "@biomejs/biome": "^2.3.11",
19
19
  "@tscircuit/find-convex-regions": "^0.0.7",
20
+ "@tscircuit/jumper-topology-generator": "^0.0.1",
20
21
  "@tscircuit/math-utils": "^0.0.29",
21
22
  "@types/bun": "latest",
22
23
  "bun-match-svg": "^0.0.15",