diff --git a/lib/DuplicateCongestedPortSolver.ts b/lib/DuplicateCongestedPortSolver.ts index cb6ced6..e9f0f08 100644 --- a/lib/DuplicateCongestedPortSolver.ts +++ b/lib/DuplicateCongestedPortSolver.ts @@ -318,7 +318,6 @@ export class DuplicateCongestedPortSolver extends BaseSolver { return { RIP_THRESHOLD_RAMP_ATTEMPTS: 0, STATIC_REACHABILITY_PRECHECK: false, - USE_LAZY_ROUTE_HEURISTIC: true, ...this.options.routeSolveOptions, } } @@ -327,12 +326,6 @@ export class DuplicateCongestedPortSolver extends BaseSolver { const { topology, problem } = loadSerializedHyperGraph( this.serializedHyperGraph, ) - const serializedPortIds = Array.from( - { length: topology.portCount }, - (_, portId) => getSerializedPortId(topology, portId), - ) - topology.portMetadata = undefined - topology.regionMetadata = undefined const portUseCounts = new Map() for (let routeId = 0; routeId < problem.routeCount; routeId++) { @@ -353,7 +346,7 @@ export class DuplicateCongestedPortSolver extends BaseSolver { } for (const portId of getUsedPortIdsForSolvedRoute(routeSolver)) { - const serializedPortId = serializedPortIds[portId] ?? `port-${portId}` + const serializedPortId = getSerializedPortId(topology, portId) portUseCounts.set( serializedPortId, (portUseCounts.get(serializedPortId) ?? 0) + 1, @@ -374,48 +367,27 @@ export class DuplicateCongestedPortSolver extends BaseSolver { const { solvedRoutes: _solvedRoutes, ...restHyperGraph } = this.serializedHyperGraph - const regions = [...this.serializedHyperGraph.regions] - const ports = [...this.serializedHyperGraph.ports] - const regionById = new Map( - this.serializedHyperGraph.regions.map((region) => [ - region.regionId, - region, - ]), + const regions: SerializedRegion[] = this.serializedHyperGraph.regions.map( + (region) => ({ + ...region, + pointIds: [...region.pointIds], + d: cloneSerializableValue(region.d), + }), ) - const regionIndexById = new Map( - this.serializedHyperGraph.regions.map((region, regionIndex) => [ - region.regionId, - regionIndex, - ]), + const ports: SerializedPort[] = this.serializedHyperGraph.ports.map( + (port) => ({ + ...port, + d: cloneSerializableValue(port.d), + }), ) - const sourcePortById = new Map( - this.serializedHyperGraph.ports.map( - (port) => [port.portId, port] as const, - ), + const regionById = new Map( + regions.map((region) => [region.regionId, region]), ) - const usedPortIds = new Set( - this.serializedHyperGraph.ports.map((port) => port.portId), + const sourcePortById = new Map( + ports.map((port) => [port.portId, port] as const), ) + const usedPortIds = new Set(ports.map((port) => port.portId)) const duplicatedPorts: DuplicatedPortSummary[] = [] - const mutableRegionIds = new Set() - - const getMutableRegion = (regionId: string) => { - const regionIndex = regionIndexById.get(regionId) - if (regionIndex === undefined) return undefined - - const region = regions[regionIndex] - if (!region) return undefined - - if (!mutableRegionIds.has(regionId)) { - regions[regionIndex] = { - ...region, - pointIds: [...region.pointIds], - } - mutableRegionIds.add(regionId) - } - - return regions[regionIndex] - } for (const [sourcePortId, useCount] of [...portUseCounts.entries()].sort( ([leftPortId], [rightPortId]) => leftPortId.localeCompare(rightPortId), @@ -470,7 +442,7 @@ export class DuplicateCongestedPortSolver extends BaseSolver { } for (const regionId of [sourcePort.region1Id, sourcePort.region2Id]) { - const region = getMutableRegion(regionId) + const region = regionById.get(regionId) if (!region) continue insertDuplicatePortIdsAfterSource( region.pointIds, @@ -498,7 +470,7 @@ export class DuplicateCongestedPortSolver extends BaseSolver { connections: this.serializedHyperGraph.connections === undefined ? undefined - : [...this.serializedHyperGraph.connections], + : cloneSerializableValue(this.serializedHyperGraph.connections), } } diff --git a/lib/compat/convertToSerializedHyperGraph.ts b/lib/compat/convertToSerializedHyperGraph.ts index 78b70e8..70e623c 100644 --- a/lib/compat/convertToSerializedHyperGraph.ts +++ b/lib/compat/convertToSerializedHyperGraph.ts @@ -20,20 +20,8 @@ interface RouteSegment { const isRecord = (value: unknown): value is Record => typeof value === "object" && value !== null -const copyEnumerableRecord = ( - value: Record, -): Record => { - const clone: Record = {} - - for (const key in value) { - clone[key] = value[key] - } - - return clone -} - const toObjectRecord = (value: unknown): Record => { - if (isRecord(value)) return copyEnumerableRecord(value) + if (isRecord(value)) return { ...value } if (value === undefined) return {} return { value } } diff --git a/lib/compat/loadSerializedHyperGraph.ts b/lib/compat/loadSerializedHyperGraph.ts index 7ff5f58..6dc4eba 100644 --- a/lib/compat/loadSerializedHyperGraph.ts +++ b/lib/compat/loadSerializedHyperGraph.ts @@ -88,7 +88,7 @@ const addSerializedRegionIdToMetadata = ( ) => { const metadata = region.d && typeof region.d === "object" && !Array.isArray(region.d) - ? Object.create(region.d) + ? { ...region.d } : { value: region.d } metadata.layer = layer @@ -109,7 +109,7 @@ const addSerializedPortIdToMetadata = ( ) => { const metadata = port.d && typeof port.d === "object" && !Array.isArray(port.d) - ? Object.create(port.d) + ? { ...port.d } : { value: port.d } metadata.layer = layer diff --git a/lib/core.ts b/lib/core.ts index 94c259a..d334dc5 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -344,10 +344,6 @@ interface SegmentGeometryScratch { export class TinyHyperGraphSolver extends BaseSolver { state: TinyHyperGraphWorkingState - /** Slots per port in the compact hop-cost arrays (max incident regions across ports) */ - protected hopSlotStride: number - /** Fallback storage for hops whose region is not incident to the port (encoded as negative hop ids) */ - private hopOverflowBestCost?: Map private _problemSetup?: TinyHyperGraphProblemSetup protected routeAttemptCountByRouteId: Uint32Array protected routeSuccessCountByRouteId: Uint32Array @@ -387,14 +383,6 @@ export class TinyHyperGraphSolver extends BaseSolver { ) { super() applyTinyHyperGraphSolverOptions(this, options) - let hopSlotStride = 1 - for (let portId = 0; portId < topology.portCount; portId++) { - const incidentCount = topology.incidentPortRegion[portId]?.length ?? 0 - if (incidentCount > hopSlotStride) { - hopSlotStride = incidentCount - } - } - this.hopSlotStride = hopSlotStride this.state = { portAssignment: new Int32Array(topology.portCount).fill(-1), regionSegments: Array.from({ length: topology.regionCount }, () => []), @@ -408,10 +396,10 @@ export class TinyHyperGraphSolver extends BaseSolver { candidateQueue: new MinHeap([], compareCandidatesByF), candidateBestCostByHopId: this.USE_SPARSE_CANDIDATE_STORAGE ? new Map() - : new Float64Array(topology.portCount * hopSlotStride), + : new Float64Array(topology.portCount * topology.regionCount), candidateBestCostGenerationByHopId: this.USE_SPARSE_CANDIDATE_STORAGE ? new Map() - : new Uint32Array(topology.portCount * hopSlotStride), + : new Uint32Array(topology.portCount * topology.regionCount), candidateBestCostGeneration: 1, goalPortId: -1, ripCount: 0, @@ -621,8 +609,6 @@ export class TinyHyperGraphSolver extends BaseSolver { resetCandidateBestCosts() { const { state } = this - this.hopOverflowBestCost?.clear() - if (state.candidateBestCostGeneration === 0xffffffff) { if (state.candidateBestCostByHopId instanceof Map) { state.candidateBestCostByHopId.clear() @@ -640,10 +626,6 @@ export class TinyHyperGraphSolver extends BaseSolver { } getCandidateBestCost(hopId: HopId) { - if (hopId < 0) { - return this.hopOverflowBestCost?.get(hopId) ?? Number.POSITIVE_INFINITY - } - const { state } = this const bestCostGeneration = state.candidateBestCostGenerationByHopId @@ -657,11 +639,6 @@ export class TinyHyperGraphSolver extends BaseSolver { } setCandidateBestCost(hopId: HopId, bestCost: number) { - if (hopId < 0) { - ;(this.hopOverflowBestCost ??= new Map()).set(hopId, bestCost) - return - } - const { state } = this if (state.candidateBestCostGenerationByHopId instanceof Map) { @@ -681,29 +658,8 @@ export class TinyHyperGraphSolver extends BaseSolver { } } - /** - * Drop search-only state that is no longer needed once the solver has - * finished. Everything released here is rebuilt on demand: `problemSetup` - * is a lazy getter, and the candidate queue/overflow map are reset at the - * start of every route attempt. - */ - releaseTransientSearchState() { - this._problemSetup = undefined - this.hopOverflowBestCost = undefined - this.bestSolvedStateSnapshot = undefined - this.state.candidateQueue.clear() - } - getHopId(portId: PortId, nextRegionId: RegionId): HopId { - // Hops are keyed by (port, incident-region slot) so the dense cost arrays - // stay O(ports * max-incidence) instead of O(ports * regions). - const incidentRegions = this.topology.incidentPortRegion[portId] - const slot = incidentRegions ? incidentRegions.indexOf(nextRegionId) : -1 - if (slot === -1) { - // Non-incident hop: encode as a negative id routed to the overflow map. - return -(portId * this.topology.regionCount + nextRegionId) - 1 - } - return portId * this.hopSlotStride + slot + return portId * this.topology.regionCount + nextRegionId } getStartingNextRegionId( diff --git a/lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts b/lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts index 2f1c648..9ccd4f9 100644 --- a/lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts +++ b/lib/section-solver/TinyHyperGraphSectionPipelineSolver.ts @@ -11,11 +11,7 @@ import type { import { TinyHyperGraphSolver } from "../core" import type { RegionId } from "../types" import type { TinyHyperGraphSectionSolverOptions } from "./index" -import { - createSolvedSolverFromSolution, - getActiveSectionRouteIds, - TinyHyperGraphSectionSolver, -} from "./index" +import { getActiveSectionRouteIds, TinyHyperGraphSectionSolver } from "./index" import { createSectionMaskCandidatesForHotRegions, DEFAULT_TINY_HYPERGRAPH_SECTION_CANDIDATE_FAMILIES, @@ -76,24 +72,17 @@ const getMaxRegionCost = (solver: TinyHyperGraphSolver) => const getSerializedOutputMaxRegionCost = ( serializedHyperGraph: SerializedHyperGraph, - solveGraphOptions?: TinyHyperGraphSolverOptions, sectionSolverOptions?: TinyHyperGraphSectionSolverOptions, ) => { const replay = loadSerializedHyperGraph(serializedHyperGraph) - const replayedSolver = createSolvedSolverFromSolution( + const replayedSolver = new TinyHyperGraphSectionSolver( replay.topology, replay.problem, replay.solution, - { - ...DEFAULT_SOLVE_GRAPH_OPTIONS, - ...solveGraphOptions, - ...(sectionSolverOptions?.minViaPadDiameter === undefined - ? {} - : { minViaPadDiameter: sectionSolverOptions.minViaPadDiameter }), - }, + sectionSolverOptions, ) - return getMaxRegionCost(replayedSolver) + return getMaxRegionCost(replayedSolver.baselineSolver) } const createPortSectionMaskForRegionIds = ( @@ -268,12 +257,6 @@ const findBestAutomaticSectionMask = ( const candidateReplayScoreStartTime = performance.now() const replayedFinalMaxRegionCost = getSerializedOutputMaxRegionCost( sectionSolver.getOutput(), - { - ...DEFAULT_SOLVE_GRAPH_OPTIONS, - ...(sectionSolverOptions.minViaPadDiameter === undefined - ? {} - : { minViaPadDiameter: sectionSolverOptions.minViaPadDiameter }), - }, sectionSolverOptions, ) candidateReplayScoreMs += @@ -340,14 +323,6 @@ export class TinyHyperGraphSectionPipelineSolver extends BasePipelineSolver[] = [ { solverName: "solveGraph", @@ -405,23 +372,12 @@ export class TinyHyperGraphSectionPipelineSolver extends BasePipelineSolver }, - onSolved: (instance: TinyHyperGraphSectionPipelineSolver) => { - instance - .getSolver("solveGraph") - ?.releaseTransientSearchState() - }, }, { solverName: "optimizeSection", solverClass: TinyHyperGraphSectionSolver, getConstructorParams: (instance: TinyHyperGraphSectionPipelineSolver) => instance.getSectionStageParams(), - onSolved: (instance: TinyHyperGraphSectionPipelineSolver) => { - instance - .getSolver("optimizeSection") - ?.releaseTransientSearchState() - instance.cachedSectionStageParams = undefined - }, }, ] @@ -431,10 +387,6 @@ export class TinyHyperGraphSectionPipelineSolver extends BasePipelineSolver("solveGraph") @@ -518,56 +470,7 @@ export class TinyHyperGraphSectionPipelineSolver extends BasePipelineSolver("solveGraph") - - if (!solveGraphOutput) { - return false - } - - const [, problem] = this.getSectionStageParams() - let sectionMaskPortCount = 0 - for (const value of problem.portSectionMask) { - if (value === 1) { - sectionMaskPortCount += 1 - } - } - - if (sectionMaskPortCount !== 0) { - return false - } - - this.pipelineOutputs.optimizeSection = solveGraphOutput - this.stats = { - ...this.stats, - activeRouteCount: 0, - optimized: false, - sectionOptimizationSkipped: true, - sectionOptimizationReason: "empty-section-mask", - } - this.currentPipelineStageIndex++ - // The section stage will never run, so the topology/problem/solution view - // loaded for the mask check is dead weight from here on. - this.cachedSectionStageParams = undefined - return true + return [topology, problem, solution, sectionSolverOptions] } getInitialVisualizationSolver() { diff --git a/lib/section-solver/index.ts b/lib/section-solver/index.ts index 55f9e1e..e66f4a1 100644 --- a/lib/section-solver/index.ts +++ b/lib/section-solver/index.ts @@ -399,7 +399,7 @@ const createSolvedSolverFromRegionSegments = ( return solver } -export const createSolvedSolverFromSolution = ( +const createSolvedSolverFromSolution = ( topology: TinyHyperGraphTopology, problem: TinyHyperGraphProblem, solution: TinyHyperGraphSolution, @@ -1067,23 +1067,6 @@ export class TinyHyperGraphSectionSolver extends BaseSolver { return this.optimizedSolver } - /** - * Drop the section search solver (including its state snapshots) and the - * transient search state of the retained solvers once a solved output - * exists. `visualize()` and `getOutput()` only need `optimizedSolver` / - * `baselineSolver` route state, which is preserved. - */ - releaseTransientSearchState() { - if (!this.solved || this.failed) { - return - } - - this.sectionSolver = undefined - this.activeSubSolver = null - this.baselineSolver.releaseTransientSearchState() - this.optimizedSolver?.releaseTransientSearchState() - } - override visualize(): GraphicsObject { if (this.optimizedSolver) { return visualizeTinyGraph(this.optimizedSolver, { diff --git a/tests/compat/layer-label.test.ts b/tests/compat/layer-label.test.ts index ccb2e55..a01d134 100644 --- a/tests/compat/layer-label.test.ts +++ b/tests/compat/layer-label.test.ts @@ -122,32 +122,3 @@ test("serialized and poly outputs preserve z layer labels for regions and ports" "z1", ]) }) - -test("serialized output preserves custom region and port metadata fields", () => { - const graph = createLayerFixture() - graph.regions[0]!.d = { - ...graph.regions[0]!.d, - customRegionFlag: "keep-me", - customRegionNested: { mode: "nested" }, - } - graph.ports[0]!.d = { - ...graph.ports[0]!.d, - customPortFlag: "keep-me-too", - customPortNested: { mode: "nested" }, - } - - const { topology, problem } = loadSerializedHyperGraph(graph) - const solver = new TinyHyperGraphSolver(topology, problem) - - solver.solve() - - expect(solver.solved).toBe(true) - expect(solver.failed).toBe(false) - - const output = solver.getOutput() - - expect(output.regions[0]?.d?.customRegionFlag).toBe("keep-me") - expect(output.regions[0]?.d?.customRegionNested).toEqual({ mode: "nested" }) - expect(output.ports[0]?.d?.customPortFlag).toBe("keep-me-too") - expect(output.ports[0]?.d?.customPortNested).toEqual({ mode: "nested" }) -})