Skip to content
Merged
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
66 changes: 66 additions & 0 deletions pages/bus-routing/region-span.page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { SerializedHyperGraph } from "@tscircuit/hypergraph"
import { loadSerializedHyperGraph } from "lib/compat/loadSerializedHyperGraph"
import { TinyHyperGraphBusSolver, TinyHyperGraphSolver } from "lib/index"
import {
BUS_REGION_SPAN_ROUTE_COUNT,
BUS_REGION_SPAN_SHARED_PORTS_PER_MIDDLE_EDGE,
busRegionSpanFixture,
} from "../../tests/fixtures/bus-region-span.fixture"
import { Debugger } from "../components/Debugger"

const createBusSolver = (serializedHyperGraph: SerializedHyperGraph) => {
const { topology, problem } = loadSerializedHyperGraph(serializedHyperGraph)
return new TinyHyperGraphBusSolver(topology, problem, {
MAX_ITERATIONS: 50_000,
})
}

const createPlainSolver = (serializedHyperGraph: SerializedHyperGraph) => {
const { topology, problem } = loadSerializedHyperGraph(serializedHyperGraph)
return new TinyHyperGraphSolver(topology, problem, {
MAX_ITERATIONS: 50_000,
})
}

export default function BusRoutingRegionSpanPage() {
return (
<div className="flex h-screen flex-col gap-3 p-3">
<div className="rounded border border-slate-300 bg-white p-3 text-sm text-slate-700">
This synthetic repro builds a{" "}
<code>{BUS_REGION_SPAN_ROUTE_COUNT}-trace</code> bus with four main
routing regions: a large <code>top-main</code>, a large{" "}
<code>bottom-main</code>, and a slit middle split into{" "}
<code>mid-left</code> and <code>mid-right</code>. Each middle half only
exposes <code>{BUS_REGION_SPAN_SHARED_PORTS_PER_MIDDLE_EDGE}</code>{" "}
shared ports on its top and bottom edges, so no single half can carry
the full bus. Two extra bottom-side regions keep the middle halves more
than two hops from the goal transit region, so the bus solver cannot
escape through the manual two-hop finish rule.
</div>
<div className="grid min-h-0 flex-1 gap-3 lg:grid-cols-2">
<section className="flex min-h-0 flex-col overflow-hidden rounded border border-slate-300 bg-white">
<div className="border-b border-slate-200 px-3 py-2 text-sm font-medium text-slate-800">
Current Bus Solver
</div>
<div className="min-h-0 flex-1">
<Debugger
serializedHyperGraph={busRegionSpanFixture}
createSolver={createBusSolver}
/>
</div>
</section>
<section className="flex min-h-0 flex-col overflow-hidden rounded border border-slate-300 bg-white">
<div className="border-b border-slate-200 px-3 py-2 text-sm font-medium text-slate-800">
Reference Plain Solver
</div>
<div className="min-h-0 flex-1">
<Debugger
serializedHyperGraph={busRegionSpanFixture}
createSolver={createPlainSolver}
/>
</div>
</section>
</div>
</div>
)
}
152 changes: 152 additions & 0 deletions tests/fixtures/bus-region-span.fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import type { SerializedHyperGraph } from "@tscircuit/hypergraph"

export const BUS_REGION_SPAN_ROUTE_COUNT = 6
export const BUS_REGION_SPAN_SHARED_PORTS_PER_MIDDLE_EDGE = 4

const START_END_XS = [-7.5, -4.5, -1.5, 1.5, 4.5, 7.5] as const
const MID_LEFT_XS = [-6.5, -5.0, -3.5, -2.0] as const
const MID_RIGHT_XS = [2.0, 3.5, 5.0, 6.5] as const

const createRegion = (
regionId: string,
centerX: number,
centerY: number,
width: number,
height: number,
pointIds: string[],
): NonNullable<SerializedHyperGraph["regions"]>[number] => ({
regionId,
pointIds,
d: {
center: { x: centerX, y: centerY },
width,
height,
},
})

const createPort = (
portId: string,
region1Id: string,
region2Id: string,
x: number,
y: number,
): NonNullable<SerializedHyperGraph["ports"]>[number] => ({
portId,
region1Id,
region2Id,
d: {
x,
y,
z: 0,
},
})

const createConnection = (
routeIndex: number,
): NonNullable<SerializedHyperGraph["connections"]>[number] => ({
connectionId: `route-${routeIndex}`,
startRegionId: `start-${routeIndex}`,
endRegionId: `end-${routeIndex}`,
mutuallyConnectedNetworkId: `net-${routeIndex}`,
})

const topStartPortIds = START_END_XS.map(
(_, routeIndex) => `start-port-${routeIndex}`,
)
const bottomChainPortIds = START_END_XS.map(
(_, routeIndex) => `bottom-chain-port-${routeIndex}`,
)
const bottomExitPortIds = START_END_XS.map(
(_, routeIndex) => `bottom-exit-port-${routeIndex}`,
)
const endPortIds = START_END_XS.map((_, routeIndex) => `end-port-${routeIndex}`)
const topLeftMidPortIds = MID_LEFT_XS.map((_, portIndex) => `tl-${portIndex}`)
const bottomLeftMidPortIds = MID_LEFT_XS.map(
(_, portIndex) => `bl-${portIndex}`,
)
const topRightMidPortIds = MID_RIGHT_XS.map((_, portIndex) => `tr-${portIndex}`)
const bottomRightMidPortIds = MID_RIGHT_XS.map(
(_, portIndex) => `br-${portIndex}`,
)

export const busRegionSpanFixture: SerializedHyperGraph = {
regions: [
...START_END_XS.flatMap((x, routeIndex) => [
createRegion(`start-${routeIndex}`, x, 12, 1.2, 1.2, [
`start-port-${routeIndex}`,
]),
createRegion(`end-${routeIndex}`, x, -16, 1.2, 1.2, [
`end-port-${routeIndex}`,
]),
]),
createRegion("top-main", 0, 7.5, 18, 3, [
...topStartPortIds,
...topLeftMidPortIds,
...topRightMidPortIds,
]),
createRegion("mid-left", -4, 2.5, 6, 7, [
...topLeftMidPortIds,
...bottomLeftMidPortIds,
]),
createRegion("mid-right", 4, 2.5, 6, 7, [
...topRightMidPortIds,
...bottomRightMidPortIds,
]),
createRegion("bottom-main", 0, -3.5, 18, 3, [
...bottomLeftMidPortIds,
...bottomRightMidPortIds,
...bottomChainPortIds,
]),
createRegion("bottom-buffer", 0, -9.5, 18, 3, [
...bottomChainPortIds,
...bottomExitPortIds,
]),
createRegion("bottom-exit", 0, -13, 18, 2.5, [
...bottomExitPortIds,
...endPortIds,
]),
],
ports: [
...START_END_XS.flatMap((x, routeIndex) => [
createPort(
`start-port-${routeIndex}`,
`start-${routeIndex}`,
"top-main",
x,
10.5,
),
createPort(
`bottom-chain-port-${routeIndex}`,
"bottom-main",
"bottom-buffer",
x,
-6.5,
),
createPort(
`bottom-exit-port-${routeIndex}`,
"bottom-buffer",
"bottom-exit",
x,
-11.25,
),
createPort(
`end-port-${routeIndex}`,
"bottom-exit",
`end-${routeIndex}`,
x,
-14.5,
),
]),
...MID_LEFT_XS.flatMap((x, portIndex) => [
createPort(`tl-${portIndex}`, "top-main", "mid-left", x, 6.0),
createPort(`bl-${portIndex}`, "mid-left", "bottom-main", x, -0.5),
]),
...MID_RIGHT_XS.flatMap((x, portIndex) => [
createPort(`tr-${portIndex}`, "top-main", "mid-right", x, 6.0),
createPort(`br-${portIndex}`, "mid-right", "bottom-main", x, -0.5),
]),
],
connections: START_END_XS.map((_, routeIndex) =>
createConnection(routeIndex),
),
}
101 changes: 101 additions & 0 deletions tests/solver/bus-region-span-repro.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import { expect, test } from "bun:test"
import { loadSerializedHyperGraph } from "lib/compat/loadSerializedHyperGraph"
import { TinyHyperGraphBusSolver, TinyHyperGraphSolver } from "lib/index"
import {
BUS_REGION_SPAN_ROUTE_COUNT,
BUS_REGION_SPAN_SHARED_PORTS_PER_MIDDLE_EDGE,
busRegionSpanFixture,
} from "tests/fixtures/bus-region-span.fixture"

const countSharedPorts = (regionAId: string, regionBId: string) =>
busRegionSpanFixture.ports.filter(
(port) =>
(port.region1Id === regionAId && port.region2Id === regionBId) ||
(port.region1Id === regionBId && port.region2Id === regionAId),
).length

const getRegionIndexBySerializedId = (
topology: ReturnType<typeof loadSerializedHyperGraph>["topology"],
) => {
const regionIndexBySerializedId = new Map<string, number>()

topology.regionMetadata?.forEach((metadata, regionIndex) => {
const serializedRegionId = (metadata as { serializedRegionId?: string })
.serializedRegionId
if (typeof serializedRegionId === "string") {
regionIndexBySerializedId.set(serializedRegionId, regionIndex)
}
})

return regionIndexBySerializedId
}

test("repro: six-trace slit middle requires spanning both middle regions", () => {
const { topology, problem } = loadSerializedHyperGraph(busRegionSpanFixture)
const plainSolver = new TinyHyperGraphSolver(topology, problem, {
MAX_ITERATIONS: 50_000,
})

expect(problem.routeCount).toBe(BUS_REGION_SPAN_ROUTE_COUNT)
expect(countSharedPorts("top-main", "mid-left")).toBe(
BUS_REGION_SPAN_SHARED_PORTS_PER_MIDDLE_EDGE,
)
expect(countSharedPorts("top-main", "mid-right")).toBe(
BUS_REGION_SPAN_SHARED_PORTS_PER_MIDDLE_EDGE,
)
expect(countSharedPorts("mid-left", "bottom-main")).toBe(
BUS_REGION_SPAN_SHARED_PORTS_PER_MIDDLE_EDGE,
)
expect(countSharedPorts("mid-right", "bottom-main")).toBe(
BUS_REGION_SPAN_SHARED_PORTS_PER_MIDDLE_EDGE,
)
expect(problem.routeCount).toBeGreaterThan(
countSharedPorts("top-main", "mid-left"),
)
expect(problem.routeCount).toBeGreaterThan(
countSharedPorts("top-main", "mid-right"),
)

plainSolver.solve()

expect(plainSolver.solved).toBe(true)
expect(plainSolver.failed).toBe(false)

const solvedRoutes = plainSolver.getOutput().solvedRoutes ?? []
const routesUsingMidLeft = solvedRoutes.filter((route) =>
route.path.some((node) => node.nextRegionId === "mid-left"),
)
const routesUsingMidRight = solvedRoutes.filter((route) =>
route.path.some((node) => node.nextRegionId === "mid-right"),
)

expect(solvedRoutes).toHaveLength(BUS_REGION_SPAN_ROUTE_COUNT)
expect(routesUsingMidLeft.length).toBeGreaterThan(0)
expect(routesUsingMidRight.length).toBeGreaterThan(0)
})

test("repro: current bus solver fails on the split-middle span fixture", () => {
const { topology, problem } = loadSerializedHyperGraph(busRegionSpanFixture)
const busSolver = new TinyHyperGraphBusSolver(topology, problem, {
MAX_ITERATIONS: 50_000,
})
const regionIndexBySerializedId = getRegionIndexBySerializedId(topology)

const midLeftRegionIndex = regionIndexBySerializedId.get("mid-left")
const midRightRegionIndex = regionIndexBySerializedId.get("mid-right")

expect(midLeftRegionIndex).toBeDefined()
expect(midRightRegionIndex).toBeDefined()
expect(
busSolver.centerGoalHopDistanceByRegion[midLeftRegionIndex!],
).toBeGreaterThan(2)
expect(
busSolver.centerGoalHopDistanceByRegion[midRightRegionIndex!],
).toBeGreaterThan(2)

busSolver.solve()

expect(busSolver.solved).toBe(false)
expect(busSolver.failed).toBe(true)
expect(busSolver.error).toBeTruthy()
})
Loading