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
113 changes: 113 additions & 0 deletions lib/auto-repair/TinyHyperGraphAutoRepairFallbackSolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import { BaseSolver } from "@tscircuit/solver-utils"
import type { GraphicsObject } from "graphics-debug"
import {
TinyHyperGraphSolver,
type TinyHyperGraphProblem,
type TinyHyperGraphSolverOptions,
type TinyHyperGraphTopology,
} from "../core"
import { splitOverloadedRouteEndpointPorts } from "./splitOverloadedRouteEndpointPorts"
import { TinyHyperGraphVirtualFanoutSolver } from "./TinyHyperGraphVirtualFanoutSolver"

export class TinyHyperGraphAutoRepairFallbackSolver extends BaseSolver {
primarySolver: TinyHyperGraphSolver
fallbackSolver?: TinyHyperGraphVirtualFanoutSolver
fallbackTriggered = false

constructor(
public topology: TinyHyperGraphTopology,
public problem: TinyHyperGraphProblem,
public options?: TinyHyperGraphSolverOptions,
) {
super()
this.primarySolver = new TinyHyperGraphSolver(topology, problem, options)
this.activeSubSolver = this.primarySolver
this.MAX_ITERATIONS = this.primarySolver.MAX_ITERATIONS * 2 + 2
}

override _step() {
const activeSolver = this.activeSubSolver

if (!activeSolver) {
this.failed = true
this.error = "Auto-repair fallback solver has no active solver"
return
}

activeSolver.step()
this.updateStats()

if (activeSolver.solved) {
this.solved = true
return
}

if (!activeSolver.failed) {
return
}

if (
activeSolver === this.primarySolver &&
this.didSolverRunOutOfIterations(this.primarySolver)
) {
this.startFallbackSolver()
return
}

this.failed = true
this.error = activeSolver.error
this.activeSubSolver = activeSolver
}

private didSolverRunOutOfIterations(solver: TinyHyperGraphSolver) {
return (
solver.failed &&
solver.iterations >= solver.MAX_ITERATIONS &&
solver.error === `${solver.getSolverName()} ran out of iterations`
)
}

private startFallbackSolver() {
const split = splitOverloadedRouteEndpointPorts(this.topology, this.problem)
this.fallbackSolver = new TinyHyperGraphVirtualFanoutSolver(
split.topology,
split.problem,
this.options,
)
this.fallbackTriggered = true
this.failedSubSolvers = [this.primarySolver]
this.activeSubSolver = this.fallbackSolver
this.updateStats()
}

private updateStats() {
const activeSolver = this.activeSubSolver

this.stats = {
...activeSolver?.stats,
autoRepairFallback: true,
autoRepairFallbackTriggered: this.fallbackTriggered,
autoRepairStage:
activeSolver === this.fallbackSolver ? "fallback" : "primary",
primaryIterations: this.primarySolver.iterations,
fallbackIterations: this.fallbackSolver?.iterations ?? 0,
primaryError: this.primarySolver.error,
}
}

override visualize(): GraphicsObject {
return this.activeSubSolver?.visualize() ?? super.visualize()
}

override preview(): GraphicsObject {
return this.activeSubSolver?.preview() ?? super.preview()
}

override getOutput() {
const solvedSolver = this.fallbackSolver?.solved
? this.fallbackSolver
: this.primarySolver

return solvedSolver.getOutput()
}
}
1 change: 1 addition & 0 deletions lib/auto-repair/TinyHyperGraphVirtualFanoutSolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { TinyHyperGraphVirtualFanoutSolver } from "./TinyHyperGraphVirtualFanoutSolver/TinyHyperGraphVirtualFanoutSolver"
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import {
createEmptyRegionIntersectionCache,
TinyHyperGraphSolver,
type Candidate,
type TinyHyperGraphProblem,
type TinyHyperGraphSolverOptions,
type TinyHyperGraphTopology,
} from "../../core"
import type { PortId, RegionId, RouteId } from "../../types"
import { appendInt32, cloneVirtualFanoutPort } from "./cloneVirtualFanoutPort"

type SolvedRouteSegment = {
regionId: RegionId
fromPortId: PortId
toPortId: PortId
}

/**
* Routes every connection as if intermediate ports have unbounded virtual
* fanout capacity, then duplicates and spaces any different-net reused ports in
* the assembled TinyHyperGraphSolver output.
*
* Real route endpoints are still reserved by net. Use
* splitOverloadedRouteEndpointPorts first when multiple different-net route
* endpoints share the same physical port.
*/
export class TinyHyperGraphVirtualFanoutSolver extends TinyHyperGraphSolver {
readonly solvedRouteSegmentsByRouteId: Array<SolvedRouteSegment[] | undefined>
readonly virtualSharedPortSpacing: number

constructor(
topology: TinyHyperGraphTopology,
problem: TinyHyperGraphProblem,
options?: TinyHyperGraphSolverOptions,
) {
super(topology, problem, options)
this.solvedRouteSegmentsByRouteId = Array.from(
{ length: problem.routeCount },
() => undefined,
)
this.virtualSharedPortSpacing = 0.2
}

override onPathFound(finalCandidate: Candidate) {
const currentRouteId = this.state.currentRouteId

if (currentRouteId === undefined) return

this.routeSuccessCountByRouteId[currentRouteId] += 1
this.solvedRouteSegmentsByRouteId[currentRouteId] =
this.getSolvedPathSegments(finalCandidate)

this.state.candidateQueue.clear()
this.state.currentRouteNetId = undefined
this.state.currentRouteId = undefined
}

override onAllRoutesRouted() {
if (
this.solvedRouteSegmentsByRouteId.some(
(routeSegments) => routeSegments === undefined,
)
) {
super.onAllRoutesRouted()
return
}

this.assembleFanoutSolution()
this.solved = true
}

protected assembleFanoutSolution() {
const { topology, problem, state } = this
const clonedPortCountByOriginalPortId = new Map<PortId, number>()
const clonedPortIdByOriginalPortIdAndNetId = new Map<string, PortId>()
let virtualFanoutClonedPortCount = 0

state.portAssignment.fill(-1)
state.regionSegments = Array.from(
{ length: topology.regionCount },
() => [],
)
state.regionIntersectionCaches = Array.from(
{ length: topology.regionCount },
() => createEmptyRegionIntersectionCache(),
)
state.currentRouteId = undefined
state.currentRouteNetId = undefined
state.unroutedRoutes = []
state.candidateQueue.clear()
this.resetCandidateBestCosts()
state.goalPortId = -1

const getPortIdForNet = (
originalPortId: PortId,
adjacentPortId: PortId,
routeNetId: number,
) => {
const assignedNetId = state.portAssignment[originalPortId]

if (assignedNetId === -1 || assignedNetId === routeNetId) {
state.portAssignment[originalPortId] = routeNetId
return originalPortId
}

const cloneKey = `${originalPortId}:${routeNetId}`
const existingClonePortId =
clonedPortIdByOriginalPortIdAndNetId.get(cloneKey)
if (existingClonePortId !== undefined) {
state.portAssignment[existingClonePortId] = routeNetId
return existingClonePortId
}

const cloneIndex =
clonedPortCountByOriginalPortId.get(originalPortId) ?? 0
const { clonedPortId } = cloneVirtualFanoutPort({
topology,
problem,
originalPortId,
adjacentPortId,
routeNetId,
cloneIndex,
spacing: this.virtualSharedPortSpacing,
})

state.portAssignment = appendInt32(state.portAssignment, routeNetId)!
clonedPortCountByOriginalPortId.set(originalPortId, cloneIndex + 1)
clonedPortIdByOriginalPortIdAndNetId.set(cloneKey, clonedPortId)
virtualFanoutClonedPortCount += 1

return clonedPortId
}

for (let routeId = 0; routeId < problem.routeCount; routeId++) {
const routeNetId = problem.routeNet[routeId]
const originalRouteStartPortId = problem.routeStartPort[routeId]
const originalRouteEndPortId = problem.routeEndPort[routeId]
state.currentRouteNetId = routeNetId

for (const { regionId, fromPortId, toPortId } of this
.solvedRouteSegmentsByRouteId[routeId] ?? []) {
const routedFromPortId = getPortIdForNet(
fromPortId,
toPortId,
routeNetId,
)
const routedToPortId = getPortIdForNet(toPortId, fromPortId, routeNetId)

if (originalRouteStartPortId === fromPortId) {
problem.routeStartPort[routeId] = routedFromPortId
}
if (originalRouteEndPortId === fromPortId) {
problem.routeEndPort[routeId] = routedFromPortId
}
if (originalRouteStartPortId === toPortId) {
problem.routeStartPort[routeId] = routedToPortId
}
if (originalRouteEndPortId === toPortId) {
problem.routeEndPort[routeId] = routedToPortId
}

state.regionSegments[regionId].push([
routeId,
routedFromPortId,
routedToPortId,
])
this.appendSegmentToRegionCache(
regionId,
routedFromPortId,
routedToPortId,
)
}
}

state.currentRouteNetId = undefined
const differentNetSharedPortCount =
this.getDifferentNetSharedPortCountFromSegments()

this.stats = {
...this.stats,
virtualFanout: true,
capturedPathCount: problem.routeCount,
virtualFanoutClonedPortCount,
differentNetSharedPortCount,
maxRegionCost: this.getMaxRegionCost(),
}
}

private getDifferentNetSharedPortCountFromSegments() {
const netIdsByPortId = new Map<PortId, Set<number>>()

for (const regionSegments of this.state.regionSegments) {
for (const [routeId, fromPortId, toPortId] of regionSegments) {
const routeNetId = this.problem.routeNet[routeId]
for (const portId of [fromPortId, toPortId]) {
const netIds = netIdsByPortId.get(portId) ?? new Set<number>()
netIds.add(routeNetId)
netIdsByPortId.set(portId, netIds)
}
}
}

return Array.from(netIdsByPortId.values()).filter(
(netIds) => netIds.size > 1,
).length
}
}
Loading
Loading