This package contains a GlobalDrcForceImproveSolver for improving high-density PCB routes against DRC-style errors.
bun install Run the solver debugger page:
bun run startRun tests:
bun testRun typecheck:
bun run typecheckRun the DRC14 benchmark locally:
bun run benchmark:drc14You can also use the shell wrapper, which is the entrypoint used by CI:
./benchmark.sh
./benchmark.sh 10
./benchmark.sh --limit all --concurrency 4
./benchmark.sh --scenario-limit 20 --effort 2 --max-iterations 100The benchmark runs the pinned dataset-drc14 samples through GlobalDrcForceImproveSolver. It prints each sample's initial-to-final DRC count, then prints a summary table. By default it writes benchmark-result.json; this is generated output and is ignored by git.
Useful options:
--limit/--scenario-limit: run the first N samples, orall--concurrency N|auto: number of Bun workers--effort: solver effort value--max-iterations: solver max iteration override--out: output path for the JSON benchmark report--no-out: skip writing the JSON report--json: print the JSON report to stdout--fail-on-drc: exit non-zero if final DRC issues remain
Benchmark CI can be triggered by commenting on a PR:
/benchmark [benchmark.sh args...]Examples:
/benchmark
/benchmark 10
/benchmark all --concurrency 4
/benchmark --scenario-limit all --effort 2
/benchmark --scenario-limit 20 --max-iterations 100PRs with [BENCHMARK TEST] in the title run the benchmark workflow automatically on PR updates. The workflow can also be run manually with workflow_dispatch, including scenario limit, concurrency, effort, max iterations, and ref inputs.
For PR comment runs, CI posts Markdown tables for the PR benchmark and the latest successful main benchmark artifact when available. The PR table includes deltas versus main for DRC counts and timing metrics.
The main export is GlobalDrcForceImproveSolver:
import { GlobalDrcForceImproveSolver } from "high-density-repair03"
const solver = new GlobalDrcForceImproveSolver({
srj: {
bounds: { minX: 0, minY: 0, maxX: 10, maxY: 10 },
connections: [{ name: "A" }, { name: "B" }],
obstacles: [],
layerCount: 2,
minTraceWidth: 0.1,
minViaDiameter: 0.3,
defaultObstacleMargin: 0.1,
},
hdRoutes: [
{
connectionName: "A",
route: [
{ x: 1, y: 5, z: 0 },
{ x: 5, y: 5, z: 0 },
{ x: 9, y: 5, z: 0 },
],
vias: [],
traceThickness: 0.1,
viaDiameter: 0.3,
},
{
connectionName: "B",
route: [
{ x: 5, y: 1, z: 0 },
{ x: 5, y: 5, z: 0 },
{ x: 5, y: 9, z: 0 },
],
vias: [],
traceThickness: 0.1,
viaDiameter: 0.3,
},
],
drcEvaluator: ({ traces }) => {
const horizontal = traces.find((trace) => trace.connection_name === "A")
const vertical = traces.find((trace) => trace.connection_name === "B")
const hMid = horizontal?.route[1]
const vMid = vertical?.route[1]
if (!hMid || !vMid) return []
const distance = Math.hypot(hMid.x - vMid.x, hMid.y - vMid.y)
return distance < 0.15
? [
{
message: `trace clearance gap: ${distance.toFixed(3)}mm required: 0.150mm`,
center: { x: (hMid.x + vMid.x) / 2, y: (hMid.y + vMid.y) / 2 },
pcb_trace_id: "A_0",
},
]
: []
},
})
solver.solve()
const output = solver.getOutput()
console.log(output.drc.count)
console.log(output.hdRoutes)The solver follows the same high-level approach as the GlobalDrcForceImproveSolver in tscircuit-autorouter:
- Score the current routed solution with relaxed DRC.
- Try one broad repulsion pass across vias, traces, and obstacles.
- If that helps, keep it.
- Run targeted error-centered force passes using the current DRC error centers.
- Accept a candidate only when it improves the upstream objective: lower DRC count, or for equal count, lower issue score, or for equal count, fewer via DRC issues.
That means this repo is intended to mirror the same repair concept and selection criteria used in the autorouter repo, while exposing a friendlier standalone package API and debugger visualization.
srj expects:
bounds: routing area{ minX, minY, maxX, maxY }connections: logical connection list, each with at leastnameobstacles: rectangular keepouts or copper obstacleslayerCount: board layer countminTraceWidth: minimum allowed trace widthminViaDiameter: optional via diameter defaultdefaultObstacleMargin: optional obstacle margin default
hdRoutes expects one or more routes shaped like:
{
connectionName: "A",
route: [
{ x: 1, y: 5, z: 0 },
{ x: 5, y: 5, z: 0 },
{ x: 9, y: 5, z: 0 },
],
vias: [],
traceThickness: 0.1,
viaDiameter: 0.3,
}This repo does not bundle a full PCB DRC engine. Instead, you provide drcEvaluator, which receives:
srjroutestraces
It should return either:
- an array of error objects, or
- an object with
errorsand optionalerrorsWithCenters
For best results, each error should include:
message: used to estimate severitycenterorpcb_center: the location of the violationpcb_trace_idfor trace-related violationspcb_via_idsfor via-related violations
solver.getOutput() returns:
{
hdRoutes: HighDensityRoute[],
drc: {
errors: DrcError[],
count: number,
issueScore: number,
traceRouteIndexById: Map<string, number>
}
}Inside tscircuit-autorouter, the solver is typically used as an internal pipeline step after trace widths are assigned:
import { GlobalDrcForceImproveSolver } from "../../solvers/GlobalDrcForceImproveSolver/GlobalDrcForceImproveSolver"
const solver = new GlobalDrcForceImproveSolver({
srj: cms.srjWithPointPairs!,
hdRoutes: cms.traceWidthSolver!.getHdRoutesWithWidths(),
effort: cms.effort,
})
solver.solve()
const repairedRoutes = solver.getOutput()That is the same pattern used in AutoroutingPipelineSolver4_TinyHypergraph in your local tscircuit-autorouter checkout.
For direct tests in tscircuit-autorouter, usage looks like:
import { GlobalDrcForceImproveSolver } from "lib/solvers/GlobalDrcForceImproveSolver/GlobalDrcForceImproveSolver"
const solver = new GlobalDrcForceImproveSolver({
srj,
hdRoutes: inputRoutes,
effort: 1,
})
solver.solve()
const outputRoutes = solver.getOutput()One API difference to keep in mind:
- In
tscircuit-autorouter,getOutput()returnsHighDensityRoute[]. - In this standalone repo,
getOutput()returns{ hdRoutes, drc }so you can inspect the resulting DRC snapshot directly.
MySolver is still exported as a thin wrapper around GlobalDrcForceImproveSolver with empty defaults, mainly for the existing debugger page and smoke tests.