Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions dictionary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Tiny Hypergraph Vocabulary

The `lib2` code uses a small vocabulary so names stay predictable. New names
should be built from these roots unless the domain clearly needs a new word.

1. `graph` - the full hypergraph input, loaded model, or output.
2. `region` - a traversable area in the graph.
3. `port` - a boundary point connecting regions.
4. `route` - one connection to solve from start port to end port.
5. `net` - electrical ownership for routes, ports, and reserved regions.
6. `hop` - one searchable state of port plus next region.
7. `path` - the ordered ports and regions chosen for a route.
8. `segment` - one routed edge inside a region.
9. `cost` - routing penalty or quality score.
10. `state` - mutable solver working data.
11. `queue` - frontier used by route search.
12. `cache` - stored derived data reused during solving.
13. `solve` - run routing work to completion or failure.
14. `parse` - convert boundary input into a trusted shape.
15. `load` - adapt serialized graph data to solver topology/problem data.
16. `result` - explicit success or failure value.
17. `error` - typed expected failure information.
18. `section` - a selected subgraph span optimized after full-graph routing.
19. `candidate` - one option considered during route or section search.
20. `srj` - simple route JSON style dataset/input family.

Example: `srjGraphResult` is acceptable because it combines `srj`, `graph`,
and `result`. Avoid inventing synonyms like `node`, `vertex`, `task`,
`payload`, or `manager` when one of these roots fits. `lib2` is allowed as the
version boundary name; it is not a domain root.
28 changes: 28 additions & 0 deletions idea.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# Lib2 Idea

Goal: create `lib2`, a clearer TypeScript-standard entrypoint for tiny
hypergraph solving while preserving current benchmark behavior.

The first version keeps the current hot solver as the execution engine. That is
intentional: the existing A* loop, intersection counting, typed-array state, and
rerip policy are performance-sensitive and already covered by regression tests.
`lib2` starts by making the boundary cleaner:

- typed `Result` values for expected parse/solve failures
- explicit serialized-graph parsing at the edge
- a named `TinyHyperGraphSolver2` facade that can be benchmarked separately
- benchmark flags for raw core comparisons
- a small vocabulary documented in `dictionary.md`

Next improvement target after this slice: move one cohesive hot module at a time
behind lib2 names, starting with region cache operations. That module has a good
chance to improve both readability and performance because the current append
path reallocates several typed arrays per committed segment.

Success rule for this slice:

- original test suite stays green
- new lib2 tests pass
- SRJ13 and CM5IO raw benchmarks can run with `--solver lib2`
- representative lib2 benchmark stats are the same as core within normal local
noise because the engine is still shared
22 changes: 11 additions & 11 deletions lib/compat/convertToSerializedHyperGraph.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { SerializedHyperGraph } from "@tscircuit/hypergraph"
import type { TinyHyperGraphSolver } from "../index"
import type { TinyHyperGraphSolverView } from "../solver-view"
import { getAvailableZFromMask, getZLayerLabel } from "../layerLabels"
import type { PortId, RegionId } from "../types"

Expand Down Expand Up @@ -30,7 +30,7 @@ const normalizePortIdFallback = (value: string) =>
value.includes("::") ? value.slice(0, value.indexOf("::")) : value

const getSerializedRegionId = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
regionId: RegionId,
): string => {
const metadata = solver.topology.regionMetadata?.[regionId]
Expand All @@ -52,7 +52,7 @@ const getSerializedRegionId = (
}

const getSerializedPortId = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
portId: PortId,
): string => {
const metadata = solver.topology.portMetadata?.[portId]
Expand All @@ -70,7 +70,7 @@ const getSerializedPortId = (
}

const getSerializedRegionData = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
regionId: RegionId,
): Record<string, unknown> => {
const data = toObjectRecord(solver.topology.regionMetadata?.[regionId])
Expand Down Expand Up @@ -110,7 +110,7 @@ const getSerializedRegionData = (
}

const getSerializedPortData = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
portId: PortId,
): Record<string, unknown> => {
const data = toObjectRecord(solver.topology.portMetadata?.[portId])
Expand All @@ -133,7 +133,7 @@ const getSerializedPortData = (
}

const getRouteSegmentsByRoute = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
): Array<RouteSegment[]> => {
const routeSegmentsByRoute = Array.from(
{ length: solver.problem.routeCount },
Expand All @@ -154,7 +154,7 @@ const getRouteSegmentsByRoute = (
}

const getOppositeRegionIdForPort = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
portId: PortId,
traversedRegionId: RegionId,
): RegionId => {
Expand All @@ -173,7 +173,7 @@ const getOppositeRegionIdForPort = (
}

const getOrderedRoutePath = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
routeId: number,
routeSegments: RouteSegment[],
): {
Expand Down Expand Up @@ -267,7 +267,7 @@ const getOrderedRoutePath = (
}

const getSerializedConnection = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
routeId: number,
startRegionId: string,
endRegionId: string,
Expand Down Expand Up @@ -301,7 +301,7 @@ const getSerializedConnection = (
}

const getSerializedSolvedRoute = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
routeId: number,
routeSegments: RouteSegment[],
): {
Expand Down Expand Up @@ -379,7 +379,7 @@ const getSerializedSolvedRoute = (
}

export const convertToSerializedHyperGraph = (
solver: TinyHyperGraphSolver,
solver: TinyHyperGraphSolverView,
): SerializedHyperGraph => {
if (!solver.solved || solver.failed) {
throw new Error(
Expand Down
100 changes: 100 additions & 0 deletions lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ import {
getStaticallyUnroutableRoutes,
getStaticReachabilityError,
} from "./static-reachability"
import {
buildLayeredSearchMap,
findLayeredRouteCorridor,
type LayeredSearchMap,
} from "../lib2/layered-search-map"
import type {
HopId,
NetId,
Expand Down Expand Up @@ -238,6 +243,9 @@ export interface TinyHyperGraphSolverOptions {
STATIC_REACHABILITY_PRECHECK_MAX_HOPS?: number
ACCEPT_BEST_SOLUTION_ON_TIMEOUT?: boolean
GREEDY_FINAL_ROUTE_ITERS?: number
USE_LAYERED_ROUTE_SEARCH?: boolean
LAYERED_SEARCH_BUCKET_SIZE?: number
LAYERED_SEARCH_INCLUDE_ADJACENT_COARSE_REGIONS?: boolean
}

export interface TinyHyperGraphSolverOptionTarget {
Expand All @@ -255,6 +263,9 @@ export interface TinyHyperGraphSolverOptionTarget {
STATIC_REACHABILITY_PRECHECK_MAX_HOPS: number
ACCEPT_BEST_SOLUTION_ON_TIMEOUT: boolean
GREEDY_FINAL_ROUTE_ITERS: number
USE_LAYERED_ROUTE_SEARCH?: boolean
LAYERED_SEARCH_BUCKET_SIZE?: number
LAYERED_SEARCH_INCLUDE_ADJACENT_COARSE_REGIONS?: boolean
}

export const applyTinyHyperGraphSolverOptions = (
Expand Down Expand Up @@ -310,6 +321,16 @@ export const applyTinyHyperGraphSolverOptions = (
if (options.GREEDY_FINAL_ROUTE_ITERS !== undefined) {
solver.GREEDY_FINAL_ROUTE_ITERS = options.GREEDY_FINAL_ROUTE_ITERS
}
if (options.USE_LAYERED_ROUTE_SEARCH !== undefined) {
solver.USE_LAYERED_ROUTE_SEARCH = options.USE_LAYERED_ROUTE_SEARCH
}
if (options.LAYERED_SEARCH_BUCKET_SIZE !== undefined) {
solver.LAYERED_SEARCH_BUCKET_SIZE = options.LAYERED_SEARCH_BUCKET_SIZE
}
if (options.LAYERED_SEARCH_INCLUDE_ADJACENT_COARSE_REGIONS !== undefined) {
solver.LAYERED_SEARCH_INCLUDE_ADJACENT_COARSE_REGIONS =
options.LAYERED_SEARCH_INCLUDE_ADJACENT_COARSE_REGIONS
}
}

export const getTinyHyperGraphSolverOptions = (
Expand All @@ -330,6 +351,10 @@ export const getTinyHyperGraphSolverOptions = (
solver.STATIC_REACHABILITY_PRECHECK_MAX_HOPS,
ACCEPT_BEST_SOLUTION_ON_TIMEOUT: solver.ACCEPT_BEST_SOLUTION_ON_TIMEOUT,
GREEDY_FINAL_ROUTE_ITERS: solver.GREEDY_FINAL_ROUTE_ITERS,
USE_LAYERED_ROUTE_SEARCH: solver.USE_LAYERED_ROUTE_SEARCH,
LAYERED_SEARCH_BUCKET_SIZE: solver.LAYERED_SEARCH_BUCKET_SIZE,
LAYERED_SEARCH_INCLUDE_ADJACENT_COARSE_REGIONS:
solver.LAYERED_SEARCH_INCLUDE_ADJACENT_COARSE_REGIONS,
})

const compareCandidatesByF = (left: Candidate, right: Candidate) =>
Expand Down Expand Up @@ -357,6 +382,8 @@ export class TinyHyperGraphSolver extends BaseSolver {
layerMask: 0,
entryExitLayerChanges: 0,
}
private layeredSearchMap?: LayeredSearchMap
private layeredSearchAllowedFineRegionMask?: Uint8Array

DISTANCE_TO_COST = 0.05 // 50mm = 1 cost unit (1 cost unit ~ 100% chance of failure)
minViaPadDiameter = DEFAULT_MIN_VIA_PAD_DIAMETER
Expand All @@ -375,6 +402,9 @@ export class TinyHyperGraphSolver extends BaseSolver {
STATIC_REACHABILITY_PRECHECK_MAX_HOPS = 16
ACCEPT_BEST_SOLUTION_ON_TIMEOUT = true
GREEDY_FINAL_ROUTE_ITERS = 4
USE_LAYERED_ROUTE_SEARCH = false
LAYERED_SEARCH_BUCKET_SIZE?: number
LAYERED_SEARCH_INCLUDE_ADJACENT_COARSE_REGIONS = true

constructor(
public topology: TinyHyperGraphTopology,
Expand Down Expand Up @@ -485,6 +515,12 @@ export class TinyHyperGraphSolver extends BaseSolver {
}
}
}

if (this.USE_LAYERED_ROUTE_SEARCH) {
this.layeredSearchMap = buildLayeredSearchMap(this.topology, {
bucketSize: this.LAYERED_SEARCH_BUCKET_SIZE,
})
}
}

override _step() {
Expand All @@ -499,6 +535,7 @@ export class TinyHyperGraphSolver extends BaseSolver {
state.currentRouteId = state.unroutedRoutes.shift()
state.currentRouteNetId = problem.routeNet[state.currentRouteId!]
this.routeAttemptCountByRouteId[state.currentRouteId!] += 1
this.layeredSearchAllowedFineRegionMask = undefined

this.resetCandidateBestCosts()
const startingPortId = problem.routeStartPort[state.currentRouteId!]
Expand Down Expand Up @@ -526,6 +563,15 @@ export class TinyHyperGraphSolver extends BaseSolver {
h: 0,
})
state.goalPortId = problem.routeEndPort[state.currentRouteId!]

if (
!this.prepareLayeredRouteSearch(
startingNextRegionId,
state.goalPortId,
)
) {
return
}
}

const currentCandidate = state.candidateQueue.dequeue()
Expand Down Expand Up @@ -583,6 +629,10 @@ export class TinyHyperGraphSolver extends BaseSolver {
continue
}

if (!this.isRegionAllowedForRouteSearch(nextRegionId)) {
continue
}

const newCandidate = {
prevRegionId: currentCandidate.nextRegionId,
nextRegionId,
Expand All @@ -606,6 +656,54 @@ export class TinyHyperGraphSolver extends BaseSolver {
}
}

protected prepareLayeredRouteSearch(
startRegionId: RegionId,
goalPortId: PortId,
): boolean {
if (!this.USE_LAYERED_ROUTE_SEARCH) {
return true
}

const currentRouteNetId = this.state.currentRouteNetId
if (currentRouteNetId === undefined) {
throw new Error("Current route net is missing during layered route search")
}

const layeredSearchMap =
this.layeredSearchMap ??
buildLayeredSearchMap(this.topology, {
bucketSize: this.LAYERED_SEARCH_BUCKET_SIZE,
})
this.layeredSearchMap = layeredSearchMap

const corridor = findLayeredRouteCorridor({
layeredMap: layeredSearchMap,
topology: this.topology,
problem: this.problem,
regionCongestionCost: this.state.regionCongestionCost,
currentRouteNetId,
startRegionId,
goalPortId,
distanceToCost: this.DISTANCE_TO_COST,
includeAdjacentCoarseRegions:
this.LAYERED_SEARCH_INCLUDE_ADJACENT_COARSE_REGIONS,
})

if (corridor._tag === "notFound") {
this.failed = true
this.error = corridor.error
return false
}

this.layeredSearchAllowedFineRegionMask = corridor.allowedFineRegionMask
return true
}

protected isRegionAllowedForRouteSearch(regionId: RegionId): boolean {
const allowedFineRegionMask = this.layeredSearchAllowedFineRegionMask
return !allowedFineRegionMask || allowedFineRegionMask[regionId] === 1
}

resetCandidateBestCosts() {
const { state } = this

Expand Down Expand Up @@ -887,6 +985,7 @@ export class TinyHyperGraphSolver extends BaseSolver {
state.candidateQueue.clear()
this.resetCandidateBestCosts()
state.goalPortId = -1
this.layeredSearchAllowedFineRegionMask = undefined
}

protected getMaxRegionCost() {
Expand Down Expand Up @@ -1359,6 +1458,7 @@ export class TinyHyperGraphSolver extends BaseSolver {
state.candidateQueue.clear()
state.currentRouteNetId = undefined
state.currentRouteId = undefined
this.layeredSearchAllowedFineRegionMask = undefined
}

computeG(currentCandidate: Candidate, neighborPortId: PortId): number {
Expand Down
Loading
Loading