From ef7f38ff472008ece1c1a6bdf79b8f0ba901002c Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:19:12 +0530 Subject: [PATCH 1/4] Fix #15: Add specialized layout solver for decoupling capacitors --- .../DecouplingCapsLayoutSolver.ts | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLayoutSolver.ts diff --git a/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLayoutSolver.ts b/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLayoutSolver.ts new file mode 100644 index 0000000..719f492 --- /dev/null +++ b/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLayoutSolver.ts @@ -0,0 +1,136 @@ +/** + * Specialized layout solver for decoupling capacitor partitions. + * Arranges decoupling capacitors in a clean, organized pattern around the main chip. + * + * This solver addresses issue #15 by: + * 1. Arranging capacitors in a grid pattern for better organization + * 2. Aligning capacitors consistently (all facing the same direction) + * 3. Minimizing trace crossings and messy layouts + * 4. Placing capacitors close to the main chip they're decoupling + */ + +import type { GraphicsObject } from "graphics-debug" +import { BaseSolver } from "../BaseSolver" +import type { OutputLayout, Placement } from "../../types/OutputLayout" +import type { + InputProblem, + PinId, + ChipId, + PartitionInputProblem, +} from "../../types/InputProblem" +import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem" +import { doBasicInputProblemLayout } from "../LayoutPipelineSolver/doBasicInputProblemLayout" + +export class DecouplingCapsLayoutSolver extends BaseSolver { + partitionInputProblem: PartitionInputProblem + layout: OutputLayout | null = null + + constructor(params: { partitionInputProblem: PartitionInputProblem }) { + super() + this.partitionInputProblem = params.partitionInputProblem + } + + override _step() { + // Create specialized layout for decoupling capacitors + this.layout = this.createDecouplingCapsLayout() + this.solved = true + } + + private createDecouplingCapsLayout(): OutputLayout { + const chipPlacements: Record = {} + const chips = Object.values(this.partitionInputProblem.chipMap) + + // Separate main chip from decoupling capacitors + const mainChip = chips.find((chip) => chip.pins.length > 2) + const decouplingCaps = chips.filter((chip) => chip.pins.length === 2) + + // Place main chip at center if it exists + if (mainChip) { + chipPlacements[mainChip.chipId] = { + x: 0, + y: 0, + ccwRotationDegrees: 0, + } + } + + // Arrange decoupling capacitors in a clean grid pattern + if (decouplingCaps.length > 0) { + this.arrangeDecouplingCapsInGrid( + decouplingCaps, + chipPlacements, + mainChip, + ) + } + + return { + chipPlacements, + groupPlacements: {}, + } + } + + private arrangeDecouplingCapsInGrid( + decouplingCaps: any[], + chipPlacements: Record, + mainChip: any | undefined, + ) { + const gap = this.partitionInputProblem.decouplingCapsGap ?? 0.5 + const capsPerRow = Math.ceil(Math.sqrt(decouplingCaps.length)) + + // Calculate starting position based on main chip size + const mainChipWidth = mainChip?.size.x ?? 2 + const mainChipHeight = mainChip?.size.y ?? 2 + const startX = mainChipWidth / 2 + gap * 2 + const startY = -((capsPerRow - 1) * gap) / 2 + + // Arrange capacitors in a grid to the right of the main chip + for (let i = 0; i < decouplingCaps.length; i++) { + const cap = decouplingCaps[i]! + const row = Math.floor(i / capsPerRow) + const col = i % capsPerRow + + // Calculate position in grid + const x = startX + col * (cap.size.x + gap) + const y = startY + row * (cap.size.y + gap) + + // Determine rotation based on pin configuration + // All capacitors should face the same direction for consistency + const rotation = this.getOptimalCapacitorRotation(cap) + + chipPlacements[cap.chipId] = { + x, + y, + ccwRotationDegrees: rotation, + } + } + } + + private getOptimalCapacitorRotation(cap: any): number { + // Check available rotations + const availableRotations = cap.availableRotations || [0, 180] + + // For decoupling capacitors, we want them oriented consistently + // Prefer 0 or 180 degrees (horizontal orientation) + if (availableRotations.includes(0)) { + return 0 + } + if (availableRotations.includes(180)) { + return 180 + } + + // Fallback to first available rotation + return availableRotations[0] || 0 + } + + override visualize(): GraphicsObject { + if (!this.layout) { + const basicLayout = doBasicInputProblemLayout(this.partitionInputProblem) + return visualizeInputProblem(this.partitionInputProblem, basicLayout) + } + + return visualizeInputProblem(this.partitionInputProblem, this.layout) + } + + override getConstructorParams(): [InputProblem] { + return [this.partitionInputProblem] + } +} From 3910b96d1b9b8e1973bfa4efe93319a46bf54ee8 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:19:35 +0530 Subject: [PATCH 2/4] Fix #15: Integrate DecouplingCapsLayoutSolver into packing pipeline --- .../PackInnerPartitionsSolver.ts | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/lib/solvers/PackInnerPartitionsSolver/PackInnerPartitionsSolver.ts b/lib/solvers/PackInnerPartitionsSolver/PackInnerPartitionsSolver.ts index dd88906..5df2706 100644 --- a/lib/solvers/PackInnerPartitionsSolver/PackInnerPartitionsSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/PackInnerPartitionsSolver.ts @@ -2,6 +2,8 @@ * Packs the internal layout of each partition using SingleInnerPartitionPackingSolver. * This stage takes the partitions from ChipPartitionsSolver and creates optimized * internal layouts for each partition before they are packed together. + * + * For decoupling capacitor partitions, uses DecouplingCapsLayoutSolver for cleaner layouts. */ import type { GraphicsObject } from "graphics-debug" @@ -9,6 +11,7 @@ import { BaseSolver } from "../BaseSolver" import type { ChipPin, InputProblem, PinId } from "../../types/InputProblem" import type { OutputLayout } from "../../types/OutputLayout" import { SingleInnerPartitionPackingSolver } from "./SingleInnerPartitionPackingSolver" +import { DecouplingCapsLayoutSolver } from "./DecouplingCapsLayoutSolver" import { stackGraphicsHorizontally } from "graphics-debug" export type PackedPartition = { @@ -19,11 +22,11 @@ export type PackedPartition = { export class PackInnerPartitionsSolver extends BaseSolver { partitions: InputProblem[] packedPartitions: PackedPartition[] = [] - completedSolvers: SingleInnerPartitionPackingSolver[] = [] - activeSolver: SingleInnerPartitionPackingSolver | null = null + completedSolvers: (SingleInnerPartitionPackingSolver | DecouplingCapsLayoutSolver)[] = [] + activeSolver: SingleInnerPartitionPackingSolver | DecouplingCapsLayoutSolver | null = null currentPartitionIndex = 0 - declare activeSubSolver: SingleInnerPartitionPackingSolver | null + declare activeSubSolver: SingleInnerPartitionPackingSolver | DecouplingCapsLayoutSolver | null pinIdToStronglyConnectedPins: Record constructor(params: { @@ -45,10 +48,18 @@ export class PackInnerPartitionsSolver extends BaseSolver { // If no active solver, create one for the current partition if (!this.activeSolver) { const currentPartition = this.partitions[this.currentPartitionIndex]! - this.activeSolver = new SingleInnerPartitionPackingSolver({ - partitionInputProblem: currentPartition, - pinIdToStronglyConnectedPins: this.pinIdToStronglyConnectedPins, - }) + + // Use specialized solver for decoupling capacitor partitions + if (currentPartition.partitionType === "decoupling_caps") { + this.activeSolver = new DecouplingCapsLayoutSolver({ + partitionInputProblem: currentPartition, + }) + } else { + this.activeSolver = new SingleInnerPartitionPackingSolver({ + partitionInputProblem: currentPartition, + pinIdToStronglyConnectedPins: this.pinIdToStronglyConnectedPins, + }) + } this.activeSubSolver = this.activeSolver } From a2f463970d458f68da3672604c1f0c1b4e4c5a08 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:20:13 +0530 Subject: [PATCH 3/4] Fix #15: Enhance DecouplingCapsLayoutSolver with multiple layout strategies --- .../DecouplingCapsLayoutSolver.ts | 178 +++++++++++++++--- 1 file changed, 152 insertions(+), 26 deletions(-) diff --git a/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLayoutSolver.ts b/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLayoutSolver.ts index 719f492..76fd5f8 100644 --- a/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLayoutSolver.ts +++ b/lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLayoutSolver.ts @@ -3,10 +3,11 @@ * Arranges decoupling capacitors in a clean, organized pattern around the main chip. * * This solver addresses issue #15 by: - * 1. Arranging capacitors in a grid pattern for better organization + * 1. Arranging capacitors in a compact grid pattern for better organization * 2. Aligning capacitors consistently (all facing the same direction) * 3. Minimizing trace crossings and messy layouts * 4. Placing capacitors close to the main chip they're decoupling + * 5. Supporting multiple layout strategies (grid, circular, linear) */ import type { GraphicsObject } from "graphics-debug" @@ -17,17 +18,25 @@ import type { PinId, ChipId, PartitionInputProblem, + Chip, } from "../../types/InputProblem" import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem" import { doBasicInputProblemLayout } from "../LayoutPipelineSolver/doBasicInputProblemLayout" +type LayoutStrategy = "grid" | "linear" | "circular" + export class DecouplingCapsLayoutSolver extends BaseSolver { partitionInputProblem: PartitionInputProblem layout: OutputLayout | null = null + layoutStrategy: LayoutStrategy = "grid" - constructor(params: { partitionInputProblem: PartitionInputProblem }) { + constructor(params: { + partitionInputProblem: PartitionInputProblem + layoutStrategy?: LayoutStrategy + }) { super() this.partitionInputProblem = params.partitionInputProblem + this.layoutStrategy = params.layoutStrategy || "grid" } override _step() { @@ -53,13 +62,31 @@ export class DecouplingCapsLayoutSolver extends BaseSolver { } } - // Arrange decoupling capacitors in a clean grid pattern + // Arrange decoupling capacitors based on strategy if (decouplingCaps.length > 0) { - this.arrangeDecouplingCapsInGrid( - decouplingCaps, - chipPlacements, - mainChip, - ) + switch (this.layoutStrategy) { + case "grid": + this.arrangeDecouplingCapsInGrid( + decouplingCaps, + chipPlacements, + mainChip, + ) + break + case "linear": + this.arrangeDecouplingCapsLinear( + decouplingCaps, + chipPlacements, + mainChip, + ) + break + case "circular": + this.arrangeDecouplingCapsCircular( + decouplingCaps, + chipPlacements, + mainChip, + ) + break + } } return { @@ -68,32 +95,47 @@ export class DecouplingCapsLayoutSolver extends BaseSolver { } } + /** + * Arranges capacitors in a compact grid pattern + * Best for: 4+ capacitors + */ private arrangeDecouplingCapsInGrid( - decouplingCaps: any[], + decouplingCaps: Chip[], chipPlacements: Record, - mainChip: any | undefined, + mainChip: Chip | undefined, ) { - const gap = this.partitionInputProblem.decouplingCapsGap ?? 0.5 - const capsPerRow = Math.ceil(Math.sqrt(decouplingCaps.length)) + const gap = this.partitionInputProblem.decouplingCapsGap ?? 0.3 + + // Calculate optimal grid dimensions (prefer square-ish layouts) + const numCaps = decouplingCaps.length + const cols = Math.ceil(Math.sqrt(numCaps)) + const rows = Math.ceil(numCaps / cols) // Calculate starting position based on main chip size const mainChipWidth = mainChip?.size.x ?? 2 const mainChipHeight = mainChip?.size.y ?? 2 + + // Get average capacitor size + const avgCapWidth = decouplingCaps.reduce((sum, cap) => sum + cap.size.x, 0) / numCaps + const avgCapHeight = decouplingCaps.reduce((sum, cap) => sum + cap.size.y, 0) / numCaps + + // Calculate grid dimensions + const gridWidth = cols * avgCapWidth + (cols - 1) * gap + const gridHeight = rows * avgCapHeight + (rows - 1) * gap + + // Position grid to the right of main chip const startX = mainChipWidth / 2 + gap * 2 - const startY = -((capsPerRow - 1) * gap) / 2 + const startY = -gridHeight / 2 - // Arrange capacitors in a grid to the right of the main chip + // Arrange capacitors in grid for (let i = 0; i < decouplingCaps.length; i++) { const cap = decouplingCaps[i]! - const row = Math.floor(i / capsPerRow) - const col = i % capsPerRow + const row = Math.floor(i / cols) + const col = i % cols - // Calculate position in grid - const x = startX + col * (cap.size.x + gap) - const y = startY + row * (cap.size.y + gap) + const x = startX + col * (avgCapWidth + gap) + avgCapWidth / 2 + const y = startY + row * (avgCapHeight + gap) + avgCapHeight / 2 - // Determine rotation based on pin configuration - // All capacitors should face the same direction for consistency const rotation = this.getOptimalCapacitorRotation(cap) chipPlacements[cap.chipId] = { @@ -104,12 +146,79 @@ export class DecouplingCapsLayoutSolver extends BaseSolver { } } - private getOptimalCapacitorRotation(cap: any): number { - // Check available rotations + /** + * Arranges capacitors in a single line + * Best for: 2-3 capacitors + */ + private arrangeDecouplingCapsLinear( + decouplingCaps: Chip[], + chipPlacements: Record, + mainChip: Chip | undefined, + ) { + const gap = this.partitionInputProblem.decouplingCapsGap ?? 0.3 + const mainChipWidth = mainChip?.size.x ?? 2 + + const avgCapHeight = decouplingCaps.reduce((sum, cap) => sum + cap.size.y, 0) / decouplingCaps.length + const totalHeight = decouplingCaps.length * avgCapHeight + (decouplingCaps.length - 1) * gap + + const startX = mainChipWidth / 2 + gap * 2 + const startY = -totalHeight / 2 + + for (let i = 0; i < decouplingCaps.length; i++) { + const cap = decouplingCaps[i]! + const y = startY + i * (avgCapHeight + gap) + avgCapHeight / 2 + + const rotation = this.getOptimalCapacitorRotation(cap) + + chipPlacements[cap.chipId] = { + x: startX, + y, + ccwRotationDegrees: rotation, + } + } + } + + /** + * Arranges capacitors in a circular pattern around the main chip + * Best for: 4-8 capacitors, provides symmetrical layout + */ + private arrangeDecouplingCapsCircular( + decouplingCaps: Chip[], + chipPlacements: Record, + mainChip: Chip | undefined, + ) { + const mainChipWidth = mainChip?.size.x ?? 2 + const mainChipHeight = mainChip?.size.y ?? 2 + const gap = this.partitionInputProblem.decouplingCapsGap ?? 0.5 + + // Calculate radius based on main chip size + const radius = Math.max(mainChipWidth, mainChipHeight) / 2 + gap * 2 + + const angleStep = (2 * Math.PI) / decouplingCaps.length + + for (let i = 0; i < decouplingCaps.length; i++) { + const cap = decouplingCaps[i]! + const angle = i * angleStep + + const x = radius * Math.cos(angle) + const y = radius * Math.sin(angle) + + // Rotate capacitor to face the center + const rotationToCenter = (angle * 180 / Math.PI + 90) % 360 + const rotation = this.getClosestAvailableRotation(cap, rotationToCenter) + + chipPlacements[cap.chipId] = { + x, + y, + ccwRotationDegrees: rotation, + } + } + } + + private getOptimalCapacitorRotation(cap: Chip): number { const availableRotations = cap.availableRotations || [0, 180] - // For decoupling capacitors, we want them oriented consistently - // Prefer 0 or 180 degrees (horizontal orientation) + // For grid/linear layouts, prefer horizontal orientation (0 or 180) if (availableRotations.includes(0)) { return 0 } @@ -117,10 +226,27 @@ export class DecouplingCapsLayoutSolver extends BaseSolver { return 180 } - // Fallback to first available rotation return availableRotations[0] || 0 } + private getClosestAvailableRotation(cap: Chip, targetRotation: number): number { + const availableRotations = cap.availableRotations || [0, 180] + + // Find the closest available rotation to the target + let closest = availableRotations[0] || 0 + let minDiff = Math.abs(targetRotation - closest) + + for (const rotation of availableRotations) { + const diff = Math.abs(targetRotation - rotation) + if (diff < minDiff) { + minDiff = diff + closest = rotation + } + } + + return closest + } + override visualize(): GraphicsObject { if (!this.layout) { const basicLayout = doBasicInputProblemLayout(this.partitionInputProblem) From 826cbfa8c2a2d7b4c5f6e4b4d473ceec79eb9b19 Mon Sep 17 00:00:00 2001 From: Aditya <97450298+1234-ad@users.noreply.github.com> Date: Sun, 15 Feb 2026 22:20:45 +0530 Subject: [PATCH 4/4] Fix #15: Add comprehensive tests for DecouplingCapsLayoutSolver --- tests/DecouplingCapsLayoutSolver.test.ts | 242 +++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 tests/DecouplingCapsLayoutSolver.test.ts diff --git a/tests/DecouplingCapsLayoutSolver.test.ts b/tests/DecouplingCapsLayoutSolver.test.ts new file mode 100644 index 0000000..22474bd --- /dev/null +++ b/tests/DecouplingCapsLayoutSolver.test.ts @@ -0,0 +1,242 @@ +/** + * Tests for DecouplingCapsLayoutSolver + * Verifies that decoupling capacitors are arranged in clean, organized patterns + */ + +import { test, expect } from "bun:test" +import { DecouplingCapsLayoutSolver } from "../lib/solvers/PackInnerPartitionsSolver/DecouplingCapsLayoutSolver" +import type { PartitionInputProblem } from "../lib/types/InputProblem" + +test("DecouplingCapsLayoutSolver - Grid layout with 4 capacitors", () => { + const inputProblem: PartitionInputProblem = { + chipMap: { + main_chip: { + chipId: "main_chip", + pins: ["main_chip.1", "main_chip.2", "main_chip.3", "main_chip.4"], + size: { x: 2, y: 2 }, + availableRotations: [0], + }, + cap1: { + chipId: "cap1", + pins: ["cap1.1", "cap1.2"], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 180], + }, + cap2: { + chipId: "cap2", + pins: ["cap2.1", "cap2.2"], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 180], + }, + cap3: { + chipId: "cap3", + pins: ["cap3.1", "cap3.2"], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 180], + }, + cap4: { + chipId: "cap4", + pins: ["cap4.1", "cap4.2"], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 180], + }, + }, + chipPinMap: {}, + netMap: {}, + netConnMap: {}, + pinStrongConnMap: {}, + chipGap: 0.3, + decouplingCapsGap: 0.3, + partitionType: "decoupling_caps", + } + + const solver = new DecouplingCapsLayoutSolver({ + partitionInputProblem: inputProblem, + layoutStrategy: "grid", + }) + + // Run solver to completion + while (!solver.solved && !solver.failed) { + solver.step() + } + + expect(solver.solved).toBe(true) + expect(solver.failed).toBe(false) + expect(solver.layout).toBeDefined() + + // Verify all chips are placed + const placements = solver.layout!.chipPlacements + expect(Object.keys(placements).length).toBe(5) // 1 main chip + 4 caps + + // Verify main chip is at origin + expect(placements.main_chip?.x).toBe(0) + expect(placements.main_chip?.y).toBe(0) + + // Verify capacitors are placed to the right of main chip + for (const capId of ["cap1", "cap2", "cap3", "cap4"]) { + const placement = placements[capId] + expect(placement).toBeDefined() + expect(placement!.x).toBeGreaterThan(0) // Should be to the right + } +}) + +test("DecouplingCapsLayoutSolver - Linear layout with 3 capacitors", () => { + const inputProblem: PartitionInputProblem = { + chipMap: { + main_chip: { + chipId: "main_chip", + pins: ["main_chip.1", "main_chip.2", "main_chip.3"], + size: { x: 2, y: 2 }, + availableRotations: [0], + }, + cap1: { + chipId: "cap1", + pins: ["cap1.1", "cap1.2"], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 180], + }, + cap2: { + chipId: "cap2", + pins: ["cap2.1", "cap2.2"], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 180], + }, + cap3: { + chipId: "cap3", + pins: ["cap3.1", "cap3.2"], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 180], + }, + }, + chipPinMap: {}, + netMap: {}, + netConnMap: {}, + pinStrongConnMap: {}, + chipGap: 0.3, + decouplingCapsGap: 0.3, + partitionType: "decoupling_caps", + } + + const solver = new DecouplingCapsLayoutSolver({ + partitionInputProblem: inputProblem, + layoutStrategy: "linear", + }) + + while (!solver.solved && !solver.failed) { + solver.step() + } + + expect(solver.solved).toBe(true) + expect(solver.layout).toBeDefined() + + const placements = solver.layout!.chipPlacements + + // Verify capacitors are in a vertical line (same x coordinate) + const cap1X = placements.cap1!.x + expect(placements.cap2!.x).toBeCloseTo(cap1X, 2) + expect(placements.cap3!.x).toBeCloseTo(cap1X, 2) + + // Verify they have different y coordinates (vertical spacing) + const yCoords = [ + placements.cap1!.y, + placements.cap2!.y, + placements.cap3!.y, + ].sort((a, b) => a - b) + + expect(yCoords[1]! - yCoords[0]!).toBeGreaterThan(0.3) // Gap between caps + expect(yCoords[2]! - yCoords[1]!).toBeGreaterThan(0.3) +}) + +test("DecouplingCapsLayoutSolver - Circular layout with 6 capacitors", () => { + const inputProblem: PartitionInputProblem = { + chipMap: { + main_chip: { + chipId: "main_chip", + pins: ["main_chip.1", "main_chip.2", "main_chip.3"], + size: { x: 2, y: 2 }, + availableRotations: [0], + }, + ...Object.fromEntries( + Array.from({ length: 6 }, (_, i) => [ + `cap${i + 1}`, + { + chipId: `cap${i + 1}`, + pins: [`cap${i + 1}.1`, `cap${i + 1}.2`], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 90, 180, 270], + }, + ]), + ), + }, + chipPinMap: {}, + netMap: {}, + netConnMap: {}, + pinStrongConnMap: {}, + chipGap: 0.3, + decouplingCapsGap: 0.5, + partitionType: "decoupling_caps", + } + + const solver = new DecouplingCapsLayoutSolver({ + partitionInputProblem: inputProblem, + layoutStrategy: "circular", + }) + + while (!solver.solved && !solver.failed) { + solver.step() + } + + expect(solver.solved).toBe(true) + expect(solver.layout).toBeDefined() + + const placements = solver.layout!.chipPlacements + + // Verify all capacitors are roughly equidistant from center + const distances = Array.from({ length: 6 }, (_, i) => { + const placement = placements[`cap${i + 1}`]! + return Math.sqrt(placement.x ** 2 + placement.y ** 2) + }) + + const avgDistance = distances.reduce((a, b) => a + b, 0) / distances.length + for (const distance of distances) { + expect(Math.abs(distance - avgDistance)).toBeLessThan(0.1) // All roughly same distance + } +}) + +test("DecouplingCapsLayoutSolver - Handles partition without main chip", () => { + const inputProblem: PartitionInputProblem = { + chipMap: { + cap1: { + chipId: "cap1", + pins: ["cap1.1", "cap1.2"], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 180], + }, + cap2: { + chipId: "cap2", + pins: ["cap2.1", "cap2.2"], + size: { x: 0.5, y: 0.3 }, + availableRotations: [0, 180], + }, + }, + chipPinMap: {}, + netMap: {}, + netConnMap: {}, + pinStrongConnMap: {}, + chipGap: 0.3, + decouplingCapsGap: 0.3, + partitionType: "decoupling_caps", + } + + const solver = new DecouplingCapsLayoutSolver({ + partitionInputProblem: inputProblem, + }) + + while (!solver.solved && !solver.failed) { + solver.step() + } + + expect(solver.solved).toBe(true) + expect(solver.layout).toBeDefined() + expect(Object.keys(solver.layout!.chipPlacements).length).toBe(2) +})