Skip to content

Commit 4496fd6

Browse files
Fix issue
1 parent e786ad0 commit 4496fd6

File tree

5 files changed

+266
-0
lines changed

5 files changed

+266
-0
lines changed
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import type { Connection, HyperGraph, SerializedConnection } from "./types"
2+
import type { Candidate, Region, RegionPort, SerializedHyperGraph } from "./types"
3+
import { HyperGraphSolver } from "./HyperGraphSolver"
4+
5+
export type HyperGraphPartialRippingInput = {
6+
inputGraph: HyperGraph | SerializedHyperGraph
7+
inputConnections: (Connection | SerializedConnection)[]
8+
greedyMultiplier?: number
9+
ripCost?: number
10+
rippingEnabled?: boolean
11+
ripCostThreshold?: number
12+
}
13+
14+
export class HyperGraphPartialRippingSolver<
15+
RegionType extends Region = Region,
16+
RegionPortType extends RegionPort = RegionPort,
17+
CandidateType extends Candidate<RegionType, RegionPortType> = Candidate<
18+
RegionType,
19+
RegionPortType
20+
>,
21+
> extends HyperGraphSolver<RegionType, RegionPortType, CandidateType> {
22+
override getSolverName(): string {
23+
return "HyperGraphPartialRippingSolver"
24+
}
25+
26+
ripCostThreshold = 0
27+
28+
constructor(input: HyperGraphPartialRippingInput) {
29+
super({
30+
...input,
31+
rippingEnabled: input.rippingEnabled ?? true,
32+
})
33+
this.ripCostThreshold = input.ripCostThreshold ?? this.ripCostThreshold
34+
}
35+
36+
override shouldAllowRip(candidate: CandidateType): boolean {
37+
const priorCost = candidate.parent?.g ?? 0
38+
return priorCost >= this.ripCostThreshold
39+
}
40+
}

lib/HyperGraphSolver.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ export class HyperGraphSolver<
147147
return []
148148
}
149149

150+
/**
151+
* OPTIONALLY OVERRIDE THIS
152+
*
153+
* Return true if a candidate that requires ripping should be considered.
154+
* This allows partial ripping strategies to gate when ripping is allowed.
155+
*/
156+
shouldAllowRip(_candidate: CandidateType): boolean {
157+
return true
158+
}
159+
150160
computeG(candidate: CandidateType): number {
151161
return (
152162
candidate.parent!.g +
@@ -194,6 +204,13 @@ export class HyperGraphSolver<
194204
if (!this.rippingEnabled && newCandidate.ripRequired) {
195205
continue
196206
}
207+
if (
208+
this.rippingEnabled &&
209+
newCandidate.ripRequired &&
210+
!this.shouldAllowRip(newCandidate as CandidateType)
211+
) {
212+
continue
213+
}
197214

198215
nextCandidatesByRegion[newCandidate.nextRegion!.regionId] ??= []
199216
nextCandidatesByRegion[newCandidate.nextRegion!.regionId].push(

lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ export * from "./JumperGraphSolver/jumper-graph-generator/generateJumperGrid"
33
export * from "./JumperGraphSolver/jumper-graph-generator/createGraphWithConnectionsFromBaseGraph"
44
export * from "./JumperGraphSolver/JumperGraphSolver"
55
export * from "./HyperGraphSolver"
6+
export * from "./HyperGraphPartialRippingSolver"
67
export * from "./convertHyperGraphToSerializedHyperGraph"
78
export * from "./convertConnectionsToSerializedConnections"
89
export * from "./JumperGraphSolver/geometry/applyTransformToGraph"
Lines changed: 1 addition & 0 deletions
Loading
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import { expect, test } from "bun:test"
2+
import { HyperGraphPartialRippingSolver } from "lib/HyperGraphPartialRippingSolver"
3+
import type { HyperGraph, Connection } from "lib/types"
4+
5+
type BasicRegion = {
6+
regionId: string
7+
ports: BasicPort[]
8+
d: Record<string, never>
9+
}
10+
11+
type BasicPort = {
12+
portId: string
13+
region1: BasicRegion
14+
region2: BasicRegion
15+
d: Record<string, never>
16+
assignment?: never
17+
ripCount?: number
18+
}
19+
20+
const buildGraph = (): { graph: HyperGraph; connections: Connection[] } => {
21+
const regionA: BasicRegion = { regionId: "A", ports: [], d: {} }
22+
const regionB: BasicRegion = { regionId: "B", ports: [], d: {} }
23+
const regionC: BasicRegion = { regionId: "C", ports: [], d: {} }
24+
const regionD: BasicRegion = { regionId: "D", ports: [], d: {} }
25+
const regionE: BasicRegion = { regionId: "E", ports: [], d: {} }
26+
const regionF: BasicRegion = { regionId: "F", ports: [], d: {} }
27+
const regionG: BasicRegion = { regionId: "G", ports: [], d: {} }
28+
29+
const port1: BasicPort = {
30+
portId: "P1",
31+
region1: regionA,
32+
region2: regionC,
33+
d: {},
34+
}
35+
const port2: BasicPort = {
36+
portId: "P2",
37+
region1: regionC,
38+
region2: regionB,
39+
d: {},
40+
}
41+
const port3: BasicPort = {
42+
portId: "P3",
43+
region1: regionA,
44+
region2: regionD,
45+
d: {},
46+
}
47+
const port4: BasicPort = {
48+
portId: "P4",
49+
region1: regionD,
50+
region2: regionE,
51+
d: {},
52+
}
53+
const port5: BasicPort = {
54+
portId: "P5",
55+
region1: regionE,
56+
region2: regionB,
57+
d: {},
58+
}
59+
const port6: BasicPort = {
60+
portId: "P6",
61+
region1: regionA,
62+
region2: regionF,
63+
d: {},
64+
}
65+
const port7: BasicPort = {
66+
portId: "P7",
67+
region1: regionF,
68+
region2: regionG,
69+
d: {},
70+
}
71+
const port8: BasicPort = {
72+
portId: "P8",
73+
region1: regionG,
74+
region2: regionB,
75+
d: {},
76+
}
77+
78+
regionA.ports.push(port1, port3, port6)
79+
regionB.ports.push(port2, port5, port8)
80+
regionC.ports.push(port1, port2)
81+
regionD.ports.push(port3, port4)
82+
regionE.ports.push(port4, port5)
83+
regionF.ports.push(port6, port7)
84+
regionG.ports.push(port7, port8)
85+
86+
const graph: HyperGraph = {
87+
regions: [regionA, regionB, regionC, regionD, regionE, regionF, regionG],
88+
ports: [port1, port2, port3, port4, port5, port6, port7, port8],
89+
}
90+
91+
const connections: Connection[] = [
92+
{
93+
connectionId: "conn-1",
94+
mutuallyConnectedNetworkId: "net-1",
95+
startRegion: regionA,
96+
endRegion: regionB,
97+
},
98+
{
99+
connectionId: "conn-2",
100+
mutuallyConnectedNetworkId: "net-2",
101+
startRegion: regionA,
102+
endRegion: regionB,
103+
},
104+
]
105+
106+
return { graph, connections }
107+
}
108+
109+
class BasicPartialRippingSolver extends HyperGraphPartialRippingSolver<
110+
BasicRegion,
111+
BasicPort
112+
> {
113+
override estimateCostToEnd(): number {
114+
return 0
115+
}
116+
117+
override computeIncreasedRegionCostIfPortsAreUsed(): number {
118+
return 1
119+
}
120+
121+
override getPortUsagePenalty(port: BasicPort): number {
122+
return (port.ripCount ?? 0) * 5
123+
}
124+
}
125+
126+
const solveWithThreshold = (ripCostThreshold: number) => {
127+
const { graph, connections } = buildGraph()
128+
const solver = new BasicPartialRippingSolver({
129+
inputGraph: graph,
130+
inputConnections: connections,
131+
ripCostThreshold,
132+
})
133+
solver.solve()
134+
return solver.solvedRoutes.map((route) => ({
135+
connectionId: route.connection.connectionId,
136+
requiredRip: route.requiredRip,
137+
portIds: route.path.map((candidate) => candidate.port.portId),
138+
}))
139+
}
140+
141+
const renderSvg = (results: {
142+
thresholdZero: Array<{ connectionId: string; requiredRip: boolean }>
143+
thresholdTwo: Array<{ connectionId: string; requiredRip: boolean }>
144+
}) => {
145+
const rowHeight = 40
146+
const gap = 10
147+
const leftPadding = 20
148+
const topPadding = 20
149+
const barWidth = 260
150+
const barHeight = 18
151+
152+
const rows = [
153+
{ label: "thresholdZero", items: results.thresholdZero },
154+
{ label: "thresholdTwo", items: results.thresholdTwo },
155+
]
156+
157+
const height =
158+
topPadding + rows.length * rowHeight + (rows.length - 1) * gap
159+
160+
let y = topPadding
161+
const bars = rows
162+
.map((row) => {
163+
const rowY = y
164+
y += rowHeight + gap
165+
const label = `<text x="${leftPadding}" y="${
166+
rowY + 14
167+
}" font-family="monospace" font-size="12">${row.label}</text>`
168+
const rects = row.items
169+
.map((item, index) => {
170+
const color = item.requiredRip ? "#e74c3c" : "#2ecc71"
171+
const rectX = leftPadding + 120 + index * (barWidth + 12)
172+
const rectY = rowY
173+
const rect = `<rect x="${rectX}" y="${rectY}" width="${barWidth}" height="${barHeight}" rx="4" fill="${color}" />`
174+
const text = `<text x="${rectX + 8}" y="${
175+
rectY + 13
176+
}" font-family="monospace" font-size="11" fill="#ffffff">${
177+
item.connectionId
178+
}${item.requiredRip ? " rip" : " ok"}</text>`
179+
return `${rect}${text}`
180+
})
181+
.join("")
182+
return `${label}${rects}`
183+
})
184+
.join("")
185+
186+
return `<svg xmlns="http://www.w3.org/2000/svg" width="900" height="${height}">${bars}</svg>`
187+
}
188+
189+
test("hypergraph partial ripping defers ripping until threshold", () => {
190+
const results = {
191+
thresholdZero: solveWithThreshold(0),
192+
thresholdTwo: solveWithThreshold(2),
193+
}
194+
195+
const svg = renderSvg({
196+
thresholdZero: results.thresholdZero.map(({ connectionId, requiredRip }) => ({
197+
connectionId,
198+
requiredRip,
199+
})),
200+
thresholdTwo: results.thresholdTwo.map(({ connectionId, requiredRip }) => ({
201+
connectionId,
202+
requiredRip,
203+
})),
204+
})
205+
206+
expect(svg).toMatchSvgSnapshot(import.meta.path)
207+
})

0 commit comments

Comments
 (0)