Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 14 additions & 2 deletions lib/solvers/MspConnectionPairSolver/MspConnectionPairSolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCen
import { getConnectivityMapsFromInputProblem } from "./getConnectivityMapFromInputProblem"
import { getOrthogonalMinimumSpanningTree } from "./getMspConnectionPairsFromPins"
import { isLabeledPeripheralConnection } from "./isLabeledPeripheralConnection"
import { replaceIntraComponentMspEdges } from "./replaceIntraComponentMspEdges"

export type MspConnectionPairId = string
export const DEFAULT_MAX_MSP_PAIR_DISTANCE = 1
Expand Down Expand Up @@ -173,8 +174,11 @@ export class MspConnectionPairSolver extends BaseSolver {
PinId,
InputPin & { chipId: string }
>
const msp = getOrthogonalMinimumSpanningTree(
directlyConnectedPins.map((p) => this.pinMap[p]!).filter(Boolean),
const connectedPins = directlyConnectedPins
.map((pinId) => this.pinMap[pinId]!)
.filter(Boolean)
const minimumSpanningTree = getOrthogonalMinimumSpanningTree(
connectedPins,
{
maxDistance: this.maxMspPairDistance,
forbidEdge: (a, b) =>
Expand All @@ -192,6 +196,14 @@ export class MspConnectionPairSolver extends BaseSolver {
}),
},
)
const msp = replaceIntraComponentMspEdges({
edges: minimumSpanningTree,
pins: connectedPins,
inputProblem: this.inputProblem,
chipMap: this.chipMap,
pinIdMap,
maxDistance: this.maxMspPairDistance,
})

for (const [pin1, pin2] of msp) {
const p1Obj = this.pinMap[pin1!]!
Expand Down
211 changes: 211 additions & 0 deletions lib/solvers/MspConnectionPairSolver/replaceIntraComponentMspEdges.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
import type {
InputChip,
InputPin,
InputProblem,
PinId,
} from "lib/types/InputProblem"
import { arePinsInDifferentSchematicSections } from "lib/utils/arePinsInDifferentSchematicSections"
import { doesPairCrossRestrictedCenterLines } from "./doesPairCrossRestrictedCenterLines"

type PinWithChip = InputPin & { chipId: string }
type MspEdge = [PinId, PinId]
const MIN_HUB_DEGREE_WITH_LOCAL_EDGE = 3
const CHIP_EDGE_TOLERANCE = 1e-6

interface ReplacementCandidate {
edge: MspEdge
distance: number
lowerPinId: PinId
upperPinId: PinId
}

const getConnectedPinIds = ({
startPinId,
edges,
}: {
startPinId: PinId
edges: MspEdge[]
}) => {
const adjacencyMap = new Map<PinId, PinId[]>()
for (const [firstPinId, secondPinId] of edges) {
adjacencyMap.set(firstPinId, [
...(adjacencyMap.get(firstPinId) ?? []),
secondPinId,
])
adjacencyMap.set(secondPinId, [
...(adjacencyMap.get(secondPinId) ?? []),
firstPinId,
])
}

const connectedPinIds = new Set<PinId>([startPinId])
const queue = [startPinId]
for (let queueIndex = 0; queueIndex < queue.length; queueIndex++) {
const pinId = queue[queueIndex]!
for (const adjacentPinId of adjacencyMap.get(pinId) ?? []) {
if (connectedPinIds.has(adjacentPinId)) continue
connectedPinIds.add(adjacentPinId)
queue.push(adjacentPinId)
}
}

return connectedPinIds
}

const getManhattanDistance = ({
firstPin,
secondPin,
}: {
firstPin: InputPin
secondPin: InputPin
}) => Math.abs(firstPin.x - secondPin.x) + Math.abs(firstPin.y - secondPin.y)

const hasPinsOnSingleAxisOfChipEdges = (chip: InputChip) => {
const leftEdge = chip.center.x - chip.width / 2
const rightEdge = chip.center.x + chip.width / 2
const topEdge = chip.center.y + chip.height / 2
const bottomEdge = chip.center.y - chip.height / 2
const allPinsOnVerticalEdges = chip.pins.every(
(pin) =>
Math.abs(pin.x - leftEdge) < CHIP_EDGE_TOLERANCE ||
Math.abs(pin.x - rightEdge) < CHIP_EDGE_TOLERANCE,
)
const allPinsOnHorizontalEdges = chip.pins.every(
(pin) =>
Math.abs(pin.y - topEdge) < CHIP_EDGE_TOLERANCE ||
Math.abs(pin.y - bottomEdge) < CHIP_EDGE_TOLERANCE,
)

return allPinsOnVerticalEdges || allPinsOnHorizontalEdges
}

const findReplacementEdge = ({
connectedPinIds,
pins,
inputProblem,
chipMap,
pinIdMap,
maxDistance,
}: {
connectedPinIds: ReadonlySet<PinId>
pins: PinWithChip[]
inputProblem: InputProblem
chipMap: Record<string, InputChip>
pinIdMap: Map<PinId, PinWithChip>
maxDistance: number
}): MspEdge | null => {
let bestCandidate: ReplacementCandidate | null = null

for (const firstPin of pins) {
for (const secondPin of pins) {
if (!connectedPinIds.has(firstPin.pinId)) continue
if (connectedPinIds.has(secondPin.pinId)) continue
if (firstPin.chipId === secondPin.chipId) continue

const distance = getManhattanDistance({ firstPin, secondPin })
if (distance > maxDistance) continue
if (
arePinsInDifferentSchematicSections(inputProblem, firstPin, secondPin)
) {
continue
}
if (
doesPairCrossRestrictedCenterLines({
inputProblem,
chipMap,
pinIdMap,
p1: firstPin,
p2: secondPin,
})
) {
continue
}

const edge: MspEdge = [firstPin.pinId, secondPin.pinId]
const candidate: ReplacementCandidate = {
edge,
distance,
lowerPinId: firstPin.pinId,
upperPinId: secondPin.pinId,
}
if (candidate.lowerPinId > candidate.upperPinId) {
candidate.lowerPinId = secondPin.pinId
candidate.upperPinId = firstPin.pinId
}
if (!bestCandidate || candidate.distance < bestCandidate.distance) {
bestCandidate = candidate
continue
}
if (
candidate.distance === bestCandidate.distance &&
(candidate.lowerPinId < bestCandidate.lowerPinId ||
(candidate.lowerPinId === bestCandidate.lowerPinId &&
candidate.upperPinId < bestCandidate.upperPinId))
) {
bestCandidate = candidate
}
}
}

return bestCandidate?.edge ?? null
}

/** Reconnects avoidable same-component tree edges through nearby components. */
export const replaceIntraComponentMspEdges = ({
edges,
pins,
inputProblem,
chipMap,
pinIdMap,
maxDistance,
}: {
edges: MspEdge[]
pins: PinWithChip[]
inputProblem: InputProblem
chipMap: Record<string, InputChip>
pinIdMap: Map<PinId, PinWithChip>
maxDistance: number
}) => {
const outputEdges = [...edges]

for (let edgeIndex = 0; edgeIndex < outputEdges.length; edgeIndex++) {
const [firstPinId, secondPinId] = outputEdges[edgeIndex]!
const firstPin = pinIdMap.get(firstPinId)!
const secondPin = pinIdMap.get(secondPinId)!
if (firstPin.chipId !== secondPin.chipId) continue
if (!hasPinsOnSingleAxisOfChipEdges(chipMap[firstPin.chipId]!)) continue

const firstPinDegree = outputEdges.filter((edge) =>
edge.includes(firstPinId),
).length
const secondPinDegree = outputEdges.filter((edge) =>
edge.includes(secondPinId),
).length
const connectsLeafToHub =
(firstPinDegree === 1 &&
secondPinDegree >= MIN_HUB_DEGREE_WITH_LOCAL_EDGE) ||
(secondPinDegree === 1 &&
firstPinDegree >= MIN_HUB_DEGREE_WITH_LOCAL_EDGE)
if (!connectsLeafToHub) continue

const remainingEdges = outputEdges.filter(
(_edge, candidateIndex) => candidateIndex !== edgeIndex,
)
const connectedPinIds = getConnectedPinIds({
startPinId: firstPinId,
edges: remainingEdges,
})
const replacementEdge = findReplacementEdge({
connectedPinIds,
pins,
inputProblem,
chipMap,
pinIdMap,
maxDistance,
})
if (!replacementEdge) continue
outputEdges[edgeIndex] = replacementEdge
}

return outputEdges
}
63 changes: 63 additions & 0 deletions tests/functions/replaceIntraComponentMspEdges.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { expect, test } from "bun:test"
import { replaceIntraComponentMspEdges } from "lib/solvers/MspConnectionPairSolver/replaceIntraComponentMspEdges"
import type { InputProblem, PinId } from "lib/types/InputProblem"

test("replaces an avoidable intra-component MSP edge", () => {
const pins = [
{ pinId: "U1.1", chipId: "U1", x: 0, y: 1 },
{ pinId: "U1.2", chipId: "U1", x: 0, y: 0 },
{ pinId: "C1.1", chipId: "C1", x: -2, y: 1 },
{ pinId: "R1.1", chipId: "R1", x: 2, y: 1 },
]
const inputProblem: InputProblem = {
chips: [
{
chipId: "U1",
center: { x: 1, y: 0.5 },
width: 2,
height: 2,
pins: pins.filter((pin) => pin.chipId === "U1"),
},
{
chipId: "C1",
center: { x: -2.5, y: 1 },
width: 1,
height: 1,
pins: pins.filter((pin) => pin.chipId === "C1"),
},
{
chipId: "R1",
center: { x: 2.5, y: 1 },
width: 1,
height: 1,
pins: pins.filter((pin) => pin.chipId === "R1"),
},
],
directConnections: [],
netConnections: [],
availableNetLabelOrientations: {},
}
const chipMap = Object.fromEntries(
inputProblem.chips.map((chip) => [chip.chipId, chip]),
)
const pinIdMap = new Map(pins.map((pin) => [pin.pinId as PinId, pin]))

const edges = replaceIntraComponentMspEdges({
edges: [
["U1.2", "U1.1"],
["C1.1", "U1.1"],
["R1.1", "U1.1"],
],
pins,
inputProblem,
chipMap,
pinIdMap,
maxDistance: 5,
})

expect(edges).toEqual([
["U1.2", "C1.1"],
["C1.1", "U1.1"],
["R1.1", "U1.1"],
])
})
44 changes: 44 additions & 0 deletions tests/repros/__snapshots__/component-5-v5.snap.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Loading