@tscircuit/hypergraph 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +224 -0
- package/dist/index.d.ts +360 -0
- package/dist/index.js +2318 -0
- package/package.json +32 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 tscircuit
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# @tscircuit/hypergraph
|
|
2
|
+
|
|
3
|
+
A generic A* pathfinding solver for routing connections through hypergraphs. Designed for circuit routing problems but extensible to any graph-based pathfinding scenario.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @tscircuit/hypergraph
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
HyperGraphSolver implements an A* algorithm that routes connections through a hypergraph data structure where:
|
|
14
|
+
|
|
15
|
+
- **Regions** are nodes representing spaces in your problem domain
|
|
16
|
+
- **Ports** are edges connecting two regions (the boundary between them)
|
|
17
|
+
- **Connections** define start and end regions that need to be linked
|
|
18
|
+
|
|
19
|
+
The solver finds optimal paths through the graph while handling conflicts via "ripping" - the ability to reroute existing paths when they block new connections.
|
|
20
|
+
|
|
21
|
+
## Core Types
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
interface Region {
|
|
25
|
+
regionId: string
|
|
26
|
+
ports: RegionPort[]
|
|
27
|
+
d: any // Domain-specific data (e.g., bounds, coordinates)
|
|
28
|
+
assignments?: RegionPortAssignment[]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
interface RegionPort {
|
|
32
|
+
portId: string
|
|
33
|
+
region1: Region
|
|
34
|
+
region2: Region
|
|
35
|
+
d: any // Domain-specific data (e.g., x, y position)
|
|
36
|
+
assignment?: PortAssignment
|
|
37
|
+
ripCount?: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface Connection {
|
|
41
|
+
connectionId: string
|
|
42
|
+
mutuallyConnectedNetworkId: string
|
|
43
|
+
startRegion: Region
|
|
44
|
+
endRegion: Region
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface HyperGraph {
|
|
48
|
+
regions: Region[]
|
|
49
|
+
ports: RegionPort[]
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Basic Usage
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { HyperGraphSolver } from "@tscircuit/hypergraph"
|
|
57
|
+
|
|
58
|
+
const solver = new HyperGraphSolver({
|
|
59
|
+
inputGraph: {
|
|
60
|
+
regions: [...],
|
|
61
|
+
ports: [...],
|
|
62
|
+
},
|
|
63
|
+
inputConnections: [
|
|
64
|
+
{ connectionId: "c1", mutuallyConnectedNetworkId: "net1", startRegion: regionA, endRegion: regionB },
|
|
65
|
+
{ connectionId: "c2", mutuallyConnectedNetworkId: "net2", startRegion: regionC, endRegion: regionD },
|
|
66
|
+
],
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
solver.solve()
|
|
70
|
+
|
|
71
|
+
if (solver.solved) {
|
|
72
|
+
console.log(solver.solvedRoutes)
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Configuration Options
|
|
77
|
+
|
|
78
|
+
| Option | Type | Default | Description |
|
|
79
|
+
|--------|------|---------|-------------|
|
|
80
|
+
| `inputGraph` | `HyperGraph \| SerializedHyperGraph` | required | The graph with regions and ports |
|
|
81
|
+
| `inputConnections` | `Connection[]` | required | Connections to route |
|
|
82
|
+
| `greedyMultiplier` | `number` | `1.0` | Weight for heuristic score (higher = greedier search) |
|
|
83
|
+
| `rippingEnabled` | `boolean` | `false` | Allow rerouting existing paths to resolve conflicts |
|
|
84
|
+
| `ripCost` | `number` | `0` | Additional cost penalty when ripping is required |
|
|
85
|
+
|
|
86
|
+
## Creating a Custom Solver
|
|
87
|
+
|
|
88
|
+
HyperGraphSolver is designed to be extended. Override these methods to customize behavior for your domain:
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { HyperGraphSolver, Region, RegionPort, Candidate } from "@tscircuit/hypergraph"
|
|
92
|
+
|
|
93
|
+
class MyCustomSolver extends HyperGraphSolver<MyRegion, MyPort> {
|
|
94
|
+
/**
|
|
95
|
+
* Estimate cost from a port to the destination region.
|
|
96
|
+
* Used for the A* heuristic.
|
|
97
|
+
*/
|
|
98
|
+
estimateCostToEnd(port: MyPort): number {
|
|
99
|
+
// Return estimated distance/cost to currentEndRegion
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Compute heuristic score for a candidate.
|
|
104
|
+
* Default uses estimateCostToEnd().
|
|
105
|
+
*/
|
|
106
|
+
computeH(candidate: Candidate<MyRegion, MyPort>): number {
|
|
107
|
+
return this.estimateCostToEnd(candidate.port)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Return penalty for using a port (e.g., if previously ripped).
|
|
112
|
+
*/
|
|
113
|
+
getPortUsagePenalty(port: MyPort): number {
|
|
114
|
+
return port.ripCount ?? 0
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Cost increase when routing through a region using two specific ports.
|
|
119
|
+
* Useful for penalizing crossings or congestion.
|
|
120
|
+
*/
|
|
121
|
+
computeIncreasedRegionCostIfPortsAreUsed(
|
|
122
|
+
region: MyRegion,
|
|
123
|
+
port1: MyPort,
|
|
124
|
+
port2: MyPort
|
|
125
|
+
): number {
|
|
126
|
+
return 0
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Detect assignments that conflict with using port1 and port2 together.
|
|
131
|
+
* Return assignments that must be ripped.
|
|
132
|
+
*/
|
|
133
|
+
getRipsRequiredForPortUsage(
|
|
134
|
+
region: MyRegion,
|
|
135
|
+
port1: MyPort,
|
|
136
|
+
port2: MyPort
|
|
137
|
+
): RegionPortAssignment[] {
|
|
138
|
+
return []
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Filter candidates entering a region to reduce redundant exploration.
|
|
143
|
+
*/
|
|
144
|
+
selectCandidatesForEnteringRegion(
|
|
145
|
+
candidates: Candidate<MyRegion, MyPort>[]
|
|
146
|
+
): Candidate<MyRegion, MyPort>[] {
|
|
147
|
+
return candidates
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Hook called after each route is solved.
|
|
152
|
+
*/
|
|
153
|
+
routeSolvedHook(solvedRoute: SolvedRoute): void {
|
|
154
|
+
// Custom logic after route completion
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
## Solver Output
|
|
160
|
+
|
|
161
|
+
After calling `solve()`, access results via:
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
solver.solved // boolean - true if all connections routed
|
|
165
|
+
solver.solvedRoutes // SolvedRoute[] - array of solved paths
|
|
166
|
+
solver.iterations // number - iterations used
|
|
167
|
+
solver.failed // boolean - true if max iterations exceeded
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Each `SolvedRoute` contains:
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
interface SolvedRoute {
|
|
174
|
+
connection: Connection // The connection that was routed
|
|
175
|
+
path: Candidate[] // Sequence of candidates forming the path
|
|
176
|
+
requiredRip: boolean // Whether ripping was needed
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Serialized Input Format
|
|
181
|
+
|
|
182
|
+
For JSON serialization, use ID references instead of object references:
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
interface SerializedHyperGraph {
|
|
186
|
+
regions: Array<{
|
|
187
|
+
regionId: string
|
|
188
|
+
d: any
|
|
189
|
+
}>
|
|
190
|
+
ports: Array<{
|
|
191
|
+
portId: string
|
|
192
|
+
region1Id: string
|
|
193
|
+
region2Id: string
|
|
194
|
+
d: any
|
|
195
|
+
}>
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
interface SerializedConnection {
|
|
199
|
+
connectionId: string
|
|
200
|
+
mutuallyConnectedNetworkId: string
|
|
201
|
+
startRegionId: string
|
|
202
|
+
endRegionId: string
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The solver automatically converts serialized inputs to hydrated object references.
|
|
207
|
+
|
|
208
|
+
## Algorithm
|
|
209
|
+
|
|
210
|
+
HyperGraphSolver uses A* pathfinding:
|
|
211
|
+
|
|
212
|
+
1. Initialize candidate queue with all ports of the start region
|
|
213
|
+
2. Process candidates in priority order (lowest `f = g + h * greedyMultiplier`)
|
|
214
|
+
3. When reaching the destination, trace back through parent pointers
|
|
215
|
+
4. Handle conflicts by ripping (removing conflicting routes and re-queuing them)
|
|
216
|
+
5. Continue until all connections are routed or max iterations exceeded
|
|
217
|
+
|
|
218
|
+
## Example Implementation
|
|
219
|
+
|
|
220
|
+
For a complete real-world implementation, see [JumperGraphSolver](./lib/JumperGraphSolver/JumperGraphSolver.ts) which extends HyperGraphSolver for PCB resistor network routing.
|
|
221
|
+
|
|
222
|
+
## License
|
|
223
|
+
|
|
224
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
import { GraphicsObject } from 'graphics-debug';
|
|
2
|
+
import { BaseSolver } from '@tscircuit/solver-utils';
|
|
3
|
+
import { Matrix } from 'transformation-matrix';
|
|
4
|
+
|
|
5
|
+
type PortId = string;
|
|
6
|
+
type RegionId = string;
|
|
7
|
+
type ConnectionId = string;
|
|
8
|
+
type NetworkId = string;
|
|
9
|
+
type GScore = number;
|
|
10
|
+
type RegionPort = {
|
|
11
|
+
portId: PortId;
|
|
12
|
+
region1: Region;
|
|
13
|
+
region2: Region;
|
|
14
|
+
d: any;
|
|
15
|
+
assignment?: PortAssignment;
|
|
16
|
+
/**
|
|
17
|
+
* The number of times this port has been ripped. Can be used to penalize
|
|
18
|
+
* ports that are likely to block off connections
|
|
19
|
+
*/
|
|
20
|
+
ripCount?: number;
|
|
21
|
+
};
|
|
22
|
+
type Region = {
|
|
23
|
+
regionId: RegionId;
|
|
24
|
+
ports: RegionPort[];
|
|
25
|
+
d: any;
|
|
26
|
+
assignments?: RegionPortAssignment[];
|
|
27
|
+
};
|
|
28
|
+
type PortAssignment = {
|
|
29
|
+
solvedRoute: SolvedRoute;
|
|
30
|
+
connection: Connection;
|
|
31
|
+
};
|
|
32
|
+
type RegionPortAssignment = {
|
|
33
|
+
regionPort1: RegionPort;
|
|
34
|
+
regionPort2: RegionPort;
|
|
35
|
+
region: Region;
|
|
36
|
+
connection: Connection;
|
|
37
|
+
solvedRoute: SolvedRoute;
|
|
38
|
+
};
|
|
39
|
+
type SolvedRoute = {
|
|
40
|
+
path: Candidate[];
|
|
41
|
+
connection: Connection;
|
|
42
|
+
requiredRip: boolean;
|
|
43
|
+
};
|
|
44
|
+
type Candidate<RegionType extends Region = Region, RegionPortType extends RegionPort = RegionPort> = {
|
|
45
|
+
port: RegionPortType;
|
|
46
|
+
g: number;
|
|
47
|
+
h: number;
|
|
48
|
+
f: number;
|
|
49
|
+
hops: number;
|
|
50
|
+
parent?: Candidate;
|
|
51
|
+
lastPort?: RegionPortType;
|
|
52
|
+
lastRegion?: RegionType;
|
|
53
|
+
nextRegion?: RegionType;
|
|
54
|
+
ripRequired: boolean;
|
|
55
|
+
};
|
|
56
|
+
type HyperGraph = {
|
|
57
|
+
ports: RegionPort[];
|
|
58
|
+
regions: Region[];
|
|
59
|
+
};
|
|
60
|
+
type SerializedGraphPort = Omit<RegionPort, "edges"> & {
|
|
61
|
+
portId: PortId;
|
|
62
|
+
region1Id: RegionId;
|
|
63
|
+
region2Id: RegionId;
|
|
64
|
+
};
|
|
65
|
+
type SerializedGraphRegion = Omit<Region, "points" | "assignments"> & {
|
|
66
|
+
pointIds: PortId[];
|
|
67
|
+
assignments?: SerializedRegionPortAssignment[];
|
|
68
|
+
};
|
|
69
|
+
type SerializedRegionPortAssignment = {
|
|
70
|
+
regionPort1Id: PortId;
|
|
71
|
+
regionPort2Id: PortId;
|
|
72
|
+
connectionId: ConnectionId;
|
|
73
|
+
};
|
|
74
|
+
type SerializedHyperGraph = {
|
|
75
|
+
ports: SerializedGraphPort[];
|
|
76
|
+
regions: SerializedGraphRegion[];
|
|
77
|
+
};
|
|
78
|
+
type Connection = {
|
|
79
|
+
connectionId: ConnectionId;
|
|
80
|
+
mutuallyConnectedNetworkId: NetworkId;
|
|
81
|
+
startRegion: Region;
|
|
82
|
+
endRegion: Region;
|
|
83
|
+
};
|
|
84
|
+
type SerializedConnection = {
|
|
85
|
+
connectionId: ConnectionId;
|
|
86
|
+
startRegionId: RegionId;
|
|
87
|
+
endRegionId: RegionId;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
type Bounds = {
|
|
91
|
+
minX: number;
|
|
92
|
+
minY: number;
|
|
93
|
+
maxX: number;
|
|
94
|
+
maxY: number;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
interface JRegion extends Region {
|
|
98
|
+
d: {
|
|
99
|
+
bounds: Bounds;
|
|
100
|
+
center: {
|
|
101
|
+
x: number;
|
|
102
|
+
y: number;
|
|
103
|
+
};
|
|
104
|
+
isPad: boolean;
|
|
105
|
+
isThroughJumper?: boolean;
|
|
106
|
+
isConnectionRegion?: boolean;
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
interface JPort extends RegionPort {
|
|
110
|
+
d: {
|
|
111
|
+
x: number;
|
|
112
|
+
y: number;
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
type JumperGraph = {
|
|
116
|
+
regions: JRegion[];
|
|
117
|
+
ports: JPort[];
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
declare const generateJumperX4Grid: ({ cols, rows, marginX, marginY, innerColChannelPointCount, innerRowChannelPointCount, regionsBetweenPads, outerPaddingX, outerPaddingY, outerChannelXPointCount, outerChannelYPointCount, orientation, center, }: {
|
|
121
|
+
cols: number;
|
|
122
|
+
rows: number;
|
|
123
|
+
marginX: number;
|
|
124
|
+
marginY: number;
|
|
125
|
+
innerColChannelPointCount?: number;
|
|
126
|
+
innerRowChannelPointCount?: number;
|
|
127
|
+
regionsBetweenPads?: boolean;
|
|
128
|
+
outerPaddingX?: number;
|
|
129
|
+
outerPaddingY?: number;
|
|
130
|
+
outerChannelXPointCount?: number;
|
|
131
|
+
outerChannelYPointCount?: number;
|
|
132
|
+
orientation?: "vertical" | "horizontal";
|
|
133
|
+
center?: {
|
|
134
|
+
x: number;
|
|
135
|
+
y: number;
|
|
136
|
+
};
|
|
137
|
+
}) => JumperGraph;
|
|
138
|
+
|
|
139
|
+
declare const generateJumperGrid: ({ cols, rows, marginX, marginY, innerColChannelPointCount, innerRowChannelPointCount, outerPaddingX, outerPaddingY, outerChannelXPoints, outerChannelYPoints, }: {
|
|
140
|
+
cols: number;
|
|
141
|
+
rows: number;
|
|
142
|
+
marginX: number;
|
|
143
|
+
marginY: number;
|
|
144
|
+
innerColChannelPointCount?: number;
|
|
145
|
+
innerRowChannelPointCount?: number;
|
|
146
|
+
outerPaddingX?: number;
|
|
147
|
+
outerPaddingY?: number;
|
|
148
|
+
outerChannelXPoints?: number;
|
|
149
|
+
outerChannelYPoints?: number;
|
|
150
|
+
}) => {
|
|
151
|
+
regions: JRegion[];
|
|
152
|
+
ports: JPort[];
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
type XYConnection = {
|
|
156
|
+
start: {
|
|
157
|
+
x: number;
|
|
158
|
+
y: number;
|
|
159
|
+
};
|
|
160
|
+
end: {
|
|
161
|
+
x: number;
|
|
162
|
+
y: number;
|
|
163
|
+
};
|
|
164
|
+
connectionId: string;
|
|
165
|
+
};
|
|
166
|
+
type JumperGraphWithConnections = JumperGraph & {
|
|
167
|
+
connections: Connection[];
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* Creates a new graph from a base graph with additional connection regions at
|
|
171
|
+
* specified positions on the boundary. Connection regions are 0.4x0.4 pseudo-regions
|
|
172
|
+
* that contain one port point connecting them to the grid boundary.
|
|
173
|
+
*/
|
|
174
|
+
declare const createGraphWithConnectionsFromBaseGraph: (baseGraph: JumperGraph, xyConnections: XYConnection[]) => JumperGraphWithConnections;
|
|
175
|
+
|
|
176
|
+
type Node = {
|
|
177
|
+
f: number;
|
|
178
|
+
[key: string]: any;
|
|
179
|
+
};
|
|
180
|
+
declare class PriorityQueue<T extends Node = Node> {
|
|
181
|
+
private heap;
|
|
182
|
+
private maxSize;
|
|
183
|
+
/**
|
|
184
|
+
* Creates a new Priority Queue.
|
|
185
|
+
* @param nodes An optional initial array of nodes to populate the queue.
|
|
186
|
+
* @param maxSize The maximum number of elements the queue can hold. Defaults to 10,000.
|
|
187
|
+
*/
|
|
188
|
+
constructor(nodes?: T[], maxSize?: number);
|
|
189
|
+
/**
|
|
190
|
+
* Returns the number of elements currently in the queue.
|
|
191
|
+
*/
|
|
192
|
+
get size(): number;
|
|
193
|
+
/**
|
|
194
|
+
* Checks if the queue is empty.
|
|
195
|
+
*/
|
|
196
|
+
isEmpty(): boolean;
|
|
197
|
+
/**
|
|
198
|
+
* Returns the node with the highest priority (smallest 'f') without removing it.
|
|
199
|
+
* Returns null if the queue is empty.
|
|
200
|
+
* @returns The highest priority node or null.
|
|
201
|
+
*/
|
|
202
|
+
peek(): T | null;
|
|
203
|
+
/**
|
|
204
|
+
* Returns up to N nodes with the highest priority (smallest 'f') without removing them.
|
|
205
|
+
* Nodes are returned sorted by priority (smallest 'f' first).
|
|
206
|
+
* @param count Maximum number of nodes to return.
|
|
207
|
+
* @returns Array of nodes sorted by priority.
|
|
208
|
+
*/
|
|
209
|
+
peekMany(count: number): T[];
|
|
210
|
+
/**
|
|
211
|
+
* Removes and returns the node with the highest priority (smallest 'f').
|
|
212
|
+
* Returns null if the queue is empty.
|
|
213
|
+
* Maintains the heap property.
|
|
214
|
+
* @returns The highest priority node or null.
|
|
215
|
+
*/
|
|
216
|
+
dequeue(): T | null;
|
|
217
|
+
/**
|
|
218
|
+
* Adds a new node to the queue.
|
|
219
|
+
* Maintains the heap property.
|
|
220
|
+
* If the queue is full (at maxSize), the node is not added.
|
|
221
|
+
* @param node The node to add.
|
|
222
|
+
*/
|
|
223
|
+
enqueue(node: T): void;
|
|
224
|
+
/**
|
|
225
|
+
* Moves the node at the given index up the heap to maintain the heap property.
|
|
226
|
+
* @param index The index of the node to sift up.
|
|
227
|
+
*/
|
|
228
|
+
private _siftUp;
|
|
229
|
+
/**
|
|
230
|
+
* Moves the node at the given index down the heap to maintain the heap property.
|
|
231
|
+
* @param index The index of the node to sift down.
|
|
232
|
+
*/
|
|
233
|
+
private _siftDown;
|
|
234
|
+
/**
|
|
235
|
+
* Swaps two elements in the heap array.
|
|
236
|
+
* @param i Index of the first element.
|
|
237
|
+
* @param j Index of the second element.
|
|
238
|
+
*/
|
|
239
|
+
private _swap;
|
|
240
|
+
/** Calculates the parent index of a node. */
|
|
241
|
+
private _parentIndex;
|
|
242
|
+
/** Calculates the left child index of a node. */
|
|
243
|
+
private _leftChildIndex;
|
|
244
|
+
/** Calculates the right child index of a node. */
|
|
245
|
+
private _rightChildIndex;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
declare class HyperGraphSolver<RegionType extends Region = Region, RegionPortType extends RegionPort = RegionPort, CandidateType extends Candidate<RegionType, RegionPortType> = Candidate<RegionType, RegionPortType>> extends BaseSolver {
|
|
249
|
+
input: {
|
|
250
|
+
inputGraph: HyperGraph | SerializedHyperGraph;
|
|
251
|
+
inputConnections: (Connection | SerializedConnection)[];
|
|
252
|
+
greedyMultiplier?: number;
|
|
253
|
+
rippingEnabled?: boolean;
|
|
254
|
+
ripCost?: number;
|
|
255
|
+
};
|
|
256
|
+
graph: HyperGraph;
|
|
257
|
+
connections: Connection[];
|
|
258
|
+
candidateQueue: PriorityQueue<Candidate>;
|
|
259
|
+
unprocessedConnections: Connection[];
|
|
260
|
+
solvedRoutes: SolvedRoute[];
|
|
261
|
+
currentConnection: Connection | null;
|
|
262
|
+
currentEndRegion: Region | null;
|
|
263
|
+
greedyMultiplier: number;
|
|
264
|
+
rippingEnabled: boolean;
|
|
265
|
+
ripCost: number;
|
|
266
|
+
lastCandidate: Candidate | null;
|
|
267
|
+
visitedPointsForCurrentConnection: Map<PortId, GScore>;
|
|
268
|
+
constructor(input: {
|
|
269
|
+
inputGraph: HyperGraph | SerializedHyperGraph;
|
|
270
|
+
inputConnections: (Connection | SerializedConnection)[];
|
|
271
|
+
greedyMultiplier?: number;
|
|
272
|
+
rippingEnabled?: boolean;
|
|
273
|
+
ripCost?: number;
|
|
274
|
+
});
|
|
275
|
+
computeH(candidate: CandidateType): number;
|
|
276
|
+
/**
|
|
277
|
+
* OVERRIDE THIS
|
|
278
|
+
*
|
|
279
|
+
* Return the estimated remaining cost to the end of the route. You must
|
|
280
|
+
* first understand the UNIT of your costs. If it's distance, then this could
|
|
281
|
+
* be something like distance(port, this.currentEndRegion.d.center)
|
|
282
|
+
*/
|
|
283
|
+
estimateCostToEnd(port: RegionPortType): number;
|
|
284
|
+
/**
|
|
285
|
+
* OPTIONALLY OVERRIDE THIS
|
|
286
|
+
*
|
|
287
|
+
* This is a penalty for using a port that is not relative to a connection,
|
|
288
|
+
* e.g. maybe this port is in a special area of congestion. Use this to
|
|
289
|
+
* penalize ports that are e.g. likely to block off connections, you may want
|
|
290
|
+
* to use port.ripCount to help determine this penalty, or you can use port
|
|
291
|
+
* position, region volume etc.
|
|
292
|
+
*/
|
|
293
|
+
getPortUsagePenalty(port: RegionPortType): number;
|
|
294
|
+
/**
|
|
295
|
+
* OVERRIDE THIS
|
|
296
|
+
*
|
|
297
|
+
* Return the cost of using two ports in the region, make sure to consider
|
|
298
|
+
* existing assignments. You may use this to penalize intersections
|
|
299
|
+
*/
|
|
300
|
+
computeIncreasedRegionCostIfPortsAreUsed(region: RegionType, port1: RegionPortType, port2: RegionPortType): number;
|
|
301
|
+
/**
|
|
302
|
+
* OPTIONALLY OVERRIDE THIS
|
|
303
|
+
*
|
|
304
|
+
* Return the assignments that would need to be ripped if the given ports
|
|
305
|
+
* are used together in the region. This is used to determine if adopting
|
|
306
|
+
* a route would require ripping other routes due to problematic crossings.
|
|
307
|
+
*/
|
|
308
|
+
getRipsRequiredForPortUsage(_region: RegionType, _port1: RegionPortType, _port2: RegionPortType): RegionPortAssignment[];
|
|
309
|
+
computeG(candidate: CandidateType): number;
|
|
310
|
+
/**
|
|
311
|
+
* Return a subset of the candidates for entering a region. These candidates
|
|
312
|
+
* are all possible ways to enter the region- you can e.g. return the middle
|
|
313
|
+
* port to make it so that you're not queueing candidates that are likely
|
|
314
|
+
* redundant.
|
|
315
|
+
*/
|
|
316
|
+
selectCandidatesForEnteringRegion(candidates: Candidate[]): Candidate[];
|
|
317
|
+
getNextCandidates(currentCandidate: CandidateType): CandidateType[];
|
|
318
|
+
processSolvedRoute(finalCandidate: CandidateType): void;
|
|
319
|
+
/**
|
|
320
|
+
* OPTIONALLY OVERRIDE THIS
|
|
321
|
+
*
|
|
322
|
+
* You can override this to perform actions after a route is solved, e.g.
|
|
323
|
+
* you may want to detect if a solvedRoute.requiredRip is true, in which
|
|
324
|
+
* case you might want to execute a "random rip" to avoid loops or check
|
|
325
|
+
* if we've exceeded a maximum number of rips.
|
|
326
|
+
*
|
|
327
|
+
* You can also use this to shuffle unprocessed routes if a rip occurred, this
|
|
328
|
+
* can also help avoid loops
|
|
329
|
+
*/
|
|
330
|
+
routeSolvedHook(solvedRoute: SolvedRoute): void;
|
|
331
|
+
ripSolvedRoute(solvedRoute: SolvedRoute): void;
|
|
332
|
+
beginNewConnection(): void;
|
|
333
|
+
_step(): void;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
declare class JumperGraphSolver extends HyperGraphSolver<JRegion, JPort> {
|
|
337
|
+
UNIT_OF_COST: string;
|
|
338
|
+
constructor(input: {
|
|
339
|
+
inputGraph: HyperGraph | SerializedHyperGraph;
|
|
340
|
+
inputConnections: (Connection | SerializedConnection)[];
|
|
341
|
+
});
|
|
342
|
+
estimateCostToEnd(port: JPort): number;
|
|
343
|
+
getPortUsagePenalty(port: JPort): number;
|
|
344
|
+
computeIncreasedRegionCostIfPortsAreUsed(region: JRegion, port1: JPort, port2: JPort): number;
|
|
345
|
+
getRipsRequiredForPortUsage(region: JRegion, port1: JPort, port2: JPort): RegionPortAssignment[];
|
|
346
|
+
routeSolvedHook(solvedRoute: SolvedRoute): void;
|
|
347
|
+
visualize(): GraphicsObject;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Applies a transformation matrix to all points in a graph.
|
|
352
|
+
* Transforms region bounds, region centers, and port positions.
|
|
353
|
+
*/
|
|
354
|
+
declare const applyTransformToGraph: (graph: JumperGraph, matrix: Matrix) => JumperGraph;
|
|
355
|
+
/**
|
|
356
|
+
* Rotates a graph 90 degrees clockwise around its center.
|
|
357
|
+
*/
|
|
358
|
+
declare const rotateGraph90Degrees: (graph: JumperGraph) => JumperGraph;
|
|
359
|
+
|
|
360
|
+
export { HyperGraphSolver, JumperGraphSolver, type JumperGraphWithConnections, type XYConnection, applyTransformToGraph, createGraphWithConnectionsFromBaseGraph, generateJumperGrid, generateJumperX4Grid, rotateGraph90Degrees };
|