Skip to content
Open
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
117 changes: 104 additions & 13 deletions lib/HyperGraphSolver.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { BaseSolver } from "@tscircuit/solver-utils"
import { convertSerializedHyperGraphToHyperGraph } from "./convertSerializedHyperGraphToHyperGraph"
import { convertHyperGraphToSerializedHyperGraph } from "./convertHyperGraphToSerializedHyperGraph"
import { convertConnectionsToSerializedConnections } from "./convertConnectionsToSerializedConnections"
import { convertHyperGraphToSerializedHyperGraph } from "./convertHyperGraphToSerializedHyperGraph"
import { convertSerializedConnectionsToConnections } from "./convertSerializedConnectionsToConnections"
import { convertSerializedHyperGraphToHyperGraph } from "./convertSerializedHyperGraphToHyperGraph"
import { PriorityQueue } from "./PriorityQueue"
import type {
Candidate,
Connection,
RegionPort,
PortId,
GScore,
HyperGraph,
SerializedConnection,
SerializedHyperGraph,
NetworkId,
PortId,
Region,
RegionId,
SolvedRoute,
RegionPort,
RegionPortAssignment,
GScore,
SerializedConnection,
SerializedHyperGraph,
SolvedRoute,
} from "./types"
import { convertSerializedConnectionsToConnections } from "./convertSerializedConnectionsToConnections"
import { PriorityQueue } from "./PriorityQueue"

export class HyperGraphSolver<
RegionType extends Region = Region,
Expand Down Expand Up @@ -61,6 +62,7 @@ export class HyperGraphSolver<
) {
super()
this.graph = convertSerializedHyperGraphToHyperGraph(input.inputGraph)
this.validateRegionCapacities()
for (const region of this.graph.regions) {
region.assignments = []
}
Expand Down Expand Up @@ -183,12 +185,81 @@ export class HyperGraphSolver<
computeRoutesToRip(newlySolvedRoute: SolvedRoute): Set<SolvedRoute> {
const crossingRoutesToRip = this.computeCrossingRoutes(newlySolvedRoute)
const portReuseRoutesToRip = this.computePortOverlapRoutes(newlySolvedRoute)
const regionCapacityRoutesToRip =
this.computeRegionCapacityOverlapRoutes(newlySolvedRoute)
return new Set<SolvedRoute>([
...crossingRoutesToRip,
...portReuseRoutesToRip,
...regionCapacityRoutesToRip,
])
}

private validateRegionCapacities() {
for (const region of this.graph.regions) {
if (region.capacity === undefined) continue
const { capacity } = region
if (
capacity !== Infinity &&
(!Number.isFinite(capacity) ||
capacity < 1 ||
!Number.isInteger(capacity))
) {
throw new Error(
`Region ${region.regionId} has invalid capacity ${capacity}. Capacity must be a positive integer or Infinity.`,
)
}
}
}

protected getRegionCapacity(region: RegionType): number {
return region.capacity ?? Infinity
}

protected getRegionAssignmentsByNetwork(
region: RegionType,
): Map<NetworkId, RegionPortAssignment[]> {
const assignmentsByNetwork = new Map<NetworkId, RegionPortAssignment[]>()
const assignments = region.assignments ?? []
for (const assignment of assignments) {
const networkId = assignment.connection.mutuallyConnectedNetworkId
assignmentsByNetwork.set(networkId, [
...(assignmentsByNetwork.get(networkId) ?? []),
assignment,
])
}
return assignmentsByNetwork
}

protected getRegionCapacityOverflowIfUsed(region: RegionType): number {
const capacity = this.getRegionCapacity(region)
if (!Number.isFinite(capacity)) return 0

const assignmentsByNetwork = this.getRegionAssignmentsByNetwork(region)
const currentNetworkId = this.currentConnection!.mutuallyConnectedNetworkId
const nextDistinctNetworkCount =
assignmentsByNetwork.size +
(assignmentsByNetwork.has(currentNetworkId) ? 0 : 1)

return Math.max(0, nextDistinctNetworkCount - capacity)
}

getRipsRequiredForRegionUsage(region: RegionType): RegionPortAssignment[] {
const overflow = this.getRegionCapacityOverflowIfUsed(region)
if (overflow <= 0) return []

const assignmentsByNetwork = this.getRegionAssignmentsByNetwork(region)
const currentNetworkId = this.currentConnection!.mutuallyConnectedNetworkId
const candidateNetworksToRip = [...assignmentsByNetwork.entries()]
.filter(([networkId]) => networkId !== currentNetworkId)
.sort((a, b) => a[1].length - b[1].length)

if (candidateNetworksToRip.length === 0) return []

return candidateNetworksToRip
.slice(0, overflow)
.flatMap(([, assignments]) => assignments)
}

/**
* Returns solved routes that overlap ports with the newly solved route.
* Use this in computeRoutesToRip overrides to include port reuse rips.
Expand Down Expand Up @@ -223,16 +294,36 @@ export class HyperGraphSolver<
return crossingRoutesToRip
}

computeRegionCapacityOverlapRoutes(
newlySolvedRoute: SolvedRoute,
): Set<SolvedRoute> {
const regionCapacityRoutesToRip: Set<SolvedRoute> = new Set()
for (const candidate of newlySolvedRoute.path) {
if (!candidate.lastRegion) continue
const ripsRequired = this.getRipsRequiredForRegionUsage(
candidate.lastRegion as RegionType,
)
for (const assignment of ripsRequired) {
regionCapacityRoutesToRip.add(assignment.solvedRoute)
}
}
return regionCapacityRoutesToRip
}

getNextCandidates(currentCandidate: CandidateType): CandidateType[] {
const currentRegion = currentCandidate.nextRegion!
const currentPort = currentCandidate.port
const regionUsageRipRequired =
this.getRipsRequiredForRegionUsage(currentRegion as RegionType).length > 0
const nextCandidatesByRegion: Record<RegionId, Candidate[]> = {}
for (const port of currentRegion.ports) {
if (port === currentCandidate.port) continue
const ripRequired =
port.assignment &&
port.assignment.connection.mutuallyConnectedNetworkId !==
this.currentConnection!.mutuallyConnectedNetworkId
!!(
port.assignment &&
port.assignment.connection.mutuallyConnectedNetworkId !==
this.currentConnection!.mutuallyConnectedNetworkId
) || regionUsageRipRequired
const newCandidate: Partial<Candidate> = {
port,
hops: currentCandidate.hops + 1,
Expand Down
18 changes: 13 additions & 5 deletions lib/JumperGraphSolver/JumperGraphSolver.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { distance } from "@tscircuit/math-utils"
import type { GraphicsObject } from "graphics-debug"
import { HyperGraphSolver } from "../HyperGraphSolver"
import type {
Expand All @@ -8,12 +9,11 @@ import type {
SerializedHyperGraph,
SolvedRoute,
} from "../types"
import type { JPort, JRegion } from "./jumper-types"
import { visualizeJumperGraphSolver } from "./visualizeJumperGraphSolver"
import { distance } from "@tscircuit/math-utils"
import { computeDifferentNetCrossings } from "./computeDifferentNetCrossings"
import { computeCrossingAssignments } from "./computeCrossingAssignments"
import { computeDifferentNetCrossings } from "./computeDifferentNetCrossings"
import { countInputConnectionCrossings } from "./countInputConnectionCrossings"
import type { JPort, JRegion } from "./jumper-types"
import { visualizeJumperGraphSolver } from "./visualizeJumperGraphSolver"

export const JUMPER_GRAPH_SOLVER_DEFAULTS = {
portUsagePenalty: 0.034685181009478865,
Expand Down Expand Up @@ -130,8 +130,16 @@ export class JumperGraphSolver extends HyperGraphSolver<JRegion, JPort> {
port1: JPort,
port2: JPort,
): number {
const capacityOverflow = this.getRegionCapacityOverflowIfUsed(region)
const capacityPenalty =
capacityOverflow * this.crossingPenalty +
capacityOverflow * this.crossingPenaltySq
const crossings = computeDifferentNetCrossings(region, port1, port2)
return crossings * this.crossingPenalty + crossings * this.crossingPenaltySq
return (
capacityPenalty +
crossings * this.crossingPenalty +
crossings * this.crossingPenaltySq
)
}

override getRipsRequiredForPortUsage(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { JPort, JRegion, JumperGraph } from "../jumper-types"
import { compose, rotate, translate } from "transformation-matrix"
import { applyTransformToGraph } from "../geometry/applyTransformToGraph"
import { computeBoundsCenter } from "../geometry/getBoundsCenter"
import { dims0603 } from "./generateSingleJumperRegions"
import type { JPort, JRegion, JumperGraph } from "../jumper-types"
import { calculateGraphBounds } from "./calculateGraphBounds"
import { applyTransformToGraph } from "../geometry/applyTransformToGraph"
import { compose, translate, rotate } from "transformation-matrix"
import { dims0603 } from "./generateSingleJumperRegions"

export const generateJumperGrid = ({
cols,
Expand Down Expand Up @@ -68,6 +68,7 @@ export const generateJumperGrid = ({
): JRegion => ({
regionId: id,
ports: [],
capacity: isPad || isThroughJumper ? 1 : undefined,
d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper },
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JPort, JRegion } from "../jumper-types"
import { computeBoundsCenter } from "../geometry/getBoundsCenter"
import type { JPort, JRegion } from "../jumper-types"
import { dims0606x2 } from "./generateSingleJumperX2Regions"

export const generateJumperX2Grid = ({
Expand Down Expand Up @@ -79,6 +79,7 @@ export const generateJumperX2Grid = ({
): JRegion => ({
regionId: id,
ports: [],
capacity: isPad || isThroughJumper ? 1 : undefined,
d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper },
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import type { JPort, JRegion, JumperGraph } from "../jumper-types"
import { compose, rotate, translate } from "transformation-matrix"
import { applyTransformToGraph } from "../geometry/applyTransformToGraph"
import { computeBoundsCenter } from "../geometry/getBoundsCenter"
import { dims1206x4 } from "./generateSingleJumperX4Regions"
import type { JPort, JRegion, JumperGraph } from "../jumper-types"
import { calculateGraphBounds } from "./calculateGraphBounds"
import { applyTransformToGraph } from "../geometry/applyTransformToGraph"
import { compose, translate, rotate } from "transformation-matrix"
import { dims1206x4 } from "./generateSingleJumperX4Regions"

export const generateJumperX4Grid = ({
cols,
Expand Down Expand Up @@ -136,6 +136,7 @@ export const generateJumperX4Grid = ({
): JRegion => ({
regionId: id,
ports: [],
capacity: isPad || isThroughJumper ? 1 : undefined,
d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper },
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JPort, JRegion } from "../jumper-types"
import { computeBoundsCenter } from "../geometry/getBoundsCenter"
import type { JPort, JRegion } from "../jumper-types"

export const dims0603 = {
padToPad: 1.65,
Expand Down Expand Up @@ -79,6 +79,7 @@ export const generateSingleJumperRegions = ({
): JRegion => ({
regionId: `${idPrefix}:${id}`,
ports: [],
capacity: isPad || isThroughJumper ? 1 : undefined,
d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper },
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JPort, JRegion } from "../jumper-types"
import { computeBoundsCenter } from "../geometry/getBoundsCenter"
import type { JPort, JRegion } from "../jumper-types"

// 0606x2 resistor chip array dimensions
// This is a 2-element array with 4 pads total (2 per resistor)
Expand Down Expand Up @@ -107,6 +107,7 @@ export const generateSingleJumperX2Regions = ({
): JRegion => ({
regionId: `${idPrefix}:${id}`,
ports: [],
capacity: isPad || isThroughJumper ? 1 : undefined,
d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper },
})

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { JPort, JRegion } from "../jumper-types"
import { computeBoundsCenter } from "../geometry/getBoundsCenter"
import type { JPort, JRegion } from "../jumper-types"

// EXB-38VR000V (Panasonic) 4x 0Ω isolated jumpers dimensions
// This is a 4-element array with 8 pads total (4 per side)
Expand Down Expand Up @@ -156,6 +156,7 @@ export const generateSingleJumperX4Regions = ({
): JRegion => ({
regionId: `${idPrefix}:${id}`,
ports: [],
capacity: isPad || isThroughJumper ? 1 : undefined,
d: { bounds, center: computeBoundsCenter(bounds), isPad, isThroughJumper },
})

Expand Down
3 changes: 2 additions & 1 deletion lib/convertHyperGraphToSerializedHyperGraph.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type {
HyperGraph,
SerializedHyperGraph,
SerializedGraphPort,
SerializedGraphRegion,
SerializedHyperGraph,
} from "./types"

export const convertHyperGraphToSerializedHyperGraph = (
Expand All @@ -20,6 +20,7 @@ export const convertHyperGraphToSerializedHyperGraph = (
regionId: region.regionId,
pointIds: region.ports.map((port) => port.portId),
d: region.d,
capacity: region.capacity,
}),
)

Expand Down
6 changes: 6 additions & 0 deletions lib/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export type Region = {
regionId: RegionId
ports: RegionPort[]
d: any
/**
* Maximum number of distinct networks allowed to occupy this region.
* If undefined, the region has no capacity limit.
*/
capacity?: number
assignments?: RegionPortAssignment[]
}

Expand Down Expand Up @@ -82,6 +87,7 @@ export type SerializedGraphRegion = {
regionId: RegionId
pointIds: PortId[]
d: any
capacity?: number
assignments?: SerializedRegionPortAssignment[]
}
export type SerializedRegionPortAssignment = {
Expand Down
Loading