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
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
HighDensitySolverA03,
HighDensitySolverA05,
HighDensitySolverA11,
HighDensitySolverA12,
} from "@tscircuit/high-density-a01"
```

Expand Down Expand Up @@ -80,6 +81,70 @@ All six outputs pass exact route-geometry validation without growing the input
node. They are covered by native-bounds regressions under
`tests/repros/dataset-hd30-a11/`.

### A12

Use `HighDensitySolverA12` when A11's uniform fine grid creates too many search
states. A12 applies the same feature-derived fine pitch to a 16-cell perimeter
band, uses cells four times larger in the middle, and enables diagonal moves on
A03's five-region graph. Set `fineGridCellThickness` to tune the fine perimeter
width for a particular portfolio.

```ts
const solver = new HighDensitySolverA12({
nodeWithPortPoints,
traceThickness: 0.1,
traceMargin: 0.15,
viaDiameter: 0.3,
viaMinDistFromBorder: 0.15,
fineGridCellThickness: 16,
})

solver.solve()
if (solver.solved) {
const routes = solver.getOutput()
}
```

A12 preserves the exact supplied endpoints and rejects completed routes that
fail exact geometry validation. At Pipeline 9 dimensions, seed 0, and a
100,000-iteration cap, it solves eight native-bound dataset-hd30 problems:

| Node | Iterations |
| --- | ---: |
| `sample003-cmn_70` | 265 |
| `sample004-topology_merge_639` | 14,478 |
| `sample005-cmn_45` | 373 |
| `sample007-cmn_345__sub_0_0` | 49 |
| `sample007-cmn_345__sub_0_2` | 149 |
| `sample008-cmn_251` | 1,067 |
| `sample008-cmn_438` | 38,206 |
| `sample016-cmn_31` | 306 |

Five of these are new beyond A11, giving the two-solver portfolio 11
native-bounds solves.

Together, the A12 graphs allocate 44.1% as many search states as A11 across all
27 dataset-hd30 nodes. On the largest grid, A12 uses 27.5% as many states. The
reduction is concentrated in the larger nodes; narrow nodes whose perimeter
bands meet in the middle remain fully fine-grid.
Because diagonal-edge conflicts are checked by the final geometry gate rather
than repaired during search, A12 is currently best used as a complementary
portfolio stage alongside A11.

### History-aware displacement in A11

A11 increases the cost of displacing a route each time that route has already
been ripped. A fixed `ripCost` does not distinguish a useful first
displacement from repeatedly undoing the same decision, so small dense nodes
can settle into stable rip cycles. The effective A11 cost is
`ripCost * (1 + priorRipCount)`; A01 keeps its original fixed cost.

At Pipeline 9 dimensions, seed 0, and a 100,000-iteration cap, this lets A11
solve `sample004-topology_merge_298` in 3,309 iterations at the original node
bounds. The output passes exact route-geometry validation, the other six A11
HD30 solves are preserved, and the A11/A12 native-bound portfolio increases
from 11 to 12 of the 27 dataset-hd30 nodes.

### A03

Use `HighDensitySolverA03` for the baseline high-density solver:
Expand Down Expand Up @@ -169,6 +234,8 @@ exploration, we consider both used and unused cells. Used cells incur rip costs
and trace/via penalties, while vias allow moving between any available layers.
A path that rips the same trace only pays `ripCost` once, so the search tracks
which traces have already been ripped along that candidate path.
For A11, that one-time candidate-path cost is additionally scaled by how many
earlier committed routes have already displaced the trace.

When we reach the `end` of a path, we mark that route as solved and apply its
occupied cells to the congestion structure. Vias occupy more cells based on
Expand All @@ -189,7 +256,7 @@ work.
Useful benchmark commands:

```sh
./benchmark.sh --solver A01,A11 --concurrency=4
./benchmark.sh --solver A01,A11,A12 --concurrency=4
bun run scripts/run-dataset02-benchmark-a03.ts --concurrency=4
bun run scripts/run-dataset02-benchmark-a05.ts --concurrency=4
```
Expand Down
22 changes: 20 additions & 2 deletions fixtures/components/SolverDebugger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ import {
HighDensitySolverA11,
type HighDensitySolverA11Props,
} from "../../lib/HighDensitySolverA11/HighDensitySolverA11"
import {
HighDensitySolverA12,
type HighDensitySolverA12Props,
} from "../../lib/HighDensitySolverA12/HighDensitySolverA12"
import {
HighDensitySolverA02,
type HighDensitySolverA02Props,
Expand Down Expand Up @@ -38,7 +42,7 @@ import {
} from "../../lib/default-params"
import type { NodeWithPortPoints } from "../../lib/types"

type SolverKey = "a01" | "a02" | "a03" | "a05" | "a08" | "a09" | "a11"
type SolverKey = "a01" | "a02" | "a03" | "a05" | "a08" | "a09" | "a11" | "a12"
type SolverPropsByKey = {
a01: Partial<Omit<HighDensitySolverA01Props, "nodeWithPortPoints">>
a02: Partial<Omit<HighDensitySolverA02Props, "nodeWithPortPoints">>
Expand All @@ -47,6 +51,7 @@ type SolverPropsByKey = {
a08: Partial<Omit<HighDensitySolverA08Props, "nodeWithPortPoints">>
a09: Partial<Omit<HighDensitySolverA09Props, "nodeWithPortPoints">>
a11: Partial<Omit<HighDensitySolverA11Props, "nodeWithPortPoints">>
a12: Partial<Omit<HighDensitySolverA12Props, "nodeWithPortPoints">>
}

const STORAGE_KEY = "high-density:selected-solver"
Expand All @@ -59,6 +64,7 @@ const SOLVER_OPTIONS: Array<{ label: string; value: SolverKey }> = [
{ label: "A08", value: "a08" },
{ label: "A09", value: "a09" },
{ label: "A11", value: "a11" },
{ label: "A12", value: "a12" },
]
const ALL_SOLVER_KEYS = SOLVER_OPTIONS.map((option) => option.value)

Expand All @@ -69,7 +75,8 @@ const isSolverKey = (value: string | null): value is SolverKey =>
value === "a05" ||
value === "a08" ||
value === "a09" ||
value === "a11"
value === "a11" ||
value === "a12"

const getInitialSolverKey = (fallback: SolverKey) => {
if (typeof window === "undefined") return fallback
Expand Down Expand Up @@ -212,6 +219,17 @@ export function SolverDebugger({
...solverPropOverrides?.a11,
}),
)
case "a12":
return prepareSolver(
new HighDensitySolverA12({
nodeWithPortPoints,
viaDiameter: defaultParams.viaDiameter,
viaMinDistFromBorder: defaultParams.viaMinDistFromBorder,
traceMargin: defaultParams.traceMargin,
traceThickness: defaultParams.traceThickness,
...solverPropOverrides?.a12,
}),
)
}

throw new Error(`Unsupported solver key: ${solverKey}`)
Expand Down
17 changes: 14 additions & 3 deletions lib/HighDensitySolverA01/HighDensitySolverA01.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,12 @@ interface CircularGridOffsets {
function createCircularGridOffsets(params: {
radiusMm: number
cellSizeMm: number
stabilizeNearInteger?: boolean
}): CircularGridOffsets {
const radiusCellRatio = params.radiusMm / params.cellSizeMm
const nearestRadiusCellCount = Math.round(radiusCellRatio)
const radiusCells =
params.stabilizeNearInteger &&
Math.abs(radiusCellRatio - nearestRadiusCellCount) <= 1e-9
? nearestRadiusCellCount
: Math.ceil(radiusCellRatio)
Expand Down Expand Up @@ -245,6 +247,7 @@ export class HighDensitySolverA01 extends BaseSolver {
hyperParameters: HyperParameters
initialPenaltyFn?: HighDensitySolverA01Props["initialPenaltyFn"]
protected useExactViaTraceClearance = false
protected ripHistoryCostMultiplier = 0

// Grid dimensions
rows!: number
Expand Down Expand Up @@ -485,6 +488,7 @@ export class HighDensitySolverA01 extends BaseSolver {
const viaOccupantScanOffsets = createCircularGridOffsets({
radiusMm: this.viaDiameter / 2,
cellSizeMm,
stabilizeNearInteger: this.useExactViaTraceClearance,
})
this.viaOccupantScanOffsetsLen = viaOccupantScanOffsets.length
this.viaOccupantScanOffsetsDr = viaOccupantScanOffsets.rowOffsets
Expand Down Expand Up @@ -745,6 +749,13 @@ export class HighDensitySolverA01 extends BaseSolver {
}

// --- Merged cost + rip computation (writes to _moveCost/_moveRipped) ---
protected getRipCost(connId: ConnId): number {
return (
this.hyperParameters.ripCost *
(1 + this.ripHistoryCostMultiplier * (this.ripCount[connId] ?? 0))
)
}

private computeMoveCostAndRips(
activeConn: ConnId,
fromZ: number,
Expand Down Expand Up @@ -794,7 +805,7 @@ export class HighDensitySolverA01 extends BaseSolver {
for (let i = 0; i < occs.length; i++) {
const occ = occs[i]!
if (!rippedContains(r, occ)) {
cost += this.hyperParameters.ripCost
cost += this.getRipCost(occ)
r = { id: occ, prev: r }
}
cost += this.hyperParameters.ripViaPenalty
Expand Down Expand Up @@ -838,7 +849,7 @@ export class HighDensitySolverA01 extends BaseSolver {
this.overlapFriendlyRootNets.has(this.connIdToRootNet[activeConn]!)
if (occ !== -1 && occ !== activeConn && !allowSameRootOverlap) {
if (!rippedContains(r, occ)) {
cost += this.hyperParameters.ripCost
cost += this.getRipCost(occ)
r = { id: occ, prev: r }
}
cost += this.hyperParameters.ripTracePenalty
Expand All @@ -859,7 +870,7 @@ export class HighDensitySolverA01 extends BaseSolver {
for (let i = 0; i < viaOccs.length; i++) {
const viaOwner = viaOccs[i]!
if (!rippedContains(r, viaOwner)) {
cost += this.hyperParameters.ripCost
cost += this.getRipCost(viaOwner)
r = { id: viaOwner, prev: r }
}
cost += this.hyperParameters.ripViaPenalty
Expand Down
45 changes: 37 additions & 8 deletions lib/HighDensitySolverA03/HighDensitySolverA03.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,18 @@ interface ConnectionSeg {
connId: ConnId
startZ: number
startCellId: number
startPoint: { x: number; y: number; z: number }
startPoint: PortPoint
endZ: number
endCellId: number
endPoint: { x: number; y: number; z: number }
endPoint: PortPoint
}

interface SolvedRouteInternal {
connId: ConnId
states: Int32Array
viaCellIds: Int32Array
startPoint: { x: number; y: number; z: number }
endPoint: { x: number; y: number; z: number }
startPoint: PortPoint
endPoint: PortPoint
}

interface HyperParameters {
Expand Down Expand Up @@ -327,6 +327,8 @@ export interface HighDensitySolverA03Props {
showPenaltyMap?: boolean
showUsedCellMap?: boolean
effort?: number
/** Enable diagonal edges within each of the five grid regions. */
enableDiagonalMoves?: boolean
hyperParameters?: Partial<HyperParameters>
initialPenaltyFn?: (params: {
x: number
Expand Down Expand Up @@ -358,9 +360,11 @@ export class HighDensitySolverA03 extends BaseSolver {
showPenaltyMap: boolean
showUsedCellMap: boolean
effort: number
enableDiagonalMoves: boolean
stepMultiplier: number
hyperParameters: HyperParameters
initialPenaltyFn?: HighDensitySolverA03Props["initialPenaltyFn"]
protected preserveExactSameCellEndpoints = false

boundsMinX!: number
boundsMaxX!: number
Expand Down Expand Up @@ -521,6 +525,7 @@ export class HighDensitySolverA03 extends BaseSolver {
this.showPenaltyMap = props.showPenaltyMap ?? false
this.showUsedCellMap = props.showUsedCellMap ?? false
this.effort = props.effort ?? 1
this.enableDiagonalMoves = props.enableDiagonalMoves ?? false
this.stepMultiplier = Math.max(1, Math.floor(props.stepMultiplier ?? 1))
this.hyperParameters = {
shuffleSeed: 0,
Expand Down Expand Up @@ -552,6 +557,7 @@ export class HighDensitySolverA03 extends BaseSolver {
showPenaltyMap: this.showPenaltyMap,
showUsedCellMap: this.showUsedCellMap,
effort: this.effort,
enableDiagonalMoves: this.enableDiagonalMoves,
hyperParameters: this.hyperParameters,
initialPenaltyFn: this.initialPenaltyFn,
},
Expand Down Expand Up @@ -883,6 +889,20 @@ export class HighDensitySolverA03 extends BaseSolver {
this.cellIdFor(region.id, row, col + 1),
)
}
if (this.enableDiagonalMoves && row + 1 < region.rows) {
if (col + 1 < region.cols) {
addBidirectionalEdge(
cellId,
this.cellIdFor(region.id, row + 1, col + 1),
)
}
if (col > 0) {
addBidirectionalEdge(
cellId,
this.cellIdFor(region.id, row + 1, col - 1),
)
}
}
}
}
}
Expand Down Expand Up @@ -2097,7 +2117,7 @@ export class HighDensitySolverA03 extends BaseSolver {
circles,
rects,
coordinateSystem: "cartesian" as const,
title: `HighDensityA03 [${this.getSolvedRouteCount()} solved, ${this.unsolvedSegs?.length ?? 0} remaining]`,
title: `${this.getSolverName()} [${this.getSolvedRouteCount()} solved, ${this.unsolvedSegs?.length ?? 0} remaining]`,
}
}

Expand All @@ -2123,14 +2143,23 @@ export class HighDensitySolverA03 extends BaseSolver {
z: this.layerToZ.get(z) ?? z,
}
})
if (points.length > 0) {
if (points.length === 1) {
points[0] = { ...route.startPoint }
if (points.length > 1) {
points[points.length - 1] = { ...route.endPoint }
if (
this.preserveExactSameCellEndpoints &&
(route.startPoint.x !== route.endPoint.x ||
route.startPoint.y !== route.endPoint.y ||
route.startPoint.z !== route.endPoint.z)
) {
points.push({ ...route.endPoint })
}
} else if (points.length > 1) {
points[0] = { ...route.startPoint }
points[points.length - 1] = { ...route.endPoint }
}
result.push({
connectionName: connName,
rootConnectionName: this.connIdToRootNet[connId],
regionId: this.nodeWithPortPoints.capacityMeshNodeId,
traceThickness: this.traceThickness,
viaDiameter: this.viaDiameter,
Expand Down
1 change: 1 addition & 0 deletions lib/HighDensitySolverA11/HighDensitySolverA11.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ export function getA11CellSizeMm(props: HighDensitySolverA11Props): number {

export class HighDensitySolverA11 extends HighDensitySolverA01 {
protected override useExactViaTraceClearance = true
protected override ripHistoryCostMultiplier = 1

override getSolverName(): string {
return "HighDensitySolverA11"
Expand Down
Loading
Loading