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
65 changes: 65 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,71 @@ if (!solver.solved || solver.failed) {
const solvedGraph = solver.getOutput()
```

### Optimize region costs after solving

`UnravelTinyHyperGraphSolver` accepts a completed solver and monotonically
reduces its maximum region cost. On a maximum-cost plateau, a mutation must be a
Pareto improvement across total region cost, segment concentration, and the
downstream detailed-router crossing-risk metrics. It alternates complete
boundary-port untwist descents (atomic swaps and three-port cycles) with
graph-wide route replacement until neither neighborhood can improve the solved
graph. At a one-route local optimum, it also evaluates two-route ejection
chains drawn from measured blocked-port and replacement-corridor dependencies.
This allows a route to move only after the route obstructing its better path is
removed, without enumerating every route pair.

Each route replacement uses the core A* marginal region-cost objective and an
equal-weight congestion scalarization that exposes minimax improvements hidden
by an additive path score. Every completed path is still selected by the exact
whole-graph objective. An admissible route-removal lower bound orders and
prunes the graph-wide search.
Valid paths found during a sweep are carried through later boundary swaps,
ownership-validated, and fully rescored before reuse; a final fresh A* sweep is
still required before reporting a local optimum. Replacement states use
copy-on-write region storage: only removed and newly traversed corridors rebuild
their cost and physical-risk geometry, while the exact objective is aggregated
over the whole graph. The default optimization is not route-, sample-, density-,
or mutation-count gated. Resource caps and a per-route detour ceiling remain
explicit opt-in options, and the original solution remains a safe fallback.

```ts
import {
TinyHyperGraphSolver,
UnravelTinyHyperGraphSolver,
} from "lib"

const solver = new TinyHyperGraphSolver(topology, problem)
solver.solve()

if (!solver.solved || solver.failed) {
throw new Error(solver.error ?? "Solver did not finish successfully")
}

const optimizer = new UnravelTinyHyperGraphSolver(solver)
optimizer.solve()

const optimizedGraph = optimizer.getOutput()
```

The section pipeline runs this as its final `optimizeRegionCosts` stage. Useful
statistics include `initialMaxRegionCost`, `finalMaxRegionCost`,
`acceptedSwapMutationCount`, `acceptedCycleMutationCount`,
`acceptedRerouteMutationCount`, `acceptedPairRerouteMutationCount`,
`evaluatedMutationCount`, `rerouteSearchCount`,
`rerouteSearchIterationCount`, and `reusedRerouteCandidateCount`. Set
`MAX_REROUTE_SEGMENT_INCREASE` to opt into a per-route detour ceiling, or
`MAX_MUTATIONS: 0` to retain the solved input without running post-solve
mutations. Run `PROFILE_UNRAVEL=1 ./benchmark.sh` to print the complete initial
and final objective summaries plus search counters for each benchmark sample.

Boundary swaps stay on the same copper layer so a local untwist cannot
silently move a long trace onto a pad's layer.
Cross-layer boundary swaps preserve each affected route's transition count, so
they can relocate a via locally without changing a long-range layer assignment.
The optimizer's secondary risk objective groups split tiny routes by
`simpleRouteConnection.name`, uses the first two distinct physical points as
that connection's region chord, and excludes shared endpoints.

Existing routing can be preloaded through the standard region assignments:

```ts
Expand Down
68 changes: 44 additions & 24 deletions lib/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -743,8 +743,7 @@ export class TinyHyperGraphSolver extends BaseSolver {
return
}

const neighbors =
topology.regionIncidentPorts[currentCandidate.nextRegionId]
const neighbors = this.getCandidateNeighborPortIds(currentCandidate)

for (const neighborPortId of neighbors) {
const assignedNetId = state.portAssignment[neighborPortId]
Expand Down Expand Up @@ -812,6 +811,12 @@ export class TinyHyperGraphSolver extends BaseSolver {
}
}

protected getCandidateNeighborPortIds(
currentCandidate: Candidate,
): readonly PortId[] {
return this.topology.regionIncidentPorts[currentCandidate.nextRegionId]!
}

resetCandidateBestCosts() {
const { state } = this

Expand Down Expand Up @@ -1636,16 +1641,40 @@ export class TinyHyperGraphSolver extends BaseSolver {
if (lowerBoundCost > maximumCost + 1e-9) {
return Number.POSITIVE_INFINITY
}
const currentPortId = currentCandidate.portId
const newRegionCost = this.computeRegionCostAfterAddingSegment(
nextRegionId,
currentCandidate.portId,
neighborPortId,
)
if (!Number.isFinite(newRegionCost)) {
return Number.POSITIVE_INFINITY
}

return (
currentCandidate.g +
(newRegionCost - regionCache.existingRegionCost) +
state.regionCongestionCost[nextRegionId] +
(this.problem.portPenalty?.[neighborPortId] ?? 0) +
segmentDistanceCost
)
}

protected computeRegionCostAfterAddingSegment(
regionId: RegionId,
currentPortId: PortId,
neighborPortId: PortId,
): number {
const { state, topology } = this
const regionCache = state.regionIntersectionCaches[regionId]
const currentPortAngle =
this.candidateFirstRegionByPortId[currentPortId] === nextRegionId ||
this.candidateSecondRegionByPortId[currentPortId] !== nextRegionId
this.candidateFirstRegionByPortId[currentPortId] === regionId ||
this.candidateSecondRegionByPortId[currentPortId] !== regionId
? topology.portAngleForRegion1[currentPortId]
: (topology.portAngleForRegion2?.[currentPortId] ??
topology.portAngleForRegion1[currentPortId])
const neighborPortAngle =
this.candidateFirstRegionByPortId[neighborPortId] === nextRegionId ||
this.candidateSecondRegionByPortId[neighborPortId] !== nextRegionId
this.candidateFirstRegionByPortId[neighborPortId] === regionId ||
this.candidateSecondRegionByPortId[neighborPortId] !== regionId
? topology.portAngleForRegion1[neighborPortId]
: (topology.portAngleForRegion2?.[neighborPortId] ??
topology.portAngleForRegion1[neighborPortId])
Expand Down Expand Up @@ -1677,27 +1706,18 @@ export class TinyHyperGraphSolver extends BaseSolver {

if (
newSameLayerIntersections > 0 &&
this.isKnownSingleLayerRegion(nextRegionId)
this.isKnownSingleLayerRegion(regionId)
) {
return Number.POSITIVE_INFINITY
}

const newRegionCost =
this.computeRegionCostForRegion(
nextRegionId,
regionCache.existingSameLayerIntersections + newSameLayerIntersections,
regionCache.existingCrossingLayerIntersections +
newCrossLayerIntersections,
regionCache.existingEntryExitLayerChanges + newEntryExitLayerChanges,
regionCache.existingSegmentCount + 1,
) - regionCache.existingRegionCost

return (
currentCandidate.g +
newRegionCost +
state.regionCongestionCost[nextRegionId] +
(this.problem.portPenalty?.[neighborPortId] ?? 0) +
segmentDistanceCost
return this.computeRegionCostForRegion(
regionId,
regionCache.existingSameLayerIntersections + newSameLayerIntersections,
regionCache.existingCrossingLayerIntersections +
newCrossLayerIntersections,
regionCache.existingEntryExitLayerChanges + newEntryExitLayerChanges,
regionCache.existingSegmentCount + 1,
)
}

Expand Down
1 change: 1 addition & 0 deletions lib/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export * from "./find-distinct-owner-blocker-path"
export * from "./indexed-candidate-heap"
export * from "./poly"
export * from "./selective-rerip-tiny-hyper-graph-solver"
export * from "./unravel-tiny-hypergraph-solver"
export * from "./bus-solver"
export * from "./region-graph"
export {
Expand Down
37 changes: 37 additions & 0 deletions lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ import type {
TinyHyperGraphTopology,
} from "../core"
import { TinyHyperGraphSolver } from "../core"
import {
UnravelTinyHyperGraphSolver,
type UnravelTinyHyperGraphSolverOptions,
} from "../unravel-tiny-hypergraph-solver"
import type { RegionId } from "../types"
import type { TinyHyperGraphSectionSolverOptions } from "./index"
import { getActiveSectionRouteIds, TinyHyperGraphSectionSolver } from "./index"
Expand Down Expand Up @@ -315,6 +319,7 @@ export interface TinyHyperGraphSectionPipelineInput {
createSectionMask?: (context: TinyHyperGraphSectionMaskContext) => Int8Array
solveGraphOptions?: TinyHyperGraphSolverOptions
sectionSolverOptions?: TinyHyperGraphSectionSolverOptions
unravelSolverOptions?: UnravelTinyHyperGraphSolverOptions
sectionSearchConfig?: TinyHyperGraphSectionPipelineSearchConfig
}

Expand Down Expand Up @@ -379,8 +384,39 @@ export class TinyHyperGraphSectionPipelineSolver extends BasePipelineSolver<Tiny
getConstructorParams: (instance: TinyHyperGraphSectionPipelineSolver) =>
instance.getSectionStageParams(),
},
{
solverName: "optimizeRegionCosts",
solverClass: UnravelTinyHyperGraphSolver,
getConstructorParams: (instance: TinyHyperGraphSectionPipelineSolver) =>
instance.getUnravelStageParams(),
},
]

getUnravelStageParams(): ConstructorParameters<
typeof UnravelTinyHyperGraphSolver
> {
const sectionSolver =
this.getSolver<TinyHyperGraphSectionSolver>("optimizeSection")
if (sectionSolver) {
if (!sectionSolver.solved || sectionSolver.failed) {
throw new Error("optimizeSection did not produce a solved solver")
}
return [
sectionSolver.getSolvedSolver(),
this.inputProblem.unravelSolverOptions,
]
}

// Integrations with structurally fixed assignments may omit the mutable
// section search. Region optimization can still operate on the solved
// graph as long as those routes are identified through FIXED_ROUTE_IDS.
const solveGraphSolver = this.getSolver<TinyHyperGraphSolver>("solveGraph")
if (!solveGraphSolver?.solved || solveGraphSolver.failed) {
throw new Error("solveGraph did not produce a solved solver")
}
return [solveGraphSolver, this.inputProblem.unravelSolverOptions]
}

getSectionStageParams(): [
TinyHyperGraphTopology,
TinyHyperGraphProblem,
Expand Down Expand Up @@ -502,6 +538,7 @@ export class TinyHyperGraphSectionPipelineSolver extends BasePipelineSolver<Tiny

override getOutput() {
return (
this.getStageOutput<SerializedHyperGraph>("optimizeRegionCosts") ??
this.getStageOutput<SerializedHyperGraph>("optimizeSection") ??
this.getStageOutput<SerializedHyperGraph>("solveGraph") ??
null
Expand Down
Loading
Loading