Skip to content
Open
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
206 changes: 173 additions & 33 deletions lib/compat/loadSerializedHyperGraph.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,13 @@ import type {
} from "../index"
import { getAvailableZFromMask, getZLayerLabel } from "../layerLabels"

const getSerializedRegionNetId = (
region: SerializedHyperGraph["regions"][number],
) => {
type SerializedRegion = SerializedHyperGraph["regions"][number]
type SerializedPort = SerializedHyperGraph["ports"][number]
type SerializedConnection = NonNullable<
SerializedHyperGraph["connections"]
>[number]

const getSerializedRegionNetId = (region: SerializedRegion) => {
const netId =
typeof region.d?.netId === "number"
? region.d.netId
Expand All @@ -19,9 +23,7 @@ const getSerializedRegionNetId = (
return Number.isFinite(netId) ? netId : undefined
}

const isFullObstacleRegion = (
region: SerializedHyperGraph["regions"][number],
) => {
const isFullObstacleRegion = (region: SerializedRegion) => {
if (region.d?._containsObstacle !== true) {
return false
}
Expand All @@ -34,19 +36,166 @@ const isFullObstacleRegion = (
return netId === undefined || netId === -1
}

const filterObstacleRegions = (serializedHyperGraph: SerializedHyperGraph) => {
const isTargetContainingObstacleRegion = (region: SerializedRegion) =>
region.d?._containsObstacle === true && region.d?._containsTarget === true

/**
* Normalizes serialized obstacle root ids into a string list.
*
* @param region - Obstacle region whose provenance metadata may include
* `_obstacleRootIds`.
* @returns Every string-valued root id in `_obstacleRootIds`, or an empty list
* when the metadata is absent or malformed.
*/
const getObstacleRootIds = (region: SerializedRegion): string[] => {
const rootIds = region.d?._obstacleRootIds
if (!Array.isArray(rootIds)) {
return []
}

return rootIds.filter(
(rootId): rootId is string => typeof rootId === "string",
)
}

const hasExactlyOneSharedObstacleRootId = (
regionA: SerializedRegion | undefined,
regionB: SerializedRegion | undefined,
) => {
if (!regionA || !regionB) return false

const rootIdsA = getObstacleRootIds(regionA)
const rootIdsB = getObstacleRootIds(regionB)

return (
rootIdsA.length === 1 &&
rootIdsB.length === 1 &&
rootIdsA[0] === rootIdsB[0]
)
}

/**
* Decides whether two full-obstacle regions can be traversed as the same
* obstacle cluster during preservation.
*
* @param regionA - Current preserved obstacle region.
* @param regionB - Adjacent obstacle region under consideration.
* @returns `true` when both regions can be treated as belonging to the same
* obstacle cluster, otherwise `false`.
* @note When both regions provide obstacle root metadata, this requires at
* least one shared root id. The legacy adjacency-only fallback is used only
* when one side lacks obstacle ancestry metadata entirely.
*/
const sharesObstacleRootId = (
regionA: SerializedRegion | undefined,
regionB: SerializedRegion | undefined,
) => {
if (!regionA || !regionB) return false

const rootIdsA = getObstacleRootIds(regionA)
const rootIdsB = getObstacleRootIds(regionB)

if (rootIdsA.length > 0 && rootIdsB.length > 0) {
return rootIdsA.some((rootId) => rootIdsB.includes(rootId))
}

return true
}

/**
* Walks obstacle-only regions that are still relevant to at least one
* connection endpoint.
*
* @param serializedHyperGraph - Serialized hypergraph to prune.
* @returns Region ids for full-obstacle regions that should remain in the
* graph after preserving connected obstacle clusters.
*/
const getPreservedObstacleRegionIds = (
serializedHyperGraph: SerializedHyperGraph,
): Set<string> => {
const connectedRegionIds = new Set<string>()
const fullObstacleRegionById = new Map(
serializedHyperGraph.regions
.filter((region) => isFullObstacleRegion(region))
.map((region) => [region.regionId, region]),
)
const preservedObstacleRegionIds = new Set<string>()
const obstacleQueue: string[] = []
let queueIndex = 0

for (const connection of serializedHyperGraph.connections ?? []) {
connectedRegionIds.add(connection.startRegionId)
connectedRegionIds.add(connection.endRegionId)
}

for (const regionId of connectedRegionIds) {
if (!fullObstacleRegionById.has(regionId)) continue
preservedObstacleRegionIds.add(regionId)
obstacleQueue.push(regionId)
}

while (queueIndex < obstacleQueue.length) {
const currentRegionId = obstacleQueue[queueIndex++]!

for (const port of serializedHyperGraph.ports) {
const neighborRegionId =
port.region1Id === currentRegionId
? port.region2Id
: port.region2Id === currentRegionId
? port.region1Id
: null

if (neighborRegionId === null) continue
if (!fullObstacleRegionById.has(neighborRegionId)) continue
if (preservedObstacleRegionIds.has(neighborRegionId)) continue
if (
isTargetContainingObstacleRegion(
fullObstacleRegionById.get(neighborRegionId)!,
) &&
!hasExactlyOneSharedObstacleRootId(
fullObstacleRegionById.get(currentRegionId),
fullObstacleRegionById.get(neighborRegionId),
)
) {
continue
}
if (
!sharesObstacleRootId(
fullObstacleRegionById.get(currentRegionId),
fullObstacleRegionById.get(neighborRegionId),
)
) {
continue
}

preservedObstacleRegionIds.add(neighborRegionId)
obstacleQueue.push(neighborRegionId)
}
}

return preservedObstacleRegionIds
}

/**
* Removes disconnected obstacle-only regions that should not participate in
* routing.
*
* @param serializedHyperGraph - Serialized graph to sanitize before loading it
* into the tiny hypergraph topology arrays.
* @returns The original graph when nothing is removed, otherwise a shallow copy
* with filtered `regions` and `ports`.
* @caution This throws when a serialized connection still references a removed
* obstacle region because that graph cannot be loaded safely.
*/
const filterObstacleRegions = (serializedHyperGraph: SerializedHyperGraph) => {
const preservedObstacleRegionIds =
getPreservedObstacleRegionIds(serializedHyperGraph)
const removedRegionIds = new Set(
serializedHyperGraph.regions
.filter(
(region) =>
isFullObstacleRegion(region) &&
!connectedRegionIds.has(region.regionId),
!preservedObstacleRegionIds.has(region.regionId),
)
.map((region) => region.regionId),
)
Expand Down Expand Up @@ -83,7 +232,7 @@ const filterObstacleRegions = (serializedHyperGraph: SerializedHyperGraph) => {
}

const addSerializedRegionIdToMetadata = (
region: SerializedHyperGraph["regions"][number],
region: SerializedRegion,
layer: string,
) => {
const metadata =
Expand All @@ -103,10 +252,7 @@ const addSerializedRegionIdToMetadata = (
return metadata
}

const addSerializedPortIdToMetadata = (
port: SerializedHyperGraph["ports"][number],
layer: string,
) => {
const addSerializedPortIdToMetadata = (port: SerializedPort, layer: string) => {
const metadata =
port.d && typeof port.d === "object" && !Array.isArray(port.d)
? { ...port.d }
Expand All @@ -124,7 +270,7 @@ const addSerializedPortIdToMetadata = (
return metadata
}

const getRegionBounds = (region: SerializedHyperGraph["regions"][number]) => {
const getRegionBounds = (region: SerializedRegion) => {
const bounds = region.d?.bounds
if (bounds) {
return bounds
Expand Down Expand Up @@ -157,7 +303,7 @@ const getRegionBounds = (region: SerializedHyperGraph["regions"][number]) => {
}

const getRegionGeometry = (
region: SerializedHyperGraph["regions"][number],
region: SerializedRegion,
): { centerX: number; centerY: number; width: number; height: number } => {
const bounds = getRegionBounds(region)
const width =
Expand All @@ -183,9 +329,7 @@ const getRegionGeometry = (
}
}

const getRegionAvailableZMask = (
region: SerializedHyperGraph["regions"][number],
): number => {
const getRegionAvailableZMask = (region: SerializedRegion): number => {
const availableZ = region.d?.availableZ
if (!Array.isArray(availableZ)) {
return 0
Expand All @@ -202,24 +346,20 @@ const getRegionAvailableZMask = (
return mask
}

const getSerializedPortZ = (
port: SerializedHyperGraph["ports"][number],
): number => {
const getSerializedPortZ = (port: SerializedPort): number => {
const z = Number(port.d?.z ?? 0)
return Number.isFinite(z) ? z : 0
}

const getSerializedPortX = (
port: SerializedHyperGraph["ports"][number],
): number => Number(port.d?.x ?? 0)
const getSerializedPortX = (port: SerializedPort): number =>
Number(port.d?.x ?? 0)

const getSerializedPortY = (
port: SerializedHyperGraph["ports"][number],
): number => Number(port.d?.y ?? 0)
const getSerializedPortY = (port: SerializedPort): number =>
Number(port.d?.y ?? 0)

const computePortAngle = (
port: SerializedHyperGraph["ports"][number],
region: SerializedHyperGraph["regions"][number] | undefined,
port: SerializedPort,
region: SerializedRegion | undefined,
): number => {
if (!region) return 0

Expand Down Expand Up @@ -285,8 +425,8 @@ const computePortAngle = (
}

const getCentermostPortIdForRegion = (
region: SerializedHyperGraph["regions"][number] | undefined,
portById: Map<string, SerializedHyperGraph["ports"][number]>,
region: SerializedRegion | undefined,
portById: Map<string, SerializedPort>,
): string | undefined => {
if (!region) return undefined

Expand Down Expand Up @@ -314,7 +454,7 @@ const getCentermostPortIdForRegion = (

const getSharedPortIdsForConnection = (
serializedHyperGraph: SerializedHyperGraph,
connection: NonNullable<SerializedHyperGraph["connections"]>[number],
connection: SerializedConnection,
): string[] =>
serializedHyperGraph.ports
.filter(
Expand All @@ -336,7 +476,7 @@ export const loadSerializedHyperGraph = (
const filteredHyperGraph = filterObstacleRegions(serializedHyperGraph)
const regionIdToIndex = new Map<string, number>()
const portIdToIndex = new Map<string, number>()
const portById = new Map<string, SerializedHyperGraph["ports"][number]>()
const portById = new Map<string, SerializedPort>()
const solvedRouteByConnectionId = new Map(
(filteredHyperGraph.solvedRoutes ?? []).map((route) => [
route.connection.connectionId,
Expand Down
8 changes: 4 additions & 4 deletions tests/solver/ddr5-dense-allocation-repro.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -164,10 +164,10 @@ test("repro: DDR5 pipeline7 port-point-pathing input implies multi-GB dense hop
}).toMatchInlineSnapshot(`
{
"boardName": "DDR5",
"denseHopBytes": 4209756768,
"denseHopCount": 350813064,
"portCount": 27597,
"regionCount": 12712,
"denseHopBytes": 4287276000,
"denseHopCount": 357273000,
"portCount": 27825,
"regionCount": 12840,
"routeCount": 79,
"source": "SRG 18 pipeline 7 circuit 6",
}
Expand Down
Loading