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
17 changes: 16 additions & 1 deletion lib/outside-in-partial-rip-tiny-hypergraph-solver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ type CompletedRoundSummary = RegionCostSummary & {
squaredRegionSegmentCount: number
}

const NO_PREFERRED_PRESERVED_ROUTE_IDS = new Set<RouteId>()

/**
* Retains the two outside portions of a completed route and only reroutes a
* bounded window around a congested region. The active window is represented
Expand Down Expand Up @@ -234,6 +236,14 @@ export class OutsideInPartialRipTinyHyperGraphSolver extends DistanceAwareTinyHy
}
}

/**
* Quality rerips retain these routes when other routes cross the same hot
* regions. Completion rerips may still use them when they are unavoidable.
*/
protected getRouteIdsPreferredForPreservation(): ReadonlySet<RouteId> {
return NO_PREFERRED_PRESERVED_ROUTE_IDS
}

private getCommittedRouteSegments(
routeId: RouteId,
): CommittedRouteSegment[] | undefined {
Expand Down Expand Up @@ -423,6 +433,8 @@ export class OutsideInPartialRipTinyHyperGraphSolver extends DistanceAwareTinyHy
}
}
if (routeIdsTouchingHotRegions.size === 0) return false
const preferredPreservedRouteIds =
this.getRouteIdsPreferredForPreservation()

const retainedSegmentsByRegion = Array.from(
{ length: this.topology.regionCount },
Expand All @@ -436,7 +448,10 @@ export class OutsideInPartialRipTinyHyperGraphSolver extends DistanceAwareTinyHy
const orderedSegments = this.getCommittedRouteSegments(routeId)
if (!orderedSegments) return false

if (!routeIdsTouchingHotRegions.has(routeId)) {
if (
!routeIdsTouchingHotRegions.has(routeId) ||
preferredPreservedRouteIds.has(routeId)
) {
for (const segment of orderedSegments) {
this.appendRetainedSegment(retainedSegmentsByRegion, routeId, segment)
retainedSegmentCount += 1
Expand Down
31 changes: 29 additions & 2 deletions lib/selective-rerip-tiny-hyper-graph-solver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export class SelectiveReripTinyHyperGraphSolver extends OutsideInPartialRipTinyH
)
}

const directPath = this.findRelaxedBlockerPath()
const directPath = this.findRelaxedBlockerPathPreferringPreservedRoutes()
if (!directPath.found || directPath.owners.size === 0) {
this.selectiveReripStats.globalReripCount += 1
this.selectiveReripStats.globalReripReason = !directPath.found
Expand Down Expand Up @@ -228,7 +228,7 @@ export class SelectiveReripTinyHyperGraphSolver extends OutsideInPartialRipTinyH
| undefined
if (repeatedOwnerRouteIds.length > 0) {
this.selectiveReripStats.alternateBlockerSearchCount += 1
alternatePath = this.findRelaxedBlockerPath(
alternatePath = this.findRelaxedBlockerPathPreferringPreservedRoutes(
new Set(repeatedOwnerRouteIds),
)
if (!alternatePath.found) {
Expand Down Expand Up @@ -350,6 +350,33 @@ export class SelectiveReripTinyHyperGraphSolver extends OutsideInPartialRipTinyH
})
}

protected findRelaxedBlockerPathPreferringPreservedRoutes(
forbiddenOwnerRouteIds: ReadonlySet<RouteId> = new Set<RouteId>(),
): DistinctOwnerBlockerSearchResult<
RelaxedSearchState,
RouteId,
RelaxedSearchHopData
> {
const preferredPreservedRouteIds =
this.getRouteIdsPreferredForPreservation()
if (preferredPreservedRouteIds.size === 0) {
return this.findRelaxedBlockerPath(forbiddenOwnerRouteIds)
}

const preferredForbiddenOwnerRouteIds = new Set(forbiddenOwnerRouteIds)
for (const routeId of preferredPreservedRouteIds) {
preferredForbiddenOwnerRouteIds.add(routeId)
}
const preferredPath = this.findRelaxedBlockerPath(
preferredForbiddenOwnerRouteIds,
)
if (preferredPath.found && preferredPath.owners.size > 0) {
return preferredPath
}

return this.findRelaxedBlockerPath(forbiddenOwnerRouteIds)
}

protected getRelaxedSearchExpansionLimit(): number {
let incidentHopCount = 0
for (const incidentRegions of this.topology.incidentPortRegion) {
Expand Down
34 changes: 34 additions & 0 deletions tests/outside-in-partial-rip-tiny-hypergraph-solver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import {
import type { PortId, RegionId, RouteId } from "lib/types"

class TestOutsideInPartialRipSolver extends OutsideInPartialRipTinyHyperGraphSolver {
preferredPreservedRouteIds = new Set<RouteId>()

protected override getRouteIdsPreferredForPreservation() {
return this.preferredPreservedRouteIds
}

prepare(hotRegionIds: RegionId[], regionCosts: Float64Array): boolean {
return this.preparePartialRip(hotRegionIds, regionCosts)
}
Expand Down Expand Up @@ -86,6 +92,34 @@ test("partial rip preserves both outside route ends", () => {
expect(solver.stats.retainedPartialRipSegmentCount).toBe(3)
})

test("partial rip leaves preferred routes unchanged", () => {
const solver = createLinearSolver(24, {}, 2)
solver.preferredPreservedRouteIds.add(0)
for (let regionId = 1; regionId <= 4; regionId++) {
const [routeZeroSegment] = solver.state.regionSegments[regionId]!
solver.state.regionSegments[regionId]!.push([
1,
routeZeroSegment![1],
routeZeroSegment![2],
])
}
const regionCosts = new Float64Array(6)
regionCosts[3] = 1

expect(solver.prepare([3], regionCosts)).toBe(true)
expect(solver.state.unroutedRoutes).toEqual([1])
expect(
solver.state.regionSegments
.flat()
.filter(([routeId]) => routeId === 0),
).toEqual([
[0, 0, 1],
[0, 1, 2],
[0, 2, 3],
[0, 3, 4],
])
})

test("a near-target initial solution selects the larger quality window", () => {
const nearTargetSolver = createLinearSolver()
nearTargetSolver.PARTIAL_RIP_QUALITY_MAX_DISTANCE = 8
Expand Down
78 changes: 78 additions & 0 deletions tests/selective-rerip-tiny-hyper-graph-solver.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { expect, test } from "bun:test"
import {
orderRoutesAfterSelectiveRerip,
SelectiveReripTinyHyperGraphSolver,
selectOwnerRouteIdsToRip,
} from "lib/selective-rerip-tiny-hyper-graph-solver"

Expand Down Expand Up @@ -32,3 +33,80 @@ test("keeps pending routes ahead of newly ripped routes", () => {
}),
).toEqual([7, 3, 8, 9, 4, 2])
})

test("prefers another blocker owner over a route marked for preservation", () => {
const searches: number[][] = []
class SolverWithPreferredPreservedRoute extends SelectiveReripTinyHyperGraphSolver {
protected override getRouteIdsPreferredForPreservation() {
return new Set([1])
}

protected override findRelaxedBlockerPath(
forbiddenOwnerRouteIds: ReadonlySet<number> = new Set(),
) {
searches.push([...forbiddenOwnerRouteIds])
return {
found: true as const,
states: [],
hops: [],
owners: new Set(forbiddenOwnerRouteIds.has(1) ? [2] : [1]),
distance: 1,
expandedLabelCount: 1,
}
}

findPreferredBlockerPath() {
return this.findRelaxedBlockerPathPreferringPreservedRoutes()
}
}
const solver = Object.create(
SolverWithPreferredPreservedRoute.prototype,
) as SolverWithPreferredPreservedRoute

const result = solver.findPreferredBlockerPath()

expect(searches).toEqual([[1]])
expect(result.found && [...result.owners]).toEqual([2])
})

test("rerips a preserved route when no other blocker path exists", () => {
const searches: number[][] = []
class SolverWithUnavoidablePreservedRoute extends SelectiveReripTinyHyperGraphSolver {
protected override getRouteIdsPreferredForPreservation() {
return new Set([1])
}

protected override findRelaxedBlockerPath(
forbiddenOwnerRouteIds: ReadonlySet<number> = new Set(),
) {
searches.push([...forbiddenOwnerRouteIds])
if (forbiddenOwnerRouteIds.has(1)) {
return {
found: false as const,
reason: "no_path" as const,
expandedLabelCount: 1,
}
}
return {
found: true as const,
states: [],
hops: [],
owners: new Set([1]),
distance: 1,
expandedLabelCount: 1,
}
}

findPreferredBlockerPath() {
return this.findRelaxedBlockerPathPreferringPreservedRoutes()
}
}
const solver = Object.create(
SolverWithUnavoidablePreservedRoute.prototype,
) as SolverWithUnavoidablePreservedRoute

const result = solver.findPreferredBlockerPath()

expect(searches).toEqual([[1], []])
expect(result.found && [...result.owners]).toEqual([1])
})
Loading