diff --git a/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLinearLayoutSolver.ts b/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLinearLayoutSolver.ts new file mode 100644 index 0000000..043a865 --- /dev/null +++ b/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLinearLayoutSolver.ts @@ -0,0 +1,24 @@ +export function applyDecouplingCapsLinearLayout( + chips: Array<{ + chipId: string + center: { x: number; y: number } + ccwRotationDegrees: number + }>, + opts?: { decouplingCapsGap?: number; chipGap?: number }, +) { + if (chips.length === 0) return + + const gap = opts?.decouplingCapsGap ?? opts?.chipGap ?? 0.2 + const chipWidth = 1 + const totalWidth = chips.length * chipWidth + (chips.length - 1) * gap + + const sorted = [...chips].sort((a, b) => a.chipId.localeCompare(b.chipId)) + let x = -totalWidth / 2 + chipWidth / 2 + + for (const chip of sorted) { + chip.center.x = x + chip.center.y = 0 + chip.ccwRotationDegrees = 0 + x += chipWidth + gap + } +} diff --git a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts index 88db103..febb164 100644 --- a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts @@ -18,6 +18,7 @@ import type { import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem" import { createFilteredNetworkMapping } from "../../utils/networkFiltering" import { getPadsBoundingBox } from "./getPadsBoundingBox" +import { applyDecouplingCapsLinearLayout } from "./DecouplingCapsLinearLayoutSolver" import { doBasicInputProblemLayout } from "../LayoutPipelineSolver/doBasicInputProblemLayout" const PIN_SIZE = 0.1 @@ -38,6 +39,32 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } override _step() { + // Fast path: decoupling cap partitions get a clean linear layout + if (this.partitionInputProblem.partitionType === "decoupling_caps") { + const chips = Object.entries(this.partitionInputProblem.chipMap).map( + ([chipId]) => ({ + chipId, + center: { x: 0, y: 0 }, + ccwRotationDegrees: 0, + }), + ) + applyDecouplingCapsLinearLayout(chips, { + decouplingCapsGap: this.partitionInputProblem.decouplingCapsGap, + chipGap: this.partitionInputProblem.chipGap, + }) + this.layout = { + chipPlacements: Object.fromEntries( + chips.map(({ chipId, center, ccwRotationDegrees }) => [ + chipId, + { x: center.x, y: center.y, ccwRotationDegrees }, + ]), + ), + groupPlacements: {}, + } + this.solved = true + return + } + // Initialize PackSolver2 if not already created if (!this.activeSubSolver) { const packInput = this.createPackInput() diff --git a/package.json b/package.json index 1fd506e..455c69f 100644 --- a/package.json +++ b/package.json @@ -31,5 +31,8 @@ }, "peerDependencies": { "typescript": "^5" + }, + "dependencies": { + "circuit-to-svg": "^0.0.343" } } diff --git a/tests/PackInnerPartitionsSolver/DecouplingCapsLayout.test.ts b/tests/PackInnerPartitionsSolver/DecouplingCapsLayout.test.ts new file mode 100644 index 0000000..a56c37e --- /dev/null +++ b/tests/PackInnerPartitionsSolver/DecouplingCapsLayout.test.ts @@ -0,0 +1,47 @@ +import { describe, it, expect } from "bun:test" +import { applyDecouplingCapsLinearLayout } from "../../lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLinearLayoutSolver" + +describe("applyDecouplingCapsLinearLayout", () => { + it("places a single cap at origin", () => { + const chips = [ + { chipId: "C1", center: { x: 99, y: 99 }, ccwRotationDegrees: 45 }, + ] + applyDecouplingCapsLinearLayout(chips) + expect(chips[0]!.center!.x).toBeCloseTo(0) + expect(chips[0]!.center!.y).toBeCloseTo(0) + expect(chips[0]!.ccwRotationDegrees).toBe(0) + }) + + it("centers multiple caps in a horizontal row", () => { + const chips = [ + { chipId: "C1", center: { x: 0, y: 0 }, ccwRotationDegrees: 0 }, + { chipId: "C2", center: { x: 0, y: 0 }, ccwRotationDegrees: 0 }, + { chipId: "C3", center: { x: 0, y: 0 }, ccwRotationDegrees: 0 }, + ] + applyDecouplingCapsLinearLayout(chips, { decouplingCapsGap: 0.2 }) + + // total width = 3*1 + 2*0.2 = 3.4, centered → x in [-1.2, 0, 1.2] + const xs = chips.map((c) => c.center!.x).sort((a, b) => a - b) + expect(xs[0]).toBeCloseTo(-1.2) + expect(xs[1]).toBeCloseTo(0) + expect(xs[2]).toBeCloseTo(1.2) + + chips.forEach((c) => expect(c.center!.y).toBeCloseTo(0)) + }) + + it("does nothing for empty array", () => { + expect(() => applyDecouplingCapsLinearLayout([])).not.toThrow() + }) + + it("sorts chips by chipId before laying out", () => { + const chips = [ + { chipId: "C3", center: { x: 0, y: 0 }, ccwRotationDegrees: 0 }, + { chipId: "C1", center: { x: 0, y: 0 }, ccwRotationDegrees: 0 }, + ] + applyDecouplingCapsLinearLayout(chips, { decouplingCapsGap: 0 }) + // C1 should be leftmost after sort + const c1 = chips.find((c) => c.chipId === "C1")! + const c3 = chips.find((c) => c.chipId === "C3")! + expect(c1.center!.x).toBeLessThan(c3.center!.x) + }) +})