diff --git a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts index 88db103..532fa42 100644 --- a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts @@ -22,6 +22,43 @@ import { doBasicInputProblemLayout } from "../LayoutPipelineSolver/doBasicInputP const PIN_SIZE = 0.1 +const compareNaturalChipIds = (a: ChipId, b: ChipId) => { + const aParts = a.match(/\d+|\D+/g) ?? [a] + const bParts = b.match(/\d+|\D+/g) ?? [b] + const partCount = Math.min(aParts.length, bParts.length) + + for (let i = 0; i < partCount; i++) { + const aPart = aParts[i]! + const bPart = bParts[i]! + const aIsNumber = /^\d+$/.test(aPart) + const bIsNumber = /^\d+$/.test(bPart) + + if (aIsNumber && bIsNumber) { + const diff = Number(aPart) - Number(bPart) + if (diff !== 0) return diff + continue + } + + if (aPart !== bPart) { + return aPart < bPart ? -1 : 1 + } + } + + return aParts.length - bParts.length +} + +const getDecouplingCapRotation = ( + availableRotations?: Array<0 | 90 | 180 | 270>, +) => { + if (!availableRotations?.length) return 0 + return availableRotations.includes(0) ? 0 : availableRotations[0]! +} + +const getRowWidthForRotation = ( + chipSize: { x: number; y: number }, + rotation: number, +) => (rotation === 90 || rotation === 270 ? chipSize.y : chipSize.x) + export class SingleInnerPartitionPackingSolver extends BaseSolver { partitionInputProblem: PartitionInputProblem layout: OutputLayout | null = null @@ -38,6 +75,13 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } override _step() { + if (this.partitionInputProblem.partitionType === "decoupling_caps") { + this.layout = this.createDecouplingCapsRowLayout() + this.solved = true + this.activeSubSolver = null + return + } + // Initialize PackSolver2 if not already created if (!this.activeSubSolver) { const packInput = this.createPackInput() @@ -64,6 +108,41 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } } + private createDecouplingCapsRowLayout(): OutputLayout { + const chipEntries = Object.entries(this.partitionInputProblem.chipMap) + .sort(([aChipId], [bChipId]) => compareNaturalChipIds(aChipId, bChipId)) + .map(([chipId, chip]) => { + const rotation = getDecouplingCapRotation(chip.availableRotations) + return { + chipId, + rowWidth: getRowWidthForRotation(chip.size, rotation), + rotation, + } + }) + const gap = + this.partitionInputProblem.decouplingCapsGap ?? + this.partitionInputProblem.chipGap + const totalWidth = + chipEntries.reduce((sum, chipEntry) => sum + chipEntry.rowWidth, 0) + + Math.max(0, chipEntries.length - 1) * gap + const chipPlacements: Record = {} + let cursorX = -totalWidth / 2 + + for (const { chipId, rowWidth, rotation } of chipEntries) { + chipPlacements[chipId] = { + x: cursorX + rowWidth / 2, + y: 0, + ccwRotationDegrees: rotation, + } + cursorX += rowWidth + gap + } + + return { + chipPlacements, + groupPlacements: {}, + } + } + private createPackInput(): PackInput { // Fall back to filtered mapping (weak + strong) const pinToNetworkMap = createFilteredNetworkMapping({ diff --git a/package.json b/package.json index 1fd506e..821e8bc 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "bpc-graph": "^0.0.66", "calculate-packing": "^0.0.31", "circuit-json": "^0.0.226", + "circuit-to-svg": "^0.0.345", "graphics-debug": "^0.0.64", "react-cosmos": "^7.0.0", "react-cosmos-plugin-vite": "^7.0.0", diff --git a/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts new file mode 100644 index 0000000..24f1975 --- /dev/null +++ b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.test.ts @@ -0,0 +1,181 @@ +import { expect, test } from "bun:test" +import { SingleInnerPartitionPackingSolver } from "../../lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver" +import type { PartitionInputProblem } from "../../lib/types/InputProblem" + +test("SingleInnerPartitionPackingSolver lays out decoupling caps in a centered natural-order row", () => { + const partitionInputProblem: PartitionInputProblem = { + isPartition: true, + partitionType: "decoupling_caps", + chipMap: { + C10: { + chipId: "C10", + pins: [], + size: { x: 1.5, y: 0.5 }, + }, + C2: { + chipId: "C2", + pins: [], + size: { x: 0.5, y: 0.5 }, + }, + C1: { + chipId: "C1", + pins: [], + size: { x: 1, y: 0.5 }, + }, + }, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 0.2, + partitionGap: 2, + decouplingCapsGap: 0.3, + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: {}, + }) + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout?.chipPlacements.C1).toEqual({ + x: -1.3, + y: 0, + ccwRotationDegrees: 0, + }) + expect(solver.layout?.chipPlacements.C2).toEqual({ + x: -0.25, + y: 0, + ccwRotationDegrees: 0, + }) + expect(solver.layout?.chipPlacements.C10).toEqual({ + x: 1.05, + y: 0, + ccwRotationDegrees: 0, + }) + + const c1Right = + solver.layout!.chipPlacements.C1!.x + + partitionInputProblem.chipMap.C1!.size.x / 2 + const c2Left = + solver.layout!.chipPlacements.C2!.x - + partitionInputProblem.chipMap.C2!.size.x / 2 + const c2Right = + solver.layout!.chipPlacements.C2!.x + + partitionInputProblem.chipMap.C2!.size.x / 2 + const c10Left = + solver.layout!.chipPlacements.C10!.x - + partitionInputProblem.chipMap.C10!.size.x / 2 + + expect(c2Left - c1Right).toBeCloseTo(0.3) + expect(c10Left - c2Right).toBeCloseTo(0.3) +}) + +test("SingleInnerPartitionPackingSolver falls back to chipGap for decoupling cap spacing", () => { + const partitionInputProblem: PartitionInputProblem = { + isPartition: true, + partitionType: "decoupling_caps", + chipMap: { + C1: { + chipId: "C1", + pins: [], + size: { x: 1, y: 0.5 }, + }, + C2: { + chipId: "C2", + pins: [], + size: { x: 1, y: 0.5 }, + }, + }, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 0.4, + partitionGap: 2, + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: {}, + }) + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout?.chipPlacements.C1?.x).toBeCloseTo(-0.7) + expect(solver.layout?.chipPlacements.C2?.x).toBeCloseTo(0.7) +}) + +test("SingleInnerPartitionPackingSolver respects fixed decoupling cap rotations", () => { + const partitionInputProblem: PartitionInputProblem = { + isPartition: true, + partitionType: "decoupling_caps", + chipMap: { + C1: { + chipId: "C1", + pins: [], + size: { x: 1, y: 2 }, + availableRotations: [90], + }, + C2: { + chipId: "C2", + pins: [], + size: { x: 1, y: 0.5 }, + }, + }, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 0.2, + partitionGap: 2, + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: {}, + }) + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout?.chipPlacements.C1?.x).toBeCloseTo(-0.6) + expect(solver.layout?.chipPlacements.C1?.y).toBe(0) + expect(solver.layout?.chipPlacements.C1?.ccwRotationDegrees).toBe(90) + expect(solver.layout?.chipPlacements.C2?.x).toBeCloseTo(1.1) + expect(solver.layout?.chipPlacements.C2?.y).toBe(0) + expect(solver.layout?.chipPlacements.C2?.ccwRotationDegrees).toBe(0) +}) + +test("SingleInnerPartitionPackingSolver places a single decoupling cap at the origin", () => { + const partitionInputProblem: PartitionInputProblem = { + isPartition: true, + partitionType: "decoupling_caps", + chipMap: { + C1: { + chipId: "C1", + pins: [], + size: { x: 1, y: 0.5 }, + }, + }, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 0.2, + partitionGap: 2, + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: {}, + }) + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout?.chipPlacements.C1).toEqual({ + x: 0, + y: 0, + ccwRotationDegrees: 0, + }) +})