A generic A* pathfinding solver for routing connections through hypergraphs. Designed for circuit routing problems but extensible to any graph-based pathfinding scenario.
npm install @tscircuit/hypergraphHyperGraphSolver implements an A* algorithm that routes connections through a hypergraph data structure where:
- Regions are nodes representing spaces in your problem domain
- Ports are edges connecting two regions (the boundary between them)
- Connections define start and end regions that need to be linked
The solver finds optimal paths through the graph while handling conflicts via "ripping" - the ability to reroute existing paths when they block new connections.
interface Region {
regionId: string
ports: RegionPort[]
d: any // Domain-specific data (e.g., bounds, coordinates)
assignments?: RegionPortAssignment[]
}
interface RegionPort {
portId: string
region1: Region
region2: Region
d: any // Domain-specific data (e.g., x, y position)
assignment?: PortAssignment
ripCount?: number
}
interface Connection {
connectionId: string
mutuallyConnectedNetworkId: string
startRegion: Region
endRegion: Region
}
interface HyperGraph {
regions: Region[]
ports: RegionPort[]
}import { HyperGraphSolver } from "@tscircuit/hypergraph"
const solver = new HyperGraphSolver({
inputGraph: {
regions: [...],
ports: [...],
},
inputConnections: [
{ connectionId: "c1", mutuallyConnectedNetworkId: "net1", startRegion: regionA, endRegion: regionB },
{ connectionId: "c2", mutuallyConnectedNetworkId: "net2", startRegion: regionC, endRegion: regionD },
],
})
solver.solve()
if (solver.solved) {
console.log(solver.solvedRoutes)
}| Option | Type | Default | Description |
|---|---|---|---|
inputGraph |
HyperGraph | SerializedHyperGraph |
required | The graph with regions and ports |
inputConnections |
Connection[] |
required | Connections to route |
greedyMultiplier |
number |
1.0 |
Weight for heuristic score (higher = greedier search) |
rippingEnabled |
boolean |
false |
Allow rerouting existing paths to resolve conflicts |
ripCost |
number |
0 |
Additional cost penalty when ripping is required |
HyperGraphSolver is designed to be extended. Override these methods to customize behavior for your domain:
import { HyperGraphSolver, Region, RegionPort, Candidate } from "@tscircuit/hypergraph"
class MyCustomSolver extends HyperGraphSolver<MyRegion, MyPort> {
/**
* Estimate cost from a port to the destination region.
* Used for the A* heuristic.
*/
estimateCostToEnd(port: MyPort): number {
// Return estimated distance/cost to currentEndRegion
}
/**
* Compute heuristic score for a candidate.
* Default uses estimateCostToEnd().
*/
computeH(candidate: Candidate<MyRegion, MyPort>): number {
return this.estimateCostToEnd(candidate.port)
}
/**
* Return penalty for using a port (e.g., if previously ripped).
*/
getPortUsagePenalty(port: MyPort): number {
return port.ripCount ?? 0
}
/**
* Cost increase when routing through a region using two specific ports.
* Useful for penalizing crossings or congestion.
*/
computeIncreasedRegionCostIfPortsAreUsed(
region: MyRegion,
port1: MyPort,
port2: MyPort
): number {
return 0
}
/**
* Detect assignments that conflict with using port1 and port2 together.
* Return assignments that must be ripped.
*/
getRipsRequiredForPortUsage(
region: MyRegion,
port1: MyPort,
port2: MyPort
): RegionPortAssignment[] {
return []
}
/**
* Filter candidates entering a region to reduce redundant exploration.
*/
selectCandidatesForEnteringRegion(
candidates: Candidate<MyRegion, MyPort>[]
): Candidate<MyRegion, MyPort>[] {
return candidates
}
/**
* Hook called after each route is solved.
*/
routeSolvedHook(solvedRoute: SolvedRoute): void {
// Custom logic after route completion
}
}After calling solve(), access results via:
solver.solved // boolean - true if all connections routed
solver.solvedRoutes // SolvedRoute[] - array of solved paths
solver.iterations // number - iterations used
solver.failed // boolean - true if max iterations exceededEach SolvedRoute contains:
interface SolvedRoute {
connection: Connection // The connection that was routed
path: Candidate[] // Sequence of candidates forming the path
requiredRip: boolean // Whether ripping was needed
}For JSON serialization, use ID references instead of object references:
interface SerializedHyperGraph {
regions: Array<{
regionId: string
d: any
}>
ports: Array<{
portId: string
region1Id: string
region2Id: string
d: any
}>
}
interface SerializedConnection {
connectionId: string
mutuallyConnectedNetworkId: string
startRegionId: string
endRegionId: string
}The solver automatically converts serialized inputs to hydrated object references.
HyperGraphSolver uses A* pathfinding:
- Initialize candidate queue with all ports of the start region
- Process candidates in priority order (lowest
f = g + h * greedyMultiplier) - When reaching the destination, trace back through parent pointers
- Handle conflicts by ripping (removing conflicting routes and re-queuing them)
- Continue until all connections are routed or max iterations exceeded
For a complete real-world implementation, see JumperGraphSolver which extends HyperGraphSolver for PCB resistor network routing.
MIT