From 52a0d61f052e8f0b209ac4f0327e74924e7f73c4 Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 22:36:28 +0200 Subject: [PATCH 1/7] fix: guide cross-layer search by directed graph hops --- lib/core.ts | 62 ++++++++++- lib/directed-route-hop-heuristic.ts | 156 ++++++++++++++++++++++++++++ 2 files changed, 213 insertions(+), 5 deletions(-) create mode 100644 lib/directed-route-hop-heuristic.ts diff --git a/lib/core.ts b/lib/core.ts index 58e9004..4789f60 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -7,6 +7,10 @@ import { isKnownSingleLayerMask, } from "./computeRegionCost" import { countNewIntersectionsWithValues } from "./countNewIntersections" +import { + createDirectedRouteHopHeuristic, + getDirectedRouteHopCount, +} from "./directed-route-hop-heuristic" import { applyInitialAssignments, type TinyHyperGraphInitialAssignment, @@ -167,6 +171,8 @@ export interface TinyHyperGraphProblem { export interface TinyHyperGraphProblemSetup { // portHCostToEndOfRoute[portId * routeCount + routeId] = distance from port to end of route portHCostToEndOfRoute: Float64Array + /** Directed graph distance is only needed for cross-layer routes. */ + directedHopCountToEndByRoute: Array portEndpointNetIds: Array> /** -1 for no endpoint, -2 for endpoints from multiple nets, otherwise the sole endpoint net. */ portEndpointReservationNetId: Int32Array @@ -486,9 +492,26 @@ export class TinyHyperGraphSolver extends BaseSolver { const routeNetId = problem.routeNet[routeId]! recordEndpointNet(problem.routeStartPort[routeId]!, routeNetId) recordEndpointNet(problem.routeEndPort[routeId]!, routeNetId) + } + + const directedHopCountToEndByRoute: Array = + new Array(problem.routeCount) + + for (let routeId = 0; routeId < problem.routeCount; routeId++) { + const startPortId = problem.routeStartPort[routeId]! + const endPortId = problem.routeEndPort[routeId]! + + if (topology.portZ[startPortId] !== topology.portZ[endPortId]) { + directedHopCountToEndByRoute[routeId] = + createDirectedRouteHopHeuristic({ + topology, + problem, + portEndpointReservationNetId, + routeId, + }) + } if (portHCostToEndOfRoute) { - const endPortId = problem.routeEndPort[routeId] const endX = portX[endPortId] const endY = portY[endPortId] @@ -503,6 +526,7 @@ export class TinyHyperGraphSolver extends BaseSolver { return { portHCostToEndOfRoute: portHCostToEndOfRoute as Float64Array, + directedHopCountToEndByRoute, portEndpointNetIds, portEndpointReservationNetId, } @@ -629,7 +653,6 @@ export class TinyHyperGraphSolver extends BaseSolver { const g = this.computeG(currentCandidate, neighborPortId) if (!Number.isFinite(g)) continue - const h = this.computeH(neighborPortId) const nextRegionId = topology.incidentPortRegion[neighborPortId][0] === @@ -644,6 +667,8 @@ export class TinyHyperGraphSolver extends BaseSolver { continue } + const h = this.computeH(neighborPortId, nextRegionId) + const newCandidate = { prevRegionId: currentCandidate.nextRegionId, nextRegionId, @@ -1529,15 +1554,42 @@ export class TinyHyperGraphSolver extends BaseSolver { this.logNeverSuccessfullyRoutedRoutes() } - computeH(neighborPortId: PortId): number { + computeH(neighborPortId: PortId, nextRegionId: RegionId): number { const precomputedHCost = this.problemSetup.portHCostToEndOfRoute + const routeId = this.state.currentRouteId! + const directedHopCountToEnd = + this.problemSetup.directedHopCountToEndByRoute[routeId] + + if (directedHopCountToEnd) { + const directedHopCount = getDirectedRouteHopCount( + this.topology, + directedHopCountToEnd, + neighborPortId, + nextRegionId, + ) + if (directedHopCount === -1) return Number.POSITIVE_INFINITY + + const directDistanceCost = precomputedHCost + ? precomputedHCost[neighborPortId * this.problem.routeCount + routeId]! + : this.computeDirectDistanceHeuristic(neighborPortId, routeId) + + return directDistanceCost + directedHopCount * this.DISTANCE_TO_COST + } + if (precomputedHCost) { return precomputedHCost[ - neighborPortId * this.problem.routeCount + this.state.currentRouteId! + neighborPortId * this.problem.routeCount + routeId ] } - const endPortId = this.problem.routeEndPort[this.state.currentRouteId!] + return this.computeDirectDistanceHeuristic(neighborPortId, routeId) + } + + private computeDirectDistanceHeuristic( + neighborPortId: PortId, + routeId: RouteId, + ): number { + const endPortId = this.problem.routeEndPort[routeId]! const dx = this.getPortRoutingCostX(neighborPortId) - this.getPortRoutingCostX(endPortId) diff --git a/lib/directed-route-hop-heuristic.ts b/lib/directed-route-hop-heuristic.ts new file mode 100644 index 0000000..42db0e4 --- /dev/null +++ b/lib/directed-route-hop-heuristic.ts @@ -0,0 +1,156 @@ +import type { TinyHyperGraphProblem, TinyHyperGraphTopology } from "./core" +import type { NetId, PortId, RegionId, RouteId } from "./types" + +const UNREACHABLE_HOP_COUNT = -1 + +interface CreateDirectedRouteHopHeuristicContext { + topology: TinyHyperGraphTopology + problem: TinyHyperGraphProblem + portEndpointReservationNetId: Int32Array + routeId: RouteId +} + +const getDirectedHopIndex = ( + topology: TinyHyperGraphTopology, + portId: PortId, + nextRegionId: RegionId, +) => { + const incidentRegions = topology.incidentPortRegion[portId] ?? [] + if (incidentRegions[0] === nextRegionId) return portId * 2 + if (incidentRegions[1] === nextRegionId) return portId * 2 + 1 + return -1 +} + +const isRegionAvailableToNet = ( + problem: TinyHyperGraphProblem, + routeNetId: NetId, + regionId: RegionId, +) => { + const reservedNetId = problem.regionNetId[regionId] + return reservedNetId === -1 || reservedNetId === routeNetId +} + +const isPortAvailableToNet = ( + problem: TinyHyperGraphProblem, + portEndpointReservationNetId: Int32Array, + routeNetId: NetId, + portId: PortId, +) => { + if (problem.portSectionMask[portId] === 0) return false + + const reservedNetId = portEndpointReservationNetId[portId] ?? -1 + return reservedNetId === -1 || reservedNetId === routeNetId +} + +/** + * Computes the remaining number of directed region traversals to a route's + * goal. A directed hop is a port together with the region the candidate will + * enter next, matching the state used by the A* candidate queue. + */ +export const createDirectedRouteHopHeuristic = ({ + topology, + problem, + portEndpointReservationNetId, + routeId, +}: CreateDirectedRouteHopHeuristicContext): Int32Array => { + const hopCountToGoal = new Int32Array(topology.portCount * 2).fill( + UNREACHABLE_HOP_COUNT, + ) + const routeNetId = problem.routeNet[routeId]! + const goalPortId = problem.routeEndPort[routeId]! + const queuedPortIds: PortId[] = [] + const queuedNextRegionIds: RegionId[] = [] + + const queueHop = ( + portId: PortId, + nextRegionId: RegionId, + hopCount: number, + ) => { + const directedHopIndex = getDirectedHopIndex( + topology, + portId, + nextRegionId, + ) + if ( + directedHopIndex === -1 || + hopCountToGoal[directedHopIndex] !== UNREACHABLE_HOP_COUNT + ) { + return + } + + hopCountToGoal[directedHopIndex] = hopCount + queuedPortIds.push(portId) + queuedNextRegionIds.push(nextRegionId) + } + + for (const goalRegionId of topology.incidentPortRegion[goalPortId] ?? []) { + if (!isRegionAvailableToNet(problem, routeNetId, goalRegionId)) continue + + for (const portId of topology.regionIncidentPorts[goalRegionId] ?? []) { + queueHop(portId, goalRegionId, 0) + } + } + + const reverseExpansionCountByRegion = new Uint8Array(topology.regionCount) + + for (let queueIndex = 0; queueIndex < queuedPortIds.length; queueIndex++) { + const exitPortId = queuedPortIds[queueIndex]! + const nextRegionId = queuedNextRegionIds[queueIndex]! + if ( + !isPortAvailableToNet( + problem, + portEndpointReservationNetId, + routeNetId, + exitPortId, + ) + ) { + continue + } + + const incidentRegions = topology.incidentPortRegion[exitPortId] ?? [] + const previousRegionId = + incidentRegions[0] === nextRegionId + ? incidentRegions[1] + : incidentRegions[0] + if ( + previousRegionId === undefined || + !isRegionAvailableToNet(problem, routeNetId, previousRegionId) || + reverseExpansionCountByRegion[previousRegionId]! >= 2 + ) { + continue + } + + reverseExpansionCountByRegion[previousRegionId] += 1 + const currentHopIndex = getDirectedHopIndex( + topology, + exitPortId, + nextRegionId, + ) + const previousHopCount = hopCountToGoal[currentHopIndex]! + 1 + + for (const previousPortId of topology.regionIncidentPorts[ + previousRegionId + ] ?? []) { + if (previousPortId === exitPortId) continue + queueHop(previousPortId, previousRegionId, previousHopCount) + } + } + + return hopCountToGoal +} + +export const getDirectedRouteHopCount = ( + topology: TinyHyperGraphTopology, + hopCountToGoal: Int32Array, + portId: PortId, + nextRegionId: RegionId, +) => { + const directedHopIndex = getDirectedHopIndex( + topology, + portId, + nextRegionId, + ) + return directedHopIndex === -1 + ? UNREACHABLE_HOP_COUNT + : hopCountToGoal[directedHopIndex]! +} From 6365c64cb77953d5be9d45bc03df73d10ace623f Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 22:39:40 +0200 Subject: [PATCH 2/7] fix: scope graph guidance to the start layer --- lib/core.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/core.ts b/lib/core.ts index 4789f60..1603091 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -1559,8 +1559,12 @@ export class TinyHyperGraphSolver extends BaseSolver { const routeId = this.state.currentRouteId! const directedHopCountToEnd = this.problemSetup.directedHopCountToEndByRoute[routeId] + const startPortId = this.problem.routeStartPort[routeId]! - if (directedHopCountToEnd) { + if ( + directedHopCountToEnd && + this.topology.portZ[neighborPortId] === this.topology.portZ[startPortId] + ) { const directedHopCount = getDirectedRouteHopCount( this.topology, directedHopCountToEnd, From 0c366d6c0117e88933c9b5429b4c78131ac0b36b Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 22:41:39 +0200 Subject: [PATCH 3/7] ci: generate directed heuristic snapshot --- .github/workflows/bun-test.yml | 4 +- .../cross-layer-heuristic-detour.snap.svg | 281 ------------------ 2 files changed, 3 insertions(+), 282 deletions(-) delete mode 100644 tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index ff846d8..ddce5e6 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -36,5 +36,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: test-snapshots - path: tests/**/__snapshots__/*.diff.png + path: | + tests/**/__snapshots__/*.diff.png + tests/**/__snapshots__/*.snap.svg if-no-files-found: ignore diff --git a/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg b/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg deleted file mode 100644 index e58d79a..0000000 --- a/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg +++ /dev/null @@ -1,281 +0,0 @@ -input topologysolver result From b050ec75d8765139e30fe2aa8ee85dd4553b626a Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 22:43:14 +0200 Subject: [PATCH 4/7] test: add hosted directed heuristic snapshot --- .github/workflows/bun-test.yml | 4 +- .../cross-layer-heuristic-detour.snap.svg | 304 ++++++++++++++++++ 2 files changed, 305 insertions(+), 3 deletions(-) create mode 100644 tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg diff --git a/.github/workflows/bun-test.yml b/.github/workflows/bun-test.yml index ddce5e6..ff846d8 100644 --- a/.github/workflows/bun-test.yml +++ b/.github/workflows/bun-test.yml @@ -36,7 +36,5 @@ jobs: uses: actions/upload-artifact@v4 with: name: test-snapshots - path: | - tests/**/__snapshots__/*.diff.png - tests/**/__snapshots__/*.snap.svg + path: tests/**/__snapshots__/*.diff.png if-no-files-found: ignore diff --git a/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg b/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg new file mode 100644 index 0000000..68daeb7 --- /dev/null +++ b/tests/solver/__snapshots__/cross-layer-heuristic-detour.snap.svg @@ -0,0 +1,304 @@ +input topologysolver result From 98e6d75489dc36e69d3cc1f8c7bbbaf442b07694 Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 22:43:37 +0200 Subject: [PATCH 5/7] ci: verify directed heuristic stack --- .github/workflows/stack-check.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/stack-check.yml diff --git a/.github/workflows/stack-check.yml b/.github/workflows/stack-check.yml new file mode 100644 index 0000000..5981e5e --- /dev/null +++ b/.github/workflows/stack-check.yml @@ -0,0 +1,18 @@ +name: Stack Check + +on: + push: + branches: + - agent/fix-directed-route-heuristic + +jobs: + check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install + - run: bun run format:check + - run: bun run typecheck From d0934352bca71c53cfba9f996ec991432051be32 Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 22:44:43 +0200 Subject: [PATCH 6/7] style: format directed heuristic changes --- .github/workflows/stack-check.yml | 2 +- lib/core.ts | 7 ++++--- lib/directed-route-hop-heuristic.ts | 12 ++---------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/.github/workflows/stack-check.yml b/.github/workflows/stack-check.yml index 5981e5e..b99c3d6 100644 --- a/.github/workflows/stack-check.yml +++ b/.github/workflows/stack-check.yml @@ -14,5 +14,5 @@ jobs: with: bun-version: latest - run: bun install - - run: bun run format:check + - run: bunx biome format lib/core.ts lib/directed-route-hop-heuristic.ts - run: bun run typecheck diff --git a/lib/core.ts b/lib/core.ts index 1603091..44ad336 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -502,13 +502,14 @@ export class TinyHyperGraphSolver extends BaseSolver { const endPortId = problem.routeEndPort[routeId]! if (topology.portZ[startPortId] !== topology.portZ[endPortId]) { - directedHopCountToEndByRoute[routeId] = - createDirectedRouteHopHeuristic({ + directedHopCountToEndByRoute[routeId] = createDirectedRouteHopHeuristic( + { topology, problem, portEndpointReservationNetId, routeId, - }) + }, + ) } if (portHCostToEndOfRoute) { diff --git a/lib/directed-route-hop-heuristic.ts b/lib/directed-route-hop-heuristic.ts index 42db0e4..aa7a97d 100644 --- a/lib/directed-route-hop-heuristic.ts +++ b/lib/directed-route-hop-heuristic.ts @@ -66,11 +66,7 @@ export const createDirectedRouteHopHeuristic = ({ nextRegionId: RegionId, hopCount: number, ) => { - const directedHopIndex = getDirectedHopIndex( - topology, - portId, - nextRegionId, - ) + const directedHopIndex = getDirectedHopIndex(topology, portId, nextRegionId) if ( directedHopIndex === -1 || hopCountToGoal[directedHopIndex] !== UNREACHABLE_HOP_COUNT @@ -145,11 +141,7 @@ export const getDirectedRouteHopCount = ( portId: PortId, nextRegionId: RegionId, ) => { - const directedHopIndex = getDirectedHopIndex( - topology, - portId, - nextRegionId, - ) + const directedHopIndex = getDirectedHopIndex(topology, portId, nextRegionId) return directedHopIndex === -1 ? UNREACHABLE_HOP_COUNT : hopCountToGoal[directedHopIndex]! From 9971a86ad7dfdf554d916f78ccbfc495ca0d0726 Mon Sep 17 00:00:00 2001 From: ShiboSoftwareDev Date: Sun, 2 Aug 2026 22:45:23 +0200 Subject: [PATCH 7/7] ci: restore standard workflows --- .github/workflows/stack-check.yml | 18 ------------------ 1 file changed, 18 deletions(-) delete mode 100644 .github/workflows/stack-check.yml diff --git a/.github/workflows/stack-check.yml b/.github/workflows/stack-check.yml deleted file mode 100644 index b99c3d6..0000000 --- a/.github/workflows/stack-check.yml +++ /dev/null @@ -1,18 +0,0 @@ -name: Stack Check - -on: - push: - branches: - - agent/fix-directed-route-heuristic - -jobs: - check: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - run: bun install - - run: bunx biome format lib/core.ts lib/directed-route-hop-heuristic.ts - - run: bun run typecheck