diff --git a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts index 88db103..e17dc96 100644 --- a/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver.ts @@ -38,6 +38,13 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } override _step() { + // For decoupling cap partitions, use specialized linear layout + if (this.partitionInputProblem.partitionType === "decoupling_caps") { + this.layout = this.createDecouplingCapsLayout() + this.solved = true + return + } + // Initialize PackSolver2 if not already created if (!this.activeSubSolver) { const packInput = this.createPackInput() @@ -141,6 +148,111 @@ export class SingleInnerPartitionPackingSolver extends BaseSolver { } } + /** + * Creates a specialized linear layout for decoupling capacitors. + * Arranges them in a horizontal row with consistent spacing and orientation. + */ + private createDecouplingCapsLayout(): OutputLayout { + const chipPlacements: Record = {} + const chips = Object.values(this.partitionInputProblem.chipMap) + + if (chips.length === 0) { + return { chipPlacements, groupPlacements: {} } + } + + // Get the gap to use between capacitors + const gap = + this.partitionInputProblem.decouplingCapsGap ?? + this.partitionInputProblem.chipGap + + // Sort chips by ID for consistent ordering + const sortedChips = [...chips].sort((a, b) => + a.chipId.localeCompare(b.chipId), + ) + + // Determine if chips should be laid out horizontally or vertically + // Prefer horizontal if chips are taller than they are wide + const firstChip = sortedChips[0]! + const isHorizontalLayout = firstChip.size.y > firstChip.size.x + + // Calculate positions for each chip in a linear arrangement + let currentOffset = 0 + + for (const chip of sortedChips) { + // Use rotation 0 for consistent orientation + const rotation = 0 + + if (isHorizontalLayout) { + // Horizontal row layout + const x = currentOffset + chip.size.x / 2 + const y = 0 + + chipPlacements[chip.chipId] = { + x, + y, + ccwRotationDegrees: rotation, + } + + currentOffset += chip.size.x + gap + } else { + // Vertical column layout + const x = 0 + const y = currentOffset + chip.size.y / 2 + + chipPlacements[chip.chipId] = { + x, + y, + ccwRotationDegrees: rotation, + } + + currentOffset += chip.size.y + gap + } + } + + // Center the entire layout around (0, 0) + const layoutBounds = this.calculateLayoutBounds(chipPlacements, chips) + const centerX = (layoutBounds.minX + layoutBounds.maxX) / 2 + const centerY = (layoutBounds.minY + layoutBounds.maxY) / 2 + + for (const chipId of Object.keys(chipPlacements)) { + chipPlacements[chipId]!.x -= centerX + chipPlacements[chipId]!.y -= centerY + } + + return { + chipPlacements, + groupPlacements: {}, + } + } + + /** + * Calculate the bounding box of the current layout + */ + private calculateLayoutBounds( + chipPlacements: Record, + chips: any[], + ): { minX: number; maxX: number; minY: number; maxY: number } { + let minX = Number.POSITIVE_INFINITY + let maxX = Number.NEGATIVE_INFINITY + let minY = Number.POSITIVE_INFINITY + let maxY = Number.NEGATIVE_INFINITY + + for (const chip of chips) { + const placement = chipPlacements[chip.chipId] + if (!placement) continue + + const halfWidth = chip.size.x / 2 + const halfHeight = chip.size.y / 2 + + minX = Math.min(minX, placement.x - halfWidth) + maxX = Math.max(maxX, placement.x + halfWidth) + minY = Math.min(minY, placement.y - halfHeight) + maxY = Math.max(maxY, placement.y + halfHeight) + } + + return { minX, maxX, minY, maxY } + } + private createLayoutFromPackingResult( packedComponents: PackSolver2["packedComponents"], ): OutputLayout { diff --git a/tests/LayoutPipelineSolver/LayoutPipelineSolver06DecouplingCaps.test.ts b/tests/LayoutPipelineSolver/LayoutPipelineSolver06DecouplingCaps.test.ts new file mode 100644 index 0000000..3d29f7b --- /dev/null +++ b/tests/LayoutPipelineSolver/LayoutPipelineSolver06DecouplingCaps.test.ts @@ -0,0 +1,173 @@ +import { test, expect } from "bun:test" +import { LayoutPipelineSolver } from "../../lib/solvers/LayoutPipelineSolver/LayoutPipelineSolver" +import type { InputProblem } from "../../lib/types/InputProblem" + +// Problem data from LayoutPipelineSolver06.page.tsx +const problem: InputProblem = { + chipMap: { + U3: { + chipId: "U3", + pins: [ + "U3.1", "U3.2", "U3.3", "U3.4", "U3.5", "U3.6", "U3.7", "U3.8", "U3.9", "U3.10", + "U3.11", "U3.12", "U3.13", "U3.14", "U3.15", "U3.16", "U3.17", "U3.18", "U3.19", "U3.20", + "U3.21", "U3.22", "U3.23", "U3.24", "U3.25", "U3.26", "U3.27", "U3.28", "U3.29", "U3.30", + "U3.31", "U3.32", "U3.33", "U3.34", "U3.35", "U3.36", "U3.37", "U3.38", "U3.39", "U3.40", + "U3.41", "U3.42", "U3.43", "U3.44", "U3.45", "U3.46", "U3.47", "U3.48", "U3.49", "U3.50", + "U3.51", "U3.52", "U3.53", "U3.54", "U3.55", "U3.56", "U3.57" + ], + size: { x: 3, y: 8.400000000000004 }, + availableRotations: [0, 90, 180, 270], + }, + C12: { chipId: "C12", pins: ["C12.1", "C12.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C14: { chipId: "C14", pins: ["C14.1", "C14.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C8: { chipId: "C8", pins: ["C8.1", "C8.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C13: { chipId: "C13", pins: ["C13.1", "C13.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C15: { chipId: "C15", pins: ["C15.1", "C15.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C19: { chipId: "C19", pins: ["C19.1", "C19.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C18: { chipId: "C18", pins: ["C18.1", "C18.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C7: { chipId: "C7", pins: ["C7.1", "C7.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C9: { chipId: "C9", pins: ["C9.1", "C9.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C10: { chipId: "C10", pins: ["C10.1", "C10.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + C11: { chipId: "C11", pins: ["C11.1", "C11.2"], size: { x: 0.53, y: 1.06 }, availableRotations: [0] }, + }, + chipPinMap: { + // U3 pins - simplified to x- and x+ sides + "U3.1": { pinId: "U3.1", offset: { x: -1.9, y: 1.2000000000000015 }, side: "x-" }, + "U3.2": { pinId: "U3.2", offset: { x: -1.9, y: -1.8000000000000007 }, side: "x-" }, + "U3.10": { pinId: "U3.10", offset: { x: -1.9, y: 1.0000000000000013 }, side: "x-" }, + "U3.22": { pinId: "U3.22", offset: { x: -1.9, y: 0.8000000000000012 }, side: "x-" }, + "U3.23": { pinId: "U3.23", offset: { x: -1.9, y: 2.8000000000000016 }, side: "x-" }, + "U3.33": { pinId: "U3.33", offset: { x: -1.9, y: 0.600000000000001 }, side: "x-" }, + "U3.42": { pinId: "U3.42", offset: { x: -1.9, y: 0.4000000000000008 }, side: "x-" }, + "U3.43": { pinId: "U3.43", offset: { x: -1.9, y: -1.6000000000000005 }, side: "x-" }, + "U3.44": { pinId: "U3.44", offset: { x: -1.9, y: 2.0000000000000018 }, side: "x-" }, + "U3.49": { pinId: "U3.49", offset: { x: -1.9, y: 0.20000000000000062 }, side: "x-" }, + "U3.50": { pinId: "U3.50", offset: { x: -1.9, y: 2.600000000000002 }, side: "x-" }, + // Capacitor pins - y+ and y- sides + "C7.1": { pinId: "C7.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C7.2": { pinId: "C7.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C8.1": { pinId: "C8.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C8.2": { pinId: "C8.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C9.1": { pinId: "C9.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C9.2": { pinId: "C9.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C10.1": { pinId: "C10.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C10.2": { pinId: "C10.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C11.1": { pinId: "C11.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C11.2": { pinId: "C11.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C12.1": { pinId: "C12.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C12.2": { pinId: "C12.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C13.1": { pinId: "C13.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C13.2": { pinId: "C13.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C14.1": { pinId: "C14.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C14.2": { pinId: "C14.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C15.1": { pinId: "C15.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C15.2": { pinId: "C15.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C18.1": { pinId: "C18.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C18.2": { pinId: "C18.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C19.1": { pinId: "C19.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C19.2": { pinId: "C19.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + }, + netMap: { + V3_3: { netId: "V3_3", isPositiveVoltageSource: true }, + V1_1: { netId: "V1_1", isPositiveVoltageSource: true }, + GND: { netId: "GND", isGround: true }, + }, + pinStrongConnMap: { + "U3.1-C12.1": true, "C12.1-U3.1": true, + "U3.10-C14.1": true, "C14.1-U3.10": true, + "U3.22-C8.1": true, "C8.1-U3.22": true, + "U3.33-C13.1": true, "C13.1-U3.33": true, + "U3.42-C15.1": true, "C15.1-U3.42": true, + "U3.49-C19.1": true, "C19.1-U3.49": true, + "U3.23-C18.1": true, "C18.1-U3.23": true, + "U3.50-C7.1": true, "C7.1-U3.50": true, + "C11.1-U3.43": true, "U3.43-C11.1": true, + "C10.1-U3.44": true, "U3.44-C10.1": true, + }, + netConnMap: { + "U3.1-V3_3": true, "U3.10-V3_3": true, "U3.22-V3_3": true, "U3.33-V3_3": true, + "U3.42-V3_3": true, "U3.49-V3_3": true, + "C12.1-V3_3": true, "C14.1-V3_3": true, "C8.1-V3_3": true, "C13.1-V3_3": true, + "C15.1-V3_3": true, "C19.1-V3_3": true, + "U3.23-V1_1": true, "U3.50-V1_1": true, + "C18.1-V1_1": true, "C7.1-V1_1": true, "C9.1-V1_1": true, + "C12.2-GND": true, "C14.2-GND": true, "C8.2-GND": true, "C13.2-GND": true, + "C15.2-GND": true, "C19.2-GND": true, "C18.2-GND": true, "C7.2-GND": true, + "C9.2-GND": true, "C10.2-GND": true, "C11.2-GND": true, + }, + chipGap: 0.6, + decouplingCapsGap: 0.2, + partitionGap: 1.2, +} + +test("Decoupling caps layout in full pipeline - LayoutPipelineSolver06", () => { + // Create solver + const solver = new LayoutPipelineSolver(problem) + + // Solve the complete pipeline + solver.solve() + + // Should be solved successfully + expect(solver.solved).toBe(true) + expect(solver.failed).toBe(false) + + // Check decoupling cap detection + console.log("\n=== Decoupling Cap Detection ===") + console.log("Decoupling cap groups identified:", solver.identifyDecouplingCapsSolver?.outputDecouplingCapGroups.length ?? 0) + + for (const group of solver.identifyDecouplingCapsSolver?.outputDecouplingCapGroups ?? []) { + console.log(` Group: ${group.decouplingCapGroupId}`) + console.log(` Main chip: ${group.mainChipId}`) + console.log(` Net pair: [${group.netPair.join(", ")}]`) + console.log(` Capacitors: [${group.decouplingCapChipIds.join(", ")}]`) + } + + // Check partitions + console.log("\n=== Partitions ===") + console.log("Total partitions:", solver.chipPartitions?.length ?? 0) + + for (let i = 0; i < (solver.chipPartitions?.length ?? 0); i++) { + const partition = solver.chipPartitions![i] + const chipIds = Object.keys(partition.chipMap) + console.log(` Partition ${i}:`) + console.log(` Type: ${partition.partitionType ?? "default"}`) + console.log(` Chips: [${chipIds.join(", ")}]`) + + if (partition.partitionType === "decoupling_caps") { + console.log(` *** This is a decoupling caps partition ***`) + } + } + + // Check packed partitions + console.log("\n=== Packed Partitions ===") + console.log("Total packed partitions:", solver.packedPartitions?.length ?? 0) + + for (let i = 0; i < (solver.packedPartitions?.length ?? 0); i++) { + const packed = solver.packedPartitions![i] + const chipIds = Object.keys(packed.inputProblem.chipMap) + const placements = Object.keys(packed.layout.chipPlacements) + console.log(` Packed Partition ${i}:`) + console.log(` Type: ${packed.inputProblem.partitionType ?? "default"}`) + console.log(` Chips: [${chipIds.join(", ")}]`) + console.log(` Placements: [${placements.join(", ")}]`) + + for (const [chipId, placement] of Object.entries(packed.layout.chipPlacements)) { + console.log(` ${chipId}: x=${placement.x.toFixed(2)}, y=${placement.y.toFixed(2)}`) + } + } + + // Test getOutputLayout method + const outputLayout = solver.getOutputLayout() + + // Check for overlaps + console.log("\n=== Overlap Detection ===") + const overlaps = solver.checkForOverlaps(outputLayout) + console.log(`Total overlaps: ${overlaps.length}`) + + for (const overlap of overlaps) { + console.log(` ${overlap.chip1} overlaps ${overlap.chip2} (area: ${overlap.overlapArea.toFixed(4)})`) + } + + // Test should pass if no overlaps + expect(overlaps.length).toBe(0) +}) \ No newline at end of file diff --git a/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolverDecouplingCaps.test.ts b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolverDecouplingCaps.test.ts new file mode 100644 index 0000000..398a758 --- /dev/null +++ b/tests/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolverDecouplingCaps.test.ts @@ -0,0 +1,252 @@ +import { test, expect } from "bun:test" +import { SingleInnerPartitionPackingSolver } from "../../lib/solvers/PackInnerPartitionsSolver/SingleInnerPartitionPackingSolver" +import type { PartitionInputProblem } from "../../lib/types/InputProblem" +import { getPinIdToStronglyConnectedPinsObj } from "../../lib/solvers/LayoutPipelineSolver/getPinIdToStronglyConnectedPinsObj" + +test("createDecouplingCapsLayout creates linear arrangement without overlaps", () => { + // Create a partition with multiple decoupling capacitors + const partitionInputProblem: PartitionInputProblem = { + chipMap: { + C1: { + chipId: "C1", + pins: ["C1.1", "C1.2"], + size: { x: 0.53, y: 1.06 }, + availableRotations: [0], + }, + C2: { + chipId: "C2", + pins: ["C2.1", "C2.2"], + size: { x: 0.53, y: 1.06 }, + availableRotations: [0], + }, + C3: { + chipId: "C3", + pins: ["C3.1", "C3.2"], + size: { x: 0.53, y: 1.06 }, + availableRotations: [0], + }, + C4: { + chipId: "C4", + pins: ["C4.1", "C4.2"], + size: { x: 0.53, y: 1.06 }, + availableRotations: [0], + }, + }, + chipPinMap: { + "C1.1": { pinId: "C1.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C1.2": { pinId: "C1.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C2.1": { pinId: "C2.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C2.2": { pinId: "C2.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C3.1": { pinId: "C3.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C3.2": { pinId: "C3.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + "C4.1": { pinId: "C4.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C4.2": { pinId: "C4.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + }, + netMap: { + VCC: { netId: "VCC", isPositiveVoltageSource: true }, + GND: { netId: "GND", isGround: true }, + }, + pinStrongConnMap: {}, + netConnMap: { + "C1.1-VCC": true, + "C1.2-GND": true, + "C2.1-VCC": true, + "C2.2-GND": true, + "C3.1-VCC": true, + "C3.2-GND": true, + "C4.1-VCC": true, + "C4.2-GND": true, + }, + chipGap: 0.6, + partitionGap: 1.2, + decouplingCapsGap: 0.2, + isPartition: true, + partitionType: "decoupling_caps", + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: getPinIdToStronglyConnectedPinsObj(partitionInputProblem), + }) + + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.failed).toBe(false) + expect(solver.layout).toBeDefined() + + const { chipPlacements } = solver.layout! + + // Check that all capacitors have placements + expect(Object.keys(chipPlacements)).toHaveLength(4) + expect(chipPlacements.C1).toBeDefined() + expect(chipPlacements.C2).toBeDefined() + expect(chipPlacements.C3).toBeDefined() + expect(chipPlacements.C4).toBeDefined() + + // All should have rotation 0 + expect(chipPlacements.C1.ccwRotationDegrees).toBe(0) + expect(chipPlacements.C2.ccwRotationDegrees).toBe(0) + expect(chipPlacements.C3.ccwRotationDegrees).toBe(0) + expect(chipPlacements.C4.ccwRotationDegrees).toBe(0) + + // Check that no capacitors overlap + // Each capacitor is 0.53 x 1.06, with gap of 0.2 + // They should be laid out horizontally since height > width + const positions = [chipPlacements.C1, chipPlacements.C2, chipPlacements.C3, chipPlacements.C4] + .sort((a, b) => a.x - b.x) + + // Check that positions are distinct and in a line + for (let i = 1; i < positions.length; i++) { + const prev = positions[i - 1] + const curr = positions[i] + const distance = Math.abs(curr.x - prev.x) + + // Distance should be at least (width + gap) = 0.53 + 0.2 = 0.73 + // Allow some tolerance for centering + expect(distance).toBeGreaterThan(0.5) + + // Y positions should be the same (linear horizontal layout) + expect(curr.y).toBeCloseTo(prev.y, 5) + } + + console.log("Decoupling cap placements:") + for (const [chipId, placement] of Object.entries(chipPlacements)) { + console.log(` ${chipId}: x=${placement.x.toFixed(2)}, y=${placement.y.toFixed(2)}, rot=${placement.ccwRotationDegrees}`) + } +}) + +test("createDecouplingCapsLayout with wide capacitors uses vertical layout", () => { + // Create a partition with wide decoupling capacitors (taller than wide after rotation) + const partitionInputProblem: PartitionInputProblem = { + chipMap: { + C1: { + chipId: "C1", + pins: ["C1.1", "C1.2"], + size: { x: 1.06, y: 0.53 }, // Wide, not tall + availableRotations: [0], + }, + C2: { + chipId: "C2", + pins: ["C2.1", "C2.2"], + size: { x: 1.06, y: 0.53 }, + availableRotations: [0], + }, + }, + chipPinMap: { + "C1.1": { pinId: "C1.1", offset: { x: 0.55, y: 0 }, side: "x+" }, + "C1.2": { pinId: "C1.2", offset: { x: -0.55, y: 0 }, side: "x-" }, + "C2.1": { pinId: "C2.1", offset: { x: 0.55, y: 0 }, side: "x+" }, + "C2.2": { pinId: "C2.2", offset: { x: -0.55, y: 0 }, side: "x-" }, + }, + netMap: { + VCC: { netId: "VCC", isPositiveVoltageSource: true }, + GND: { netId: "GND", isGround: true }, + }, + pinStrongConnMap: {}, + netConnMap: { + "C1.1-VCC": true, + "C1.2-GND": true, + "C2.1-VCC": true, + "C2.2-GND": true, + }, + chipGap: 0.6, + partitionGap: 1.2, + decouplingCapsGap: 0.2, + isPartition: true, + partitionType: "decoupling_caps", + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: getPinIdToStronglyConnectedPinsObj(partitionInputProblem), + }) + + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout).toBeDefined() + + const { chipPlacements } = solver.layout! + const positions = [chipPlacements.C1, chipPlacements.C2] + .sort((a, b) => a.y - b.y) + + // For wide capacitors, should use vertical layout + // X positions should be the same + expect(positions[0].x).toBeCloseTo(positions[1].x, 5) + + // Y positions should be separated + const distance = Math.abs(positions[1].y - positions[0].y) + expect(distance).toBeGreaterThan(0.5) +}) + +test("createDecouplingCapsLayout with single capacitor", () => { + const partitionInputProblem: PartitionInputProblem = { + chipMap: { + C1: { + chipId: "C1", + pins: ["C1.1", "C1.2"], + size: { x: 0.53, y: 1.06 }, + availableRotations: [0], + }, + }, + chipPinMap: { + "C1.1": { pinId: "C1.1", offset: { x: 0, y: 0.55 }, side: "y+" }, + "C1.2": { pinId: "C1.2", offset: { x: 0, y: -0.55 }, side: "y-" }, + }, + netMap: { + VCC: { netId: "VCC", isPositiveVoltageSource: true }, + GND: { netId: "GND", isGround: true }, + }, + pinStrongConnMap: {}, + netConnMap: { + "C1.1-VCC": true, + "C1.2-GND": true, + }, + chipGap: 0.6, + partitionGap: 1.2, + decouplingCapsGap: 0.2, + isPartition: true, + partitionType: "decoupling_caps", + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: getPinIdToStronglyConnectedPinsObj(partitionInputProblem), + }) + + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout).toBeDefined() + expect(solver.layout!.chipPlacements.C1).toBeDefined() + + // Single cap should be at origin (centered) + expect(solver.layout!.chipPlacements.C1.x).toBe(0) + expect(solver.layout!.chipPlacements.C1.y).toBe(0) +}) + +test("createDecouplingCapsLayout with empty partition", () => { + const partitionInputProblem: PartitionInputProblem = { + chipMap: {}, + chipPinMap: {}, + netMap: {}, + pinStrongConnMap: {}, + netConnMap: {}, + chipGap: 0.6, + partitionGap: 1.2, + isPartition: true, + partitionType: "decoupling_caps", + } + + const solver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem, + pinIdToStronglyConnectedPins: {}, + }) + + solver.solve() + + expect(solver.solved).toBe(true) + expect(solver.layout).toBeDefined() + expect(Object.keys(solver.layout!.chipPlacements)).toHaveLength(0) +}) \ No newline at end of file