diff --git a/lib/solvers/GroundedLoadPairSolver/GroundedLoadPairSolver.ts b/lib/solvers/GroundedLoadPairSolver/GroundedLoadPairSolver.ts index c0cf87b8..198da02a 100644 --- a/lib/solvers/GroundedLoadPairSolver/GroundedLoadPairSolver.ts +++ b/lib/solvers/GroundedLoadPairSolver/GroundedLoadPairSolver.ts @@ -9,7 +9,7 @@ import { } from "./getGroundedLoadPairs" import { layoutGroundedLoadPair } from "./layoutGroundedLoadPair" import { offsetChipAnchoredGroundedLoadConnections } from "../../utils/offsetCollinearConnections" -import { alignStandaloneGroundedLoadPairs } from "./alignStandaloneGroundedLoadPairs" +import { alignGroundedLoadPairRows } from "./alignGroundedLoadPairRows" export class GroundedLoadPairSolver extends BaseSolver { outputLayout: OutputLayout | null = null @@ -42,7 +42,7 @@ export class GroundedLoadPairSolver extends BaseSolver { inputProblem: this.params.inputProblem, }) } - alignStandaloneGroundedLoadPairs({ + alignGroundedLoadPairRows({ groundedLoadPairs: this.groundedLoadPairs, chipPlacements, inputProblem: this.params.inputProblem, diff --git a/lib/solvers/GroundedLoadPairSolver/alignGroundedLoadPairRows.ts b/lib/solvers/GroundedLoadPairSolver/alignGroundedLoadPairRows.ts new file mode 100644 index 00000000..0f5f8dc9 --- /dev/null +++ b/lib/solvers/GroundedLoadPairSolver/alignGroundedLoadPairRows.ts @@ -0,0 +1,157 @@ +import type { ChipId, InputProblem, NetId } from "../../types/InputProblem" +import type { Placement } from "../../types/OutputLayout" +import { rotatePinOffset } from "../../utils/rotatePinOffset" +import { getPlacementBounds } from "../AlignTestPointsSolver/placementsOverlap" +import type { GroundedLoadPair } from "./getGroundedLoadPairs" + +type GroundedLoadPairBounds = { + minX: number + maxX: number +} + +type GroundedLoadRowContext = { + chipPlacements: Record + inputProblem: InputProblem +} + +const MINIMUM_PAIRS_PER_ROW = 2 + +const getPairBounds = ( + groundedLoadPair: GroundedLoadPair, + context: GroundedLoadRowContext, +): GroundedLoadPairBounds => { + const { chipPlacements } = context + const upperPlacement = chipPlacements[groundedLoadPair.upperChip.chipId]! + const lowerPlacement = chipPlacements[groundedLoadPair.lowerChip.chipId]! + const upperBounds = getPlacementBounds({ + placement: upperPlacement, + size: groundedLoadPair.upperChip.size, + }) + const lowerBounds = getPlacementBounds({ + placement: lowerPlacement, + size: groundedLoadPair.lowerChip.size, + }) + + return { + minX: Math.min(upperBounds.minX, lowerBounds.minX), + maxX: Math.max(upperBounds.maxX, lowerBounds.maxX), + } +} + +const getGroundPinY = ( + groundedLoadPair: GroundedLoadPair, + context: GroundedLoadRowContext, +): number => { + const { chipPlacements, inputProblem } = context + const placement = chipPlacements[groundedLoadPair.lowerChip.chipId]! + const groundPin = inputProblem.chipPinMap[groundedLoadPair.groundPinId]! + const groundPinOffset = rotatePinOffset( + groundPin.offset, + placement.ccwRotationDegrees, + ) + return placement.y + groundPinOffset.y +} + +const translateGroundedLoadPair = ( + { + groundedLoadPair, + dx, + dy, + }: { + groundedLoadPair: GroundedLoadPair + dx: number + dy: number + }, + context: GroundedLoadRowContext, +): void => { + const { chipPlacements } = context + for (const chipId of [ + groundedLoadPair.upperChip.chipId, + groundedLoadPair.lowerChip.chipId, + ]) { + const placement = chipPlacements[chipId]! + placement.x += dx + placement.y += dy + } +} + +const getLeftPairEdge = ( + groundedLoadPair: GroundedLoadPair, + context: GroundedLoadRowContext, +): number => { + return getPairBounds(groundedLoadPair, context).minX +} + +const alignGroundedLoadPairRow = ( + groundedLoadPairs: GroundedLoadPair[], + context: GroundedLoadRowContext, +): void => { + const { inputProblem } = context + const leftToRightPairs = [...groundedLoadPairs].sort( + (pairA, pairB) => + getLeftPairEdge(pairA, context) - getLeftPairEdge(pairB, context), + ) + const initialGroundPinYs = leftToRightPairs.map((groundedLoadPair) => + getGroundPinY(groundedLoadPair, context), + ) + + const targetGroundPinY = Math.min(...initialGroundPinYs) + for (const groundedLoadPair of leftToRightPairs) { + const groundPinY = getGroundPinY(groundedLoadPair, context) + translateGroundedLoadPair( + { + groundedLoadPair, + dx: 0, + dy: targetGroundPinY - groundPinY, + }, + context, + ) + } + + let previousPairMaxX: number | undefined + for (const groundedLoadPair of leftToRightPairs) { + const pairBounds = getPairBounds(groundedLoadPair, context) + if (previousPairMaxX === undefined) { + previousPairMaxX = pairBounds.maxX + continue + } + const dx = previousPairMaxX + inputProblem.partitionGap - pairBounds.minX + translateGroundedLoadPair( + { + groundedLoadPair, + dx, + dy: 0, + }, + context, + ) + previousPairMaxX = pairBounds.maxX + dx + } +} + +export const alignGroundedLoadPairRows = ({ + groundedLoadPairs, + chipPlacements, + inputProblem, +}: { + groundedLoadPairs: GroundedLoadPair[] + chipPlacements: Record + inputProblem: InputProblem +}): void => { + const context = { chipPlacements, inputProblem } + const pairsByGroundNetId = new Map() + for (const groundedLoadPair of groundedLoadPairs) { + if (groundedLoadPair.mainChipId) continue + if (!chipPlacements[groundedLoadPair.upperChip.chipId]) continue + if (!chipPlacements[groundedLoadPair.lowerChip.chipId]) continue + if (!inputProblem.chipPinMap[groundedLoadPair.groundPinId]) continue + const groundNetId = groundedLoadPair.groundNetId + const rowPairs = pairsByGroundNetId.get(groundNetId) ?? [] + rowPairs.push(groundedLoadPair) + pairsByGroundNetId.set(groundNetId, rowPairs) + } + + for (const rowPairs of pairsByGroundNetId.values()) { + if (rowPairs.length < MINIMUM_PAIRS_PER_ROW) continue + alignGroundedLoadPairRow(rowPairs, context) + } +} diff --git a/lib/solvers/GroundedLoadPairSolver/alignStandaloneGroundedLoadPairs.ts b/lib/solvers/GroundedLoadPairSolver/alignStandaloneGroundedLoadPairs.ts deleted file mode 100644 index 3c5ac067..00000000 --- a/lib/solvers/GroundedLoadPairSolver/alignStandaloneGroundedLoadPairs.ts +++ /dev/null @@ -1,110 +0,0 @@ -import { getBoundFromCenteredRect } from "@tscircuit/math-utils" -import type { ChipId, InputProblem } from "../../types/InputProblem" -import type { Placement } from "../../types/OutputLayout" -import { getRotatedSize } from "../../utils/rotatePinOffset" -import type { GroundedLoadPair } from "./getGroundedLoadPairs" - -const CHIPS_PER_PAIR = 2 - -const getChipBounds = ({ - chipId, - chipPlacements, - inputProblem, -}: { - chipId: ChipId - chipPlacements: Record - inputProblem: InputProblem -}) => { - const chip = inputProblem.chipMap[chipId] - const placement = chipPlacements[chipId] - if (!chip || !placement) return null - const size = getRotatedSize(chip.size, placement.ccwRotationDegrees) - return getBoundFromCenteredRect({ - center: placement, - width: size.x, - height: size.y, - }) -} - -const getPairHorizontalBounds = ({ - groundedLoadPair, - chipPlacements, - inputProblem, -}: { - groundedLoadPair: GroundedLoadPair - chipPlacements: Record - inputProblem: InputProblem -}) => { - const upperBounds = getChipBounds({ - chipId: groundedLoadPair.upperChip.chipId, - chipPlacements, - inputProblem, - }) - const lowerBounds = getChipBounds({ - chipId: groundedLoadPair.lowerChip.chipId, - chipPlacements, - inputProblem, - }) - if (!upperBounds || !lowerBounds) return null - return { - minX: Math.min(upperBounds.minX, lowerBounds.minX), - maxX: Math.max(upperBounds.maxX, lowerBounds.maxX), - } -} - -export const alignStandaloneGroundedLoadPairs = ({ - groundedLoadPairs, - chipPlacements, - inputProblem, -}: { - groundedLoadPairs: GroundedLoadPair[] - chipPlacements: Record - inputProblem: InputProblem -}): void => { - const standalonePairs = groundedLoadPairs.filter( - (groundedLoadPair) => groundedLoadPair.isStandaloneSignalChain, - ) - const firstPair = standalonePairs[0] - if (!firstPair || standalonePairs.length < 2) return - if ( - standalonePairs.length * CHIPS_PER_PAIR !== - Object.keys(inputProblem.chipMap).length - ) { - return - } - - const firstUpperPlacement = chipPlacements[firstPair.upperChip.chipId] - if (!firstUpperPlacement) return - let previousRightEdge = getPairHorizontalBounds({ - groundedLoadPair: firstPair, - chipPlacements, - inputProblem, - })?.maxX - if (previousRightEdge === undefined) return - - for (const groundedLoadPair of standalonePairs.slice(1)) { - const upperPlacement = chipPlacements[groundedLoadPair.upperChip.chipId] - const lowerPlacement = chipPlacements[groundedLoadPair.lowerChip.chipId] - const pairBounds = getPairHorizontalBounds({ - groundedLoadPair, - chipPlacements, - inputProblem, - }) - if (!upperPlacement || !lowerPlacement || !pairBounds) continue - - const dx = previousRightEdge + inputProblem.partitionGap - pairBounds.minX - const dy = firstUpperPlacement.y - upperPlacement.y - upperPlacement.x += dx - upperPlacement.y += dy - lowerPlacement.x += dx - lowerPlacement.y += dy - - const alignedPairBounds = getPairHorizontalBounds({ - groundedLoadPair, - chipPlacements, - inputProblem, - }) - if (!alignedPairBounds) continue - previousRightEdge = alignedPairBounds.maxX - } -} diff --git a/lib/solvers/GroundedLoadPairSolver/getGroundedLoadPairs.ts b/lib/solvers/GroundedLoadPairSolver/getGroundedLoadPairs.ts index 0c2a6412..ad0d6edf 100644 --- a/lib/solvers/GroundedLoadPairSolver/getGroundedLoadPairs.ts +++ b/lib/solvers/GroundedLoadPairSolver/getGroundedLoadPairs.ts @@ -18,6 +18,7 @@ export type GroundedLoadPair = { upperInnerPinId: PinId lowerInnerPinId: PinId groundPinId: PinId + groundNetId: NetId isStandaloneSignalChain?: boolean } @@ -68,16 +69,25 @@ const getPinIdsForNet = ({ return pinIds } -const pinConnectsToGround = ({ +const getGroundConnection = ({ + lowerChip, + lowerInnerPinId, inputProblem, - pinId, }: { + lowerChip: Chip + lowerInnerPinId: PinId inputProblem: InputProblem - pinId: PinId -}) => - getNetIdsForPin({ inputProblem, pinId }).some( - (netId) => inputProblem.netMap[netId]?.isGround === true, - ) +}): Pick | null => { + for (const groundPinId of lowerChip.pins) { + if (groundPinId === lowerInnerPinId) continue + const groundNetId = getNetIdsForPin({ + inputProblem, + pinId: groundPinId, + }).find((netId) => inputProblem.netMap[netId]?.isGround === true) + if (groundNetId) return { groundPinId, groundNetId } + } + return null +} const pinConnectsToPositiveVoltage = ({ inputProblem, @@ -90,6 +100,34 @@ const pinConnectsToPositiveVoltage = ({ (netId) => inputProblem.netMap[netId]?.isPositiveVoltageSource === true, ) +const getDirectlyConnectedGroundedLowerChip = ( + { upperInnerPinId }: { upperInnerPinId: PinId }, + ctx: ConnectivityContext, +): Pick< + GroundedLoadPair, + "lowerChip" | "lowerInnerPinId" | "groundPinId" | "groundNetId" +> | null => { + const { inputProblem, pinOwnerMap, connectedPinsByPinId, pairedChipIds } = ctx + for (const lowerInnerPinId of getStronglyConnectedPinIds({ + connectedPinsByPinId, + pinId: upperInnerPinId, + })) { + const lowerChip = pinOwnerMap.get(lowerInnerPinId) + if (!lowerChip) continue + if (lowerChip.pins.length !== TWO_PIN_COMPONENT_PIN_COUNT) continue + if (lowerChip.fixedPosition) continue + if (pairedChipIds.has(lowerChip.chipId)) continue + const groundConnection = getGroundConnection({ + lowerChip, + lowerInnerPinId, + inputProblem, + }) + if (!groundConnection) continue + return { lowerChip, lowerInnerPinId, ...groundConnection } + } + return null +} + const getChipConnectedPair = ( upperChip: Chip, ctx: ConnectivityContext, @@ -136,12 +174,12 @@ const getChipConnectedPair = ( ) if (!lowerInnerPinId) continue - const groundPinId = lowerChip.pins.find( - (pinId) => - pinId !== lowerInnerPinId && - pinConnectsToGround({ inputProblem, pinId }), - ) - if (!groundPinId) continue + const groundConnection = getGroundConnection({ + lowerChip, + lowerInnerPinId, + inputProblem, + }) + if (!groundConnection) continue return { upperChip, @@ -151,7 +189,7 @@ const getChipConnectedPair = ( upperOuterPinId, upperInnerPinId, lowerInnerPinId, - groundPinId, + ...groundConnection, } } return null @@ -173,6 +211,19 @@ const getRailConnectedPair = ( ) if (!upperInnerPinId) return null + const directlyConnectedLowerChip = getDirectlyConnectedGroundedLowerChip( + { upperInnerPinId }, + ctx, + ) + if (directlyConnectedLowerChip) { + return { + upperChip, + upperOuterPinId, + upperInnerPinId, + ...directlyConnectedLowerChip, + } + } + const internalNetId = getNetIdsForPin({ inputProblem, pinId: upperInnerPinId, @@ -182,7 +233,10 @@ const getRailConnectedPair = ( }) if (!internalNetId) return null - const internalPinIds = getPinIdsForNet({ inputProblem, netId: internalNetId }) + const internalPinIds = getPinIdsForNet({ + inputProblem, + netId: internalNetId, + }) // A private two-pin net prevents grouping a branched circuit as one load chain. if (internalPinIds.length !== TWO_PIN_COMPONENT_PIN_COUNT) return null const lowerInnerPinId = internalPinIds.find( @@ -196,11 +250,12 @@ const getRailConnectedPair = ( if (lowerChip.fixedPosition) return null if (pairedChipIds.has(lowerChip.chipId)) return null - const groundPinId = lowerChip.pins.find( - (pinId) => - pinId !== lowerInnerPinId && pinConnectsToGround({ inputProblem, pinId }), - ) - if (!groundPinId) return null + const groundConnection = getGroundConnection({ + lowerChip, + lowerInnerPinId, + inputProblem, + }) + if (!groundConnection) return null return { upperChip, @@ -208,7 +263,7 @@ const getRailConnectedPair = ( upperOuterPinId, upperInnerPinId, lowerInnerPinId, - groundPinId, + ...groundConnection, } } @@ -216,7 +271,7 @@ const getSignalConnectedPair = ( upperChip: Chip, ctx: ConnectivityContext, ): GroundedLoadPair | null => { - const { inputProblem, pinOwnerMap, connectedPinsByPinId, pairedChipIds } = ctx + const { inputProblem } = ctx for (const upperOuterPinId of upperChip.pins) { const hasStandaloneSignalNet = getNetIdsForPin({ inputProblem, @@ -233,34 +288,17 @@ const getSignalConnectedPair = ( ) if (!upperInnerPinId) continue - const lowerInnerPinId = getStronglyConnectedPinIds({ - connectedPinsByPinId, - pinId: upperInnerPinId, - }).find((pinId) => { - const chip = pinOwnerMap.get(pinId) - if (!chip) return false - if (chip.pins.length !== TWO_PIN_COMPONENT_PIN_COUNT) return false - if (chip.fixedPosition) return false - return !pairedChipIds.has(chip.chipId) - }) - if (!lowerInnerPinId) continue - - const lowerChip = pinOwnerMap.get(lowerInnerPinId) - if (!lowerChip) continue - const groundPinId = lowerChip.pins.find( - (pinId) => - pinId !== lowerInnerPinId && - pinConnectsToGround({ inputProblem, pinId }), + const directlyConnectedLowerChip = getDirectlyConnectedGroundedLowerChip( + { upperInnerPinId }, + ctx, ) - if (!groundPinId) continue + if (!directlyConnectedLowerChip) continue return { upperChip, - lowerChip, upperOuterPinId, upperInnerPinId, - lowerInnerPinId, - groundPinId, + ...directlyConnectedLowerChip, isStandaloneSignalChain: true, } } diff --git a/tests/repros/__snapshots__/repro-power-section.snap.svg b/tests/repros/__snapshots__/repro-power-section.snap.svg index 52387684..3bed3396 100644 --- a/tests/repros/__snapshots__/repro-power-section.snap.svg +++ b/tests/repros/__snapshots__/repro-power-section.snap.svg @@ -1,4 +1,4 @@ -J1D1U2U1U3C1C2C3C4LED1R1 + ]]> \ No newline at end of file diff --git a/tests/repros/__snapshots__/repro27-schematic-section-rp2040-boot-reset.snap.svg b/tests/repros/__snapshots__/repro27-schematic-section-rp2040-boot-reset.snap.svg index 0f5c8eb6..610325f8 100644 --- a/tests/repros/__snapshots__/repro27-schematic-section-rp2040-boot-reset.snap.svg +++ b/tests/repros/__snapshots__/repro27-schematic-section-rp2040-boot-reset.snap.svg @@ -1,4 +1,4 @@ -R3R2SW3SW2