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
252 changes: 252 additions & 0 deletions fixtures/jumper-graph-solver-0603-generated.fixture.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
import { generate0603JumperHyperGraph } from "@tscircuit/jumper-topology-generator"
import { GenericSolverDebugger } from "@tscircuit/solver-utils/react"
import type { JPort, JRegion } from "lib/index"
import { JumperGraphSolver } from "lib/JumperGraphSolver/JumperGraphSolver"
import { createProblemFromBaseGraph } from "lib/JumperGraphSolver/jumper-graph-generator/createProblemFromBaseGraph"
import type { JumperGraph } from "lib/JumperGraphSolver/jumper-types"
import { useMemo, useState } from "react"

const MIN_ROWS = 1
const MAX_ROWS = 6
const MIN_COLS = 1
const MAX_COLS = 6
const MIN_CROSSINGS = 1
const MAX_CROSSINGS = 12

export default () => {
const [rows, setRows] = useState(3)
const [cols, setCols] = useState(2)
const [orientation, setOrientation] = useState<"horizontal" | "vertical">(
"horizontal",
)
const [pattern, setPattern] = useState<"grid" | "staggered">("staggered")
const [staggerAxis, setStaggerAxis] = useState<"x" | "y">("x")
const [numCrossings, setNumCrossings] = useState(2)
const [seed, setSeed] = useState(0)
const [resetNonce, setResetNonce] = useState(0)

const problemState = useMemo(() => {
try {
const baseGraph = generate0603JumperHyperGraph({
rows,
cols,
orientation,
pattern,
...(pattern === "staggered" ? { staggerAxis } : {}),
}) as unknown as JumperGraph

const graphWithConnections = createProblemFromBaseGraph({
baseGraph,
numCrossings,
randomSeed: seed,
})

return {
problem: {
graph: graphWithConnections,
connections: graphWithConnections.connections,
},
error: null as string | null,
}
} catch (error) {
return {
problem: null,
error:
error instanceof Error
? error.message
: "Unknown problem generation error",
}
}
}, [rows, cols, orientation, pattern, staggerAxis, numCrossings, seed])

const problem = problemState.problem
const debuggerKey = [
rows,
cols,
orientation,
pattern,
staggerAxis,
numCrossings,
seed,
resetNonce,
].join("|")

return (
<div style={{ display: "flex", flexDirection: "column", height: "100vh" }}>
<div
style={{
padding: "10px 20px",
borderBottom: "1px solid #ccc",
background: "#f5f5f5",
fontFamily: "monospace",
fontSize: 14,
display: "flex",
alignItems: "center",
gap: 12,
flexWrap: "wrap",
}}
>
<label>
Rows:{" "}
<input
type="number"
min={MIN_ROWS}
max={MAX_ROWS}
value={rows}
onChange={(e) =>
setRows(
Math.max(
MIN_ROWS,
Math.min(MAX_ROWS, parseInt(e.target.value || "0", 10) || 1),
),
)
}
style={{ width: 60 }}
/>
</label>

<label>
Cols:{" "}
<input
type="number"
min={MIN_COLS}
max={MAX_COLS}
value={cols}
onChange={(e) =>
setCols(
Math.max(
MIN_COLS,
Math.min(MAX_COLS, parseInt(e.target.value || "0", 10) || 1),
),
)
}
style={{ width: 60 }}
/>
</label>

<label>
Orientation:{" "}
<select
value={orientation}
onChange={(e) =>
setOrientation(e.target.value as "horizontal" | "vertical")
}
>
<option value="horizontal">horizontal</option>
<option value="vertical">vertical</option>
</select>
</label>

<label>
Pattern:{" "}
<select
value={pattern}
onChange={(e) => setPattern(e.target.value as "grid" | "staggered")}
>
<option value="grid">grid</option>
<option value="staggered">staggered</option>
</select>
</label>

{pattern === "staggered" && (
<label>
Stagger Axis:{" "}
<select
value={staggerAxis}
onChange={(e) => setStaggerAxis(e.target.value as "x" | "y")}
>
<option value="x">x</option>
<option value="y">y</option>
</select>
</label>
)}

<label>
Crossings:{" "}
<input
type="number"
min={MIN_CROSSINGS}
max={MAX_CROSSINGS}
value={numCrossings}
onChange={(e) =>
setNumCrossings(
Math.max(
MIN_CROSSINGS,
Math.min(
MAX_CROSSINGS,
parseInt(e.target.value || "0", 10) || MIN_CROSSINGS,
),
),
)
}
style={{ width: 70 }}
/>
</label>

<label>
Seed:{" "}
<input
type="number"
value={seed}
onChange={(e) => setSeed(parseInt(e.target.value || "0", 10) || 0)}
style={{ width: 80 }}
/>
</label>

<button
onClick={() => {
setSeed(Math.floor(Math.random() * 100000))
}}
>
Random Seed
</button>

<button onClick={() => setResetNonce((k) => k + 1)}>
Reset Solver
</button>

<span style={{ marginLeft: 8 }}>
{problem
? `Regions: ${problem.graph.regions.length}, Ports: ${problem.graph.ports.length}, Connections: ${problem.connections.length}`
: "No problem generated"}
</span>
</div>

{problemState.error && (
<div
style={{
padding: "8px 20px",
color: "#8a1c1c",
background: "#ffe9e9",
borderBottom: "1px solid #e4b9b9",
fontFamily: "monospace",
fontSize: 13,
}}
>
Generation error: {problemState.error}
</div>
)}

<div style={{ flex: 1 }}>
{problem ? (
<GenericSolverDebugger
key={debuggerKey}
createSolver={() =>
new JumperGraphSolver({
inputGraph: {
regions: problem.graph.regions as JRegion[],
ports: problem.graph.ports as unknown as JPort[],
},
inputConnections: problem.connections,
})
}
/>
) : (
<div style={{ padding: 20, fontFamily: "monospace" }}>
Adjust rows/cols/crossings/seed to generate a valid problem.
</div>
)}
</div>
</div>
)
}
20 changes: 10 additions & 10 deletions lib/HyperGraphSolver.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
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,
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
53 changes: 49 additions & 4 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 @@ -148,6 +148,51 @@ export class JumperGraphSolver extends HyperGraphSolver<JRegion, JPort> {
)
}

override computeRoutesToRip(newlySolvedRoute: SolvedRoute): Set<SolvedRoute> {
const routesToRip = super.computeRoutesToRip(newlySolvedRoute)
for (const route of this.computeSingleNetRegionOverlapRoutes(
newlySolvedRoute,
)) {
routesToRip.add(route)
}
return routesToRip
}

private isSingleNetRegion(region: JRegion): boolean {
const regionData = region.d as Record<string, unknown>
const maxDistinctNets = regionData.maxDistinctNets
if (typeof maxDistinctNets === "number") return maxDistinctNets <= 1

return Boolean(
regionData.singleNetOnly ||
regionData.singleNet ||
regionData.exclusiveToSingleNet ||
regionData.isThroughJumper,
)
}

private computeSingleNetRegionOverlapRoutes(
newlySolvedRoute: SolvedRoute,
): Set<SolvedRoute> {
const singleNetRegionRoutesToRip: Set<SolvedRoute> = new Set()
const networkId = newlySolvedRoute.connection.mutuallyConnectedNetworkId

for (const candidate of newlySolvedRoute.path) {
const region = candidate.lastRegion
if (!region) continue
if (!this.isSingleNetRegion(region)) continue

for (const assignment of region.assignments ?? []) {
if (assignment.connection.mutuallyConnectedNetworkId === networkId) {
continue
}
singleNetRegionRoutesToRip.add(assignment.solvedRoute)
}
}

return singleNetRegionRoutesToRip
}

override routeSolvedHook(solvedRoute: SolvedRoute) {}

override routeStartedHook(connection: Connection) {}
Expand Down
Loading