From 1af569ac09b7045035958fe37663e8c5127691c2 Mon Sep 17 00:00:00 2001 From: Vinzz2303 Date: Wed, 10 Jun 2026 02:46:10 +0700 Subject: [PATCH 1/3] Fix layout issue #12 by isolating passives in ChipPartitionsSolver --- .../ChipPartitionsSolver.ts | 65 ++++++++++++-- .../PartitionPackingSolver.ts | 57 ++++++++---- tests/ChipPartitionsSolver.test.ts | 87 +++++++++++++++++- .../PartitionPackingSolver01.test.ts | 90 ++++++++++++++++++- 4 files changed, 268 insertions(+), 31 deletions(-) diff --git a/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts b/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts index 20b445f0..900425ad 100644 --- a/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts +++ b/lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver.ts @@ -3,20 +3,35 @@ * Divides the layout problem into manageable sections for more efficient processing. */ -import { BaseSolver } from "../BaseSolver" +import type { GraphicsObject } from "graphics-debug" +import { stackGraphicsHorizontally } from "graphics-debug" +import { doBasicInputProblemLayout } from "lib/solvers/LayoutPipelineSolver/doBasicInputProblemLayout" +import { visualizeInputProblem } from "lib/solvers/LayoutPipelineSolver/visualizeInputProblem" import type { - InputProblem, + Chip, ChipId, - PinId, + InputProblem, NetId, PartitionInputProblem, + PinId, } from "lib/types/InputProblem" -import type { GraphicsObject } from "graphics-debug" -import { stackGraphicsHorizontally } from "graphics-debug" -import { visualizeInputProblem } from "lib/solvers/LayoutPipelineSolver/visualizeInputProblem" -import { doBasicInputProblemLayout } from "lib/solvers/LayoutPipelineSolver/doBasicInputProblemLayout" +import { BaseSolver } from "../BaseSolver" import type { DecouplingCapGroup } from "../IdentifyDecouplingCapsSolver/IdentifyDecouplingCapsSolver" +const LARGE_CHIP_PIN_COUNT = 4 +const SMALL_SUPPORT_COMPONENT_MAX_PIN_COUNT = 3 +const SMALL_SUPPORT_COMPONENT_PREFIXES = new Set([ + "R", + "C", + "L", + "D", + "LED", + "FB", + "F", + "SJ", + "JP", +]) + export class ChipPartitionsSolver extends BaseSolver { inputProblem: InputProblem partitions: PartitionInputProblem[] = [] @@ -99,8 +114,13 @@ export class ChipPartitionsSolver extends BaseSolver { !decapChipIdSet.has(owner1) && !decapChipIdSet.has(owner2) ) { - adjacencyMap.get(owner1)!.add(owner2) - adjacencyMap.get(owner2)!.add(owner1) + const chip1 = inputProblem.chipMap[owner1]! + const chip2 = inputProblem.chipMap[owner2]! + + if (this.shouldConnectChipsInPartition(chip1, chip2)) { + adjacencyMap.get(owner1)!.add(owner2) + adjacencyMap.get(owner2)!.add(owner1) + } } } @@ -150,6 +170,33 @@ export class ChipPartitionsSolver extends BaseSolver { return null } + private shouldConnectChipsInPartition(chip1: Chip, chip2: Chip): boolean { + const chip1IsLarge = chip1.pins.length >= LARGE_CHIP_PIN_COUNT + const chip2IsLarge = chip2.pins.length >= LARGE_CHIP_PIN_COUNT + const chip1IsSmallSupport = this.isSmallSupportComponent(chip1) + const chip2IsSmallSupport = this.isSmallSupportComponent(chip2) + + return !( + (chip1IsLarge && chip2IsSmallSupport) || + (chip2IsLarge && chip1IsSmallSupport) + ) + } + + private isSmallSupportComponent(chip: Chip): boolean { + if (chip.pins.length > SMALL_SUPPORT_COMPONENT_MAX_PIN_COUNT) { + return false + } + + const prefix = this.getRefdesPrefix(chip.chipId) + return prefix !== null && SMALL_SUPPORT_COMPONENT_PREFIXES.has(prefix) + } + + private getRefdesPrefix(chipId: ChipId): string | null { + const name = chipId.split(/[/.]/).pop() ?? chipId + const match = name.toUpperCase().match(/^[A-Z]+/) + return match?.[0] ?? null + } + /** * Depth-first search to find connected components */ diff --git a/lib/solvers/PartitionPackingSolver/PartitionPackingSolver.ts b/lib/solvers/PartitionPackingSolver/PartitionPackingSolver.ts index 9360c835..612f8410 100644 --- a/lib/solvers/PartitionPackingSolver/PartitionPackingSolver.ts +++ b/lib/solvers/PartitionPackingSolver/PartitionPackingSolver.ts @@ -3,11 +3,11 @@ * Combines all the individually processed partitions into the final schematic layout. */ -import type { GraphicsObject } from "graphics-debug" import { type PackInput, PackSolver2 } from "calculate-packing" -import { BaseSolver } from "../BaseSolver" +import type { GraphicsObject } from "graphics-debug" +import type { InputProblem, NetId, PinId } from "../../types/InputProblem" import type { OutputLayout, Placement } from "../../types/OutputLayout" -import type { InputProblem, PinId, NetId } from "../../types/InputProblem" +import { BaseSolver } from "../BaseSolver" import { visualizeInputProblem } from "../LayoutPipelineSolver/visualizeInputProblem" import type { PackedPartition } from "../PackInnerPartitionsSolver/PackInnerPartitionsSolver" @@ -99,30 +99,48 @@ export class PartitionPackingSolver extends BaseSolver { private buildConnectivityMap(): Map { const pinToNetworkMap = new Map() + const addNetConnection = (connKey: string, connected: boolean) => { + if (!connected) return + const [pinId, netId] = connKey.split("-") + if (pinId && netId) pinToNetworkMap.set(pinId, netId) + } + const addStrongConnection = (connKey: string, connected: boolean) => { + if (!connected) return + const pins = connKey.split("-") + if (pins.length === 2 && pins[0] && pins[1]) { + const existingNet = + pinToNetworkMap.get(pins[0]) || pinToNetworkMap.get(pins[1]) + if (existingNet) { + pinToNetworkMap.set(pins[0], existingNet) + pinToNetworkMap.set(pins[1], existingNet) + } else { + pinToNetworkMap.set(pins[0], connKey) + pinToNetworkMap.set(pins[1], connKey) + } + } + } + + for (const [connKey, connected] of Object.entries( + this.inputProblem.netConnMap, + )) { + addNetConnection(connKey, connected) + } + for (const [connKey, connected] of Object.entries( + this.inputProblem.pinStrongConnMap, + )) { + addStrongConnection(connKey, connected) + } + for (const packedPartition of this.packedPartitions) { for (const [connKey, connected] of Object.entries( packedPartition.inputProblem.netConnMap, )) { - if (!connected) continue - const [pinId, netId] = connKey.split("-") - if (pinId && netId) pinToNetworkMap.set(pinId, netId) + addNetConnection(connKey, connected) } for (const [connKey, connected] of Object.entries( packedPartition.inputProblem.pinStrongConnMap, )) { - if (!connected) continue - const pins = connKey.split("-") - if (pins.length === 2 && pins[0] && pins[1]) { - const existingNet = - pinToNetworkMap.get(pins[0]) || pinToNetworkMap.get(pins[1]) - if (existingNet) { - pinToNetworkMap.set(pins[0], existingNet) - pinToNetworkMap.set(pins[1], existingNet) - } else { - pinToNetworkMap.set(pins[0], connKey) - pinToNetworkMap.set(pins[1], connKey) - } - } + addStrongConnection(connKey, connected) } } return pinToNetworkMap @@ -279,6 +297,7 @@ export class PartitionPackingSolver extends BaseSolver { for (const packedComponent of packedComponents) { const partitionIndex = parseInt( packedComponent.componentId.replace("partition_", ""), + 10, ) const group = partitionGroups.find( (g) => g.partitionIndex === partitionIndex, diff --git a/tests/ChipPartitionsSolver.test.ts b/tests/ChipPartitionsSolver.test.ts index d8eb08c6..18d3b923 100644 --- a/tests/ChipPartitionsSolver.test.ts +++ b/tests/ChipPartitionsSolver.test.ts @@ -1,4 +1,4 @@ -import { test, expect } from "bun:test" +import { expect, test } from "bun:test" import { ChipPartitionsSolver } from "../lib/solvers/ChipPartitionsSolver/ChipPartitionsSolver" import type { InputProblem } from "../lib/types/InputProblem" @@ -162,6 +162,91 @@ test("ChipPartitionsSolver handles complex connected graph", () => { expect(solver.partitions[0]!.chipMap).toEqual(inputProblem.chipMap) }) +test("ChipPartitionsSolver separates small passive support components from large chips", () => { + const inputProblem: InputProblem = { + chipMap: { + U1: { + chipId: "U1", + pins: ["U1.1", "U1.2", "U1.3", "U1.4"], + size: { x: 4, y: 4 }, + }, + R1: { + chipId: "R1", + pins: ["R1.1", "R1.2"], + size: { x: 1, y: 0.4 }, + }, + }, + chipPinMap: { + "U1.1": { pinId: "U1.1", offset: { x: -2, y: 1 }, side: "x-" }, + "U1.2": { pinId: "U1.2", offset: { x: -2, y: 0 }, side: "x-" }, + "U1.3": { pinId: "U1.3", offset: { x: 2, y: 0 }, side: "x+" }, + "U1.4": { pinId: "U1.4", offset: { x: 2, y: 1 }, side: "x+" }, + "R1.1": { pinId: "R1.1", offset: { x: -0.5, y: 0 }, side: "x-" }, + "R1.2": { pinId: "R1.2", offset: { x: 0.5, y: 0 }, side: "x+" }, + }, + netMap: {}, + pinStrongConnMap: { + "U1.1-R1.1": true, + "R1.1-U1.1": true, + }, + netConnMap: {}, + chipGap: 0.2, + partitionGap: 2, + } + + const solver = new ChipPartitionsSolver({ inputProblem }) + solver.solve() + + const partitions = solver.partitions.map((partition) => + Object.keys(partition.chipMap), + ) + expect(partitions).toHaveLength(2) + expect(partitions.flat().sort()).toEqual(["R1", "U1"]) +}) + +test("ChipPartitionsSolver keeps small active components attached to large chips", () => { + const inputProblem: InputProblem = { + chipMap: { + U1: { + chipId: "U1", + pins: ["U1.1", "U1.2", "U1.3", "U1.4"], + size: { x: 4, y: 4 }, + }, + Q1: { + chipId: "Q1", + pins: ["Q1.1", "Q1.2", "Q1.3"], + size: { x: 1, y: 1 }, + }, + }, + chipPinMap: { + "U1.1": { pinId: "U1.1", offset: { x: -2, y: 1 }, side: "x-" }, + "U1.2": { pinId: "U1.2", offset: { x: -2, y: 0 }, side: "x-" }, + "U1.3": { pinId: "U1.3", offset: { x: 2, y: 0 }, side: "x+" }, + "U1.4": { pinId: "U1.4", offset: { x: 2, y: 1 }, side: "x+" }, + "Q1.1": { pinId: "Q1.1", offset: { x: -0.5, y: 0 }, side: "x-" }, + "Q1.2": { pinId: "Q1.2", offset: { x: 0.5, y: 0 }, side: "x+" }, + "Q1.3": { pinId: "Q1.3", offset: { x: 0, y: 0.5 }, side: "y+" }, + }, + netMap: {}, + pinStrongConnMap: { + "U1.1-Q1.1": true, + "Q1.1-U1.1": true, + }, + netConnMap: {}, + chipGap: 0.2, + partitionGap: 2, + } + + const solver = new ChipPartitionsSolver({ inputProblem }) + solver.solve() + + expect(solver.partitions).toHaveLength(1) + expect(Object.keys(solver.partitions[0]!.chipMap).sort()).toEqual([ + "Q1", + "U1", + ]) +}) + test("ChipPartitionsSolver visualization contains partition components", () => { const inputProblem: InputProblem = { chipMap: { diff --git a/tests/PartitionPackingSolver/PartitionPackingSolver01.test.ts b/tests/PartitionPackingSolver/PartitionPackingSolver01.test.ts index d2e204e5..62486132 100644 --- a/tests/PartitionPackingSolver/PartitionPackingSolver01.test.ts +++ b/tests/PartitionPackingSolver/PartitionPackingSolver01.test.ts @@ -1,9 +1,10 @@ -import { test, expect } from "bun:test" +import { expect, test } from "bun:test" +import type { PackedPartition } from "../../lib/solvers/PackInnerPartitionsSolver/PackInnerPartitionsSolver" import { PartitionPackingSolver, type PartitionPackingSolverInput, } from "../../lib/solvers/PartitionPackingSolver/PartitionPackingSolver" -import type { PackedPartition } from "../../lib/solvers/PackInnerPartitionsSolver/PackInnerPartitionsSolver" +import type { InputProblem } from "../../lib/types/InputProblem" test("PartitionPackingSolver works with single packed partition", () => { // Create a simple packed partition @@ -135,3 +136,88 @@ test("PartitionPackingSolver works with empty partitions", () => { expect(solver.finalLayout).toBeDefined() expect(Object.keys(solver.finalLayout!.chipPlacements).length).toBe(0) }) + +test("PartitionPackingSolver keeps original cross-partition strong connections", () => { + const inputProblem: InputProblem = { + chipMap: { + U1: { + chipId: "U1", + pins: ["U1.1", "U1.2", "U1.3", "U1.4"], + size: { x: 4, y: 4 }, + }, + R1: { + chipId: "R1", + pins: ["R1.1", "R1.2"], + size: { x: 1, y: 0.4 }, + }, + }, + chipPinMap: { + "U1.1": { pinId: "U1.1", offset: { x: -2, y: 1 }, side: "x-" }, + "U1.2": { pinId: "U1.2", offset: { x: -2, y: 0 }, side: "x-" }, + "U1.3": { pinId: "U1.3", offset: { x: 2, y: 0 }, side: "x+" }, + "U1.4": { pinId: "U1.4", offset: { x: 2, y: 1 }, side: "x+" }, + "R1.1": { pinId: "R1.1", offset: { x: -0.5, y: 0 }, side: "x-" }, + "R1.2": { pinId: "R1.2", offset: { x: 0.5, y: 0 }, side: "x+" }, + }, + netMap: {}, + pinStrongConnMap: { + "U1.1-R1.1": true, + "R1.1-U1.1": true, + }, + netConnMap: {}, + chipGap: 0.2, + partitionGap: 2, + } + const packedPartitions: PackedPartition[] = [ + { + inputProblem: { + ...inputProblem, + chipMap: { U1: inputProblem.chipMap.U1! }, + chipPinMap: { + "U1.1": inputProblem.chipPinMap["U1.1"]!, + "U1.2": inputProblem.chipPinMap["U1.2"]!, + "U1.3": inputProblem.chipPinMap["U1.3"]!, + "U1.4": inputProblem.chipPinMap["U1.4"]!, + }, + pinStrongConnMap: {}, + netConnMap: {}, + }, + layout: { + chipPlacements: { + U1: { x: 0, y: 0, ccwRotationDegrees: 0 }, + }, + groupPlacements: {}, + }, + }, + { + inputProblem: { + ...inputProblem, + chipMap: { R1: inputProblem.chipMap.R1! }, + chipPinMap: { + "R1.1": inputProblem.chipPinMap["R1.1"]!, + "R1.2": inputProblem.chipPinMap["R1.2"]!, + }, + pinStrongConnMap: {}, + netConnMap: {}, + }, + layout: { + chipPlacements: { + R1: { x: 0, y: 0, ccwRotationDegrees: 0 }, + }, + groupPlacements: {}, + }, + }, + ] + + const solver = new PartitionPackingSolver({ + packedPartitions, + inputProblem, + }) + const connectivityMap = (solver as any).buildConnectivityMap() as Map< + string, + string + > + + expect(connectivityMap.get("U1.1")).toBe("U1.1-R1.1") + expect(connectivityMap.get("R1.1")).toBe("U1.1-R1.1") +}) From ce4b43a96dd436ffcc21dd2f4f231de7803da6b0 Mon Sep 17 00:00:00 2001 From: Vinzz2303 Date: Wed, 10 Jun 2026 16:27:48 +0700 Subject: [PATCH 2/3] fix: add react dev dependencies to fix jsx runtime error and update layout snapshot --- package.json | 4 ++ .../layout-pipeline-example02.snap.svg | 4 +- .../solver-snapshots-chip-partitions.snap.svg | 44 +++++++++++++++++++ ...apshots-layout-pipeline-example02.snap.svg | 44 +++++++++++++++++++ ...r-snapshots-layout-pipeline-small.snap.svg | 44 +++++++++++++++++++ 5 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 tests/__snapshots__/solver-snapshots-chip-partitions.snap.svg create mode 100644 tests/__snapshots__/solver-snapshots-layout-pipeline-example02.snap.svg create mode 100644 tests/__snapshots__/solver-snapshots-layout-pipeline-small.snap.svg diff --git a/package.json b/package.json index 5bc5d80f..d92076eb 100644 --- a/package.json +++ b/package.json @@ -34,5 +34,9 @@ }, "peerDependencies": { "typescript": "^5" + }, + "dependencies": { + "react": "^19.2.7", + "react-dom": "^19.2.7" } } diff --git a/tests/__snapshots__/layout-pipeline-example02.snap.svg b/tests/__snapshots__/layout-pipeline-example02.snap.svg index b6807ddf..bd4c7433 100644 --- a/tests/__snapshots__/layout-pipeline-example02.snap.svg +++ b/tests/__snapshots__/layout-pipeline-example02.snap.svg @@ -1,4 +1,4 @@ -C6U1C5C2C1 \ No newline at end of file diff --git a/tests/__snapshots__/solver-snapshots-layout-pipeline-example02.snap.svg b/tests/__snapshots__/solver-snapshots-layout-pipeline-example02.snap.svg new file mode 100644 index 00000000..0de17789 --- /dev/null +++ b/tests/__snapshots__/solver-snapshots-layout-pipeline-example02.snap.svg @@ -0,0 +1,44 @@ +C6C1C2C5U1 \ No newline at end of file diff --git a/tests/__snapshots__/solver-snapshots-layout-pipeline-small.snap.svg b/tests/__snapshots__/solver-snapshots-layout-pipeline-small.snap.svg new file mode 100644 index 00000000..6a82bf9e --- /dev/null +++ b/tests/__snapshots__/solver-snapshots-layout-pipeline-small.snap.svg @@ -0,0 +1,44 @@ +U1C1R1 \ No newline at end of file From 72de050b96ff268a26f657dfe877bd8bfcfb1b0e Mon Sep 17 00:00:00 2001 From: Vinzz2303 Date: Tue, 30 Jun 2026 17:35:45 +0700 Subject: [PATCH 3/3] chore: update svg snapshots after merge --- ...solver-snapshots-layout-pipeline-example02.snap.svg | 4 ++-- ...-pipeline-mixed-chip-power-ground-passives.snap.svg | 10 +++++----- .../__snapshots__/repro-backwards-net-labels.snap.svg | 4 ++-- .../repro-bq24074-battery-charger.snap.svg | 4 ++-- .../repro-bq24074-bottom-resistors.snap.svg | 4 ++-- .../repro-bq24074-right-resistors.snap.svg | 4 ++-- .../repro-e2e-pack-and-schematic.snap.svg | 4 ++-- .../repro-rp2040-decoupling-capacitors.snap.svg | 4 ++-- .../__snapshots__/rp2040-zero-crystal-group4.snap.svg | 4 ++-- tests/repros/__snapshots__/rp2040-zero-group1.snap.svg | 4 ++-- tests/repros/__snapshots__/rp2040-zero-group3.snap.svg | 4 ++-- 11 files changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/__snapshots__/solver-snapshots-layout-pipeline-example02.snap.svg b/tests/__snapshots__/solver-snapshots-layout-pipeline-example02.snap.svg index 40347b00..52d23fef 100644 --- a/tests/__snapshots__/solver-snapshots-layout-pipeline-example02.snap.svg +++ b/tests/__snapshots__/solver-snapshots-layout-pipeline-example02.snap.svg @@ -1,4 +1,4 @@ -C6C1C2C5U1