diff --git a/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts b/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts index e9bac7ca3..e376b5ea2 100644 --- a/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts +++ b/lib/solvers/TraceCleanupSolver/TraceCleanupSolver.ts @@ -20,6 +20,7 @@ interface TraceCleanupSolverInput { import { UntangleTraceSubsolver } from "./sub-solver/UntangleTraceSubsolver" import { is4PointRectangle } from "./is4PointRectangle" +import { mergeSameNetCollinearTraces } from "./mergeSameNetCollinearTraces" /** * Represents the different stages or steps within the trace cleanup pipeline. @@ -49,11 +50,9 @@ export class TraceCleanupSolver extends BaseSolver { constructor(solverInput: TraceCleanupSolverInput) { super() this.input = solverInput - this.outputTraces = [...solverInput.allTraces] + this.outputTraces = mergeSameNetCollinearTraces(solverInput.allTraces) this.tracesMap = new Map(this.outputTraces.map((t) => [t.mspPairId, t])) - this.traceIdQueue = Array.from( - solverInput.allTraces.map((e) => e.mspPairId), - ) + this.traceIdQueue = Array.from(this.outputTraces.map((e) => e.mspPairId)) } override _step() { @@ -97,9 +96,7 @@ export class TraceCleanupSolver extends BaseSolver { private _runMinimizeTurnsStep() { if (this.traceIdQueue.length === 0) { this.pipelineStep = "balancing_l_shapes" - this.traceIdQueue = Array.from( - this.input.allTraces.map((e) => e.mspPairId), - ) + this.traceIdQueue = Array.from(this.outputTraces.map((e) => e.mspPairId)) return } diff --git a/lib/solvers/TraceCleanupSolver/mergeSameNetCollinearTraces.ts b/lib/solvers/TraceCleanupSolver/mergeSameNetCollinearTraces.ts new file mode 100644 index 000000000..394559b92 --- /dev/null +++ b/lib/solvers/TraceCleanupSolver/mergeSameNetCollinearTraces.ts @@ -0,0 +1,96 @@ +import type { Point } from "@tscircuit/math-utils" +import type { SolvedTracePath } from "lib/solvers/SchematicTraceLinesSolver/SchematicTraceLinesSolver" +import { simplifyPath } from "./simplifyPath" + +const EPS = 1e-9 + +const pointsEqual = (a: Point, b: Point) => + Math.abs(a.x - b.x) < EPS && Math.abs(a.y - b.y) < EPS + +const isHorizontalPath = (path: Point[]) => + path.length >= 2 && path.every((p) => Math.abs(p.y - path[0]!.y) < EPS) + +const isVerticalPath = (path: Point[]) => + path.length >= 2 && path.every((p) => Math.abs(p.x - path[0]!.x) < EPS) + +const canMergePair = (a: SolvedTracePath, b: SolvedTracePath) => { + if (a.globalConnNetId !== b.globalConnNetId) return false + + const aIsHorizontal = isHorizontalPath(a.tracePath) + const bIsHorizontal = isHorizontalPath(b.tracePath) + if (aIsHorizontal && bIsHorizontal) { + return Math.abs(a.tracePath[0]!.y - b.tracePath[0]!.y) < EPS + } + + const aIsVertical = isVerticalPath(a.tracePath) + const bIsVertical = isVerticalPath(b.tracePath) + if (aIsVertical && bIsVertical) { + return Math.abs(a.tracePath[0]!.x - b.tracePath[0]!.x) < EPS + } + + return false +} + +const mergePair = ( + a: SolvedTracePath, + b: SolvedTracePath, +): SolvedTracePath | null => { + const aStart = a.tracePath[0]! + const aEnd = a.tracePath[a.tracePath.length - 1]! + const bStart = b.tracePath[0]! + const bEnd = b.tracePath[b.tracePath.length - 1]! + + let tracePath: Point[] | null = null + if (pointsEqual(aEnd, bStart)) + tracePath = [...a.tracePath, ...b.tracePath.slice(1)] + else if (pointsEqual(bEnd, aStart)) + tracePath = [...b.tracePath, ...a.tracePath.slice(1)] + else if (pointsEqual(aStart, bStart)) + tracePath = [...a.tracePath.slice().reverse(), ...b.tracePath.slice(1)] + else if (pointsEqual(aEnd, bEnd)) + tracePath = [...a.tracePath, ...b.tracePath.slice(0, -1).reverse()] + + if (!tracePath) return null + + return { + ...a, + mspPairId: `${a.mspPairId}+${b.mspPairId}`, + mspConnectionPairIds: Array.from( + new Set([ + ...(a.mspConnectionPairIds ?? [a.mspPairId]), + ...(b.mspConnectionPairIds ?? [b.mspPairId]), + ]), + ), + pinIds: Array.from(new Set([...(a.pinIds ?? []), ...(b.pinIds ?? [])])), + pins: [a.pins[0], b.pins[1]], + tracePath: simplifyPath(tracePath), + } +} + +export const mergeSameNetCollinearTraces = ( + traces: SolvedTracePath[], +): SolvedTracePath[] => { + const remaining = [...traces] + let changed = true + + while (changed) { + changed = false + outer: for (let i = 0; i < remaining.length; i++) { + for (let j = i + 1; j < remaining.length; j++) { + const a = remaining[i]! + const b = remaining[j]! + if (!canMergePair(a, b)) continue + + const merged = mergePair(a, b) + if (!merged) continue + + remaining.splice(j, 1) + remaining.splice(i, 1, merged) + changed = true + break outer + } + } + } + + return remaining +} diff --git a/tests/solvers/TraceCleanupSolver/mergeSameNetCollinearTraces.test.ts b/tests/solvers/TraceCleanupSolver/mergeSameNetCollinearTraces.test.ts new file mode 100644 index 000000000..91194c7d0 --- /dev/null +++ b/tests/solvers/TraceCleanupSolver/mergeSameNetCollinearTraces.test.ts @@ -0,0 +1,82 @@ +import { expect, test } from "bun:test" +import { mergeSameNetCollinearTraces } from "lib/solvers/TraceCleanupSolver/mergeSameNetCollinearTraces" + +const pin = (pinId: string, x: number, y: number) => ({ + pinId, + x, + y, + chipId: pinId.split(".")[0]!, +}) + +const makeTrace = ({ id, net, points }: any) => ({ + mspPairId: id, + dcConnNetId: net, + globalConnNetId: net, + userNetId: net, + pins: [ + pin(`${id}.a`, points[0].x, points[0].y), + pin(`${id}.b`, points.at(-1).x, points.at(-1).y), + ], + tracePath: points, + mspConnectionPairIds: [id], + pinIds: [`${id}.a`, `${id}.b`], +}) + +test("merges adjacent same-net horizontal traces on the same y", () => { + const output = mergeSameNetCollinearTraces([ + makeTrace({ + id: "a-b", + net: "connectivity_net0", + points: [ + { x: 0, y: 1 }, + { x: 1, y: 1 }, + ], + }), + makeTrace({ + id: "b-c", + net: "connectivity_net0", + points: [ + { x: 1, y: 1 }, + { x: 2, y: 1 }, + ], + }), + ] as any) + + expect(output).toHaveLength(1) + expect(output[0]!.tracePath).toEqual([ + { x: 0, y: 1 }, + { x: 2, y: 1 }, + ]) + expect(output[0]!.mspConnectionPairIds).toEqual(["a-b", "b-c"]) +}) + +test("does not merge different nets or non-collinear traces", () => { + const output = mergeSameNetCollinearTraces([ + makeTrace({ + id: "a-b", + net: "connectivity_net0", + points: [ + { x: 0, y: 1 }, + { x: 1, y: 1 }, + ], + }), + makeTrace({ + id: "b-c", + net: "connectivity_net1", + points: [ + { x: 1, y: 1 }, + { x: 2, y: 1 }, + ], + }), + makeTrace({ + id: "c-d", + net: "connectivity_net0", + points: [ + { x: 1, y: 2 }, + { x: 2, y: 2 }, + ], + }), + ] as any) + + expect(output).toHaveLength(3) +})