From bb678455326a689e142a5e6556a736b55b4a0a12 Mon Sep 17 00:00:00 2001 From: Chronolapse411 Date: Tue, 31 Mar 2026 22:45:08 -0400 Subject: [PATCH 1/2] feature: add getOutput to SchematicTracePipelineSolver --- .../SchematicTracePipelineSolver.ts | 747 +++++++++--------- 1 file changed, 387 insertions(+), 360 deletions(-) diff --git a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts index c9d5a995..166b5141 100644 --- a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +++ b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts @@ -1,360 +1,387 @@ -/** - * Pipeline solver that runs a series of solvers to find the best schematic layout. - * Coordinates the entire layout process from chip partitioning through final packing. - */ - -import type { GraphicsObject } from "graphics-debug" -import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver" -import type { InputProblem } from "lib/types/InputProblem" -import { MspConnectionPairSolver } from "../MspConnectionPairSolver/MspConnectionPairSolver" -import { - SchematicTraceLinesSolver, - type SolvedTracePath, -} from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver" -import { TraceOverlapShiftSolver } from "../TraceOverlapShiftSolver/TraceOverlapShiftSolver" -import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver" -import { visualizeInputProblem } from "./visualizeInputProblem" -import { TraceLabelOverlapAvoidanceSolver } from "../TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver" -import { correctPinsInsideChips } from "./correctPinsInsideChip" -import { expandChipsToFitPins } from "./expandChipsToFitPins" -import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePairSolver" -import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver" -import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver" - -type PipelineStep BaseSolver> = { - solverName: string - solverClass: T - getConstructorParams: ( - instance: SchematicTracePipelineSolver, - ) => ConstructorParameters - onSolved?: (instance: SchematicTracePipelineSolver) => void - shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean -} - -function definePipelineStep< - T extends new ( - ...args: any[] - ) => BaseSolver, - const P extends ConstructorParameters, ->( - solverName: keyof SchematicTracePipelineSolver, - solverClass: T, - getConstructorParams: (instance: SchematicTracePipelineSolver) => P, - opts: { - onSolved?: (instance: SchematicTracePipelineSolver) => void - shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean - } = {}, -): PipelineStep { - return { - solverName, - solverClass, - getConstructorParams, - onSolved: opts.onSolved, - shouldSkip: opts.shouldSkip, - } -} - -export interface SchematicTracePipelineSolverParams { - inputProblem: InputProblem - allowLongDistanceTraces?: boolean -} - -export class SchematicTracePipelineSolver extends BaseSolver { - mspConnectionPairSolver?: MspConnectionPairSolver - // guidelinesSolver?: GuidelinesSolver - schematicTraceLinesSolver?: SchematicTraceLinesSolver - longDistancePairSolver?: LongDistancePairSolver - traceOverlapShiftSolver?: TraceOverlapShiftSolver - netLabelPlacementSolver?: NetLabelPlacementSolver - labelMergingSolver?: MergedNetLabelObstacleSolver - traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver - traceCleanupSolver?: TraceCleanupSolver - - startTimeOfPhase: Record - endTimeOfPhase: Record - timeSpentOnPhase: Record - firstIterationOfPhase: Record - - inputProblem: InputProblem - - pipelineDef = [ - definePipelineStep( - "mspConnectionPairSolver", - MspConnectionPairSolver, - () => [{ inputProblem: this.inputProblem }], - { - onSolved: (mspSolver) => {}, - }, - ), - // definePipelineStep( - // "guidelinesSolver", - // GuidelinesSolver, - // () => [ - // { - // inputProblem: this.inputProblem, - // }, - // ], - // { - // onSolved: (guidelinesSolver) => {}, - // }, - // ), - definePipelineStep( - "schematicTraceLinesSolver", - SchematicTraceLinesSolver, - () => [ - { - mspConnectionPairs: this.mspConnectionPairSolver!.mspConnectionPairs, - dcConnMap: this.mspConnectionPairSolver!.dcConnMap, - globalConnMap: this.mspConnectionPairSolver!.globalConnMap, - inputProblem: this.inputProblem, - // guidelines: this.guidelinesSolver!.guidelines, - chipMap: this.mspConnectionPairSolver!.chipMap, - }, - ], - ), - definePipelineStep( - "longDistancePairSolver", - LongDistancePairSolver, - (instance) => [ - { - inputProblem: instance.inputProblem, - primaryMspConnectionPairs: - instance.mspConnectionPairSolver!.mspConnectionPairs, - alreadySolvedTraces: - instance.schematicTraceLinesSolver!.solvedTracePaths, - }, - ], - { - onSolved: (schematicTraceLinesSolver) => {}, - }, - ), - definePipelineStep( - "traceOverlapShiftSolver", - TraceOverlapShiftSolver, - () => [ - { - inputProblem: this.inputProblem, - inputTracePaths: - this.longDistancePairSolver?.getOutput().allTracesMerged!, - globalConnMap: this.mspConnectionPairSolver!.globalConnMap, - }, - ], - { - onSolved: (_solver) => {}, - }, - ), - definePipelineStep( - "netLabelPlacementSolver", - NetLabelPlacementSolver, - () => [ - { - inputProblem: this.inputProblem, - inputTraceMap: - this.traceOverlapShiftSolver?.correctedTraceMap ?? - Object.fromEntries( - this.longDistancePairSolver!.getOutput().allTracesMerged.map( - (p) => [p.mspPairId, p], - ), - ), - }, - ], - { - onSolved: (_solver) => { - // TODO - }, - }, - ), - definePipelineStep( - "traceLabelOverlapAvoidanceSolver", - TraceLabelOverlapAvoidanceSolver, - (instance) => { - const traceMap = - instance.traceOverlapShiftSolver?.correctedTraceMap ?? - Object.fromEntries( - instance - .longDistancePairSolver!.getOutput() - .allTracesMerged.map((p) => [p.mspPairId, p]), - ) - const traces = Object.values(traceMap) - const netLabelPlacements = - instance.netLabelPlacementSolver!.netLabelPlacements - - return [ - { - inputProblem: instance.inputProblem, - traces, - netLabelPlacements, - }, - ] - }, - ), - definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => { - const prevSolverOutput = - instance.traceLabelOverlapAvoidanceSolver!.getOutput() - const traces = prevSolverOutput.traces - - const labelMergingOutput = - instance.traceLabelOverlapAvoidanceSolver!.labelMergingSolver!.getOutput() - - return [ - { - inputProblem: instance.inputProblem, - allTraces: traces, - allLabelPlacements: labelMergingOutput.netLabelPlacements, - mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap, - paddingBuffer: 0.1, - }, - ] - }), - definePipelineStep( - "netLabelPlacementSolver", - NetLabelPlacementSolver, - (instance) => { - const traces = - instance.traceCleanupSolver?.getOutput().traces ?? - instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces - - return [ - { - inputProblem: instance.inputProblem, - inputTraceMap: Object.fromEntries( - traces.map((trace: SolvedTracePath) => [trace.mspPairId, trace]), - ), - }, - ] - }, - ), - ] - - constructor(inputProblem: InputProblem) { - super() - this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem) - this.MAX_ITERATIONS = 1e6 - this.startTimeOfPhase = {} - this.endTimeOfPhase = {} - this.timeSpentOnPhase = {} - this.firstIterationOfPhase = {} - } - - override getConstructorParams(): ConstructorParameters< - typeof SchematicTracePipelineSolver - >[0] { - return this.inputProblem - } - - currentPipelineStepIndex = 0 - - private cloneAndCorrectInputProblem(original: InputProblem): InputProblem { - const cloned: InputProblem = structuredClone({ - ...original, - _chipObstacleSpatialIndex: undefined, - }) - - // First, expand chips so existing pin coordinates sit on or within their edges without shrinking. - expandChipsToFitPins(cloned) - // Then, for any remaining pins that are still inside due to mixed extremes, snap them to the nearest edge. - correctPinsInsideChips(cloned) - - return cloned - } - - override _step() { - const pipelineStepDef = this.pipelineDef[this.currentPipelineStepIndex] - if (!pipelineStepDef) { - this.solved = true - return - } - - if (this.activeSubSolver) { - this.activeSubSolver.step() - if (this.activeSubSolver.solved) { - this.endTimeOfPhase[pipelineStepDef.solverName] = performance.now() - this.timeSpentOnPhase[pipelineStepDef.solverName] = - this.endTimeOfPhase[pipelineStepDef.solverName]! - - this.startTimeOfPhase[pipelineStepDef.solverName]! - pipelineStepDef.onSolved?.(this) - this.activeSubSolver = null - this.currentPipelineStepIndex++ - } else if (this.activeSubSolver.failed) { - this.error = this.activeSubSolver?.error - this.failed = true - this.activeSubSolver = null - } - return - } - - const constructorParams = pipelineStepDef.getConstructorParams(this) - // @ts-ignore - this.activeSubSolver = new pipelineStepDef.solverClass(...constructorParams) - ;(this as any)[pipelineStepDef.solverName] = this.activeSubSolver - this.timeSpentOnPhase[pipelineStepDef.solverName] = 0 - this.startTimeOfPhase[pipelineStepDef.solverName] = performance.now() - this.firstIterationOfPhase[pipelineStepDef.solverName] = this.iterations - } - - solveUntilPhase(phase: string) { - while (this.getCurrentPhase().toLowerCase() !== phase.toLowerCase()) { - this.step() - } - } - - getCurrentPhase(): string { - return this.pipelineDef[this.currentPipelineStepIndex]?.solverName ?? "none" - } - - override visualize(): GraphicsObject { - if (!this.solved && this.activeSubSolver) - return this.activeSubSolver.visualize() - - const visualizations = [ - visualizeInputProblem(this.inputProblem), - ...(this.pipelineDef - .map((p) => (this as any)[p.solverName]?.visualize()) - .filter(Boolean) - .map((viz, stepIndex) => { - for (const rect of viz!.rects ?? []) { - rect.step = stepIndex - } - for (const point of viz!.points ?? []) { - point.step = stepIndex - } - for (const circle of viz!.circles ?? []) { - circle.step = stepIndex - } - for (const text of viz!.texts ?? []) { - text.step = stepIndex - } - for (const line of viz!.lines ?? []) { - line.step = stepIndex - } - return viz - }) as GraphicsObject[]), - ] - - if (visualizations.length === 1) { - return visualizations[0]! - } - - // Simple combination of visualizations - const finalGraphics = { - points: visualizations.flatMap((v) => v.points || []), - rects: visualizations.flatMap((v) => v.rects || []), - lines: visualizations.flatMap((v) => v.lines || []), - circles: visualizations.flatMap((v) => v.circles || []), - texts: visualizations.flatMap((v) => v.texts || []), - } - return finalGraphics - } - - /** - * A lightweight version of the visualize method that can be used to stream - * progress - */ - override preview(): GraphicsObject { - if (this.activeSubSolver) { - return this.activeSubSolver.preview() - } - - return super.preview() - } -} +/** + * Pipeline solver that runs a series of solvers to find the best schematic layout. + * Coordinates the entire layout process from chip partitioning through final packing. + */ + +import type { GraphicsObject } from "graphics-debug" +import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver" +import type { InputProblem } from "lib/types/InputProblem" +import { MspConnectionPairSolver } from "../MspConnectionPairSolver/MspConnectionPairSolver" +import { + SchematicTraceLinesSolver, + type SolvedTracePath, +} from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver" +import { TraceOverlapShiftSolver } from "../TraceOverlapShiftSolver/TraceOverlapShiftSolver" +import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver" +import { visualizeInputProblem } from "./visualizeInputProblem" +import { TraceLabelOverlapAvoidanceSolver } from "../TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver" +import { correctPinsInsideChips } from "./correctPinsInsideChip" +import { expandChipsToFitPins } from "./expandChipsToFitPins" +import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePairSolver" +import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver" +import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver" + +type PipelineStep BaseSolver> = { + solverName: string + solverClass: T + getConstructorParams: ( + instance: SchematicTracePipelineSolver, + ) => ConstructorParameters + onSolved?: (instance: SchematicTracePipelineSolver) => void + shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean +} + +function definePipelineStep< + T extends new ( + ...args: any[] + ) => BaseSolver, + const P extends ConstructorParameters, +>( + solverName: keyof SchematicTracePipelineSolver, + solverClass: T, + getConstructorParams: (instance: SchematicTracePipelineSolver) => P, + opts: { + onSolved?: (instance: SchematicTracePipelineSolver) => void + shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean + } = {}, +): PipelineStep { + return { + solverName, + solverClass, + getConstructorParams, + onSolved: opts.onSolved, + shouldSkip: opts.shouldSkip, + } +} + +export interface SchematicTracePipelineSolverParams { + inputProblem: InputProblem + allowLongDistanceTraces?: boolean +} + +export class SchematicTracePipelineSolver extends BaseSolver { + mspConnectionPairSolver?: MspConnectionPairSolver + // guidelinesSolver?: GuidelinesSolver + schematicTraceLinesSolver?: SchematicTraceLinesSolver + longDistancePairSolver?: LongDistancePairSolver + traceOverlapShiftSolver?: TraceOverlapShiftSolver + netLabelPlacementSolver?: NetLabelPlacementSolver + labelMergingSolver?: MergedNetLabelObstacleSolver + traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver + traceCleanupSolver?: TraceCleanupSolver + + startTimeOfPhase: Record + endTimeOfPhase: Record + timeSpentOnPhase: Record + firstIterationOfPhase: Record + + inputProblem: InputProblem + + pipelineDef = [ + definePipelineStep( + "mspConnectionPairSolver", + MspConnectionPairSolver, + () => [{ inputProblem: this.inputProblem }], + { + onSolved: (mspSolver) => {}, + }, + ), + // definePipelineStep( + // "guidelinesSolver", + // GuidelinesSolver, + // () => [ + // { + // inputProblem: this.inputProblem, + // }, + // ], + // { + // onSolved: (guidelinesSolver) => {}, + // }, + // ), + definePipelineStep( + "schematicTraceLinesSolver", + SchematicTraceLinesSolver, + () => [ + { + mspConnectionPairs: this.mspConnectionPairSolver!.mspConnectionPairs, + dcConnMap: this.mspConnectionPairSolver!.dcConnMap, + globalConnMap: this.mspConnectionPairSolver!.globalConnMap, + inputProblem: this.inputProblem, + // guidelines: this.guidelinesSolver!.guidelines, + chipMap: this.mspConnectionPairSolver!.chipMap, + }, + ], + ), + definePipelineStep( + "longDistancePairSolver", + LongDistancePairSolver, + (instance) => [ + { + inputProblem: instance.inputProblem, + primaryMspConnectionPairs: + instance.mspConnectionPairSolver!.mspConnectionPairs, + alreadySolvedTraces: + instance.schematicTraceLinesSolver!.solvedTracePaths, + }, + ], + { + onSolved: (schematicTraceLinesSolver) => {}, + }, + ), + definePipelineStep( + "traceOverlapShiftSolver", + TraceOverlapShiftSolver, + () => [ + { + inputProblem: this.inputProblem, + inputTracePaths: + this.longDistancePairSolver?.getOutput().allTracesMerged!, + globalConnMap: this.mspConnectionPairSolver!.globalConnMap, + }, + ], + { + onSolved: (_solver) => {}, + }, + ), + definePipelineStep( + "netLabelPlacementSolver", + NetLabelPlacementSolver, + () => [ + { + inputProblem: this.inputProblem, + inputTraceMap: + this.traceOverlapShiftSolver?.correctedTraceMap ?? + Object.fromEntries( + this.longDistancePairSolver!.getOutput().allTracesMerged.map( + (p) => [p.mspPairId, p], + ), + ), + }, + ], + { + onSolved: (_solver) => { + // TODO + }, + }, + ), + definePipelineStep( + "traceLabelOverlapAvoidanceSolver", + TraceLabelOverlapAvoidanceSolver, + (instance) => { + const traceMap = + instance.traceOverlapShiftSolver?.correctedTraceMap ?? + Object.fromEntries( + instance + .longDistancePairSolver!.getOutput() + .allTracesMerged.map((p) => [p.mspPairId, p]), + ) + const traces = Object.values(traceMap) + const netLabelPlacements = + instance.netLabelPlacementSolver!.netLabelPlacements + + return [ + { + inputProblem: instance.inputProblem, + traces, + netLabelPlacements, + }, + ] + }, + ), + definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => { + const prevSolverOutput = + instance.traceLabelOverlapAvoidanceSolver!.getOutput() + const traces = prevSolverOutput.traces + + const labelMergingOutput = + instance.traceLabelOverlapAvoidanceSolver!.labelMergingSolver!.getOutput() + + return [ + { + inputProblem: instance.inputProblem, + allTraces: traces, + allLabelPlacements: labelMergingOutput.netLabelPlacements, + mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap, + paddingBuffer: 0.1, + }, + ] + }), + definePipelineStep( + "netLabelPlacementSolver", + NetLabelPlacementSolver, + (instance) => { + const traces = + instance.traceCleanupSolver?.getOutput().traces ?? + instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces + + return [ + { + inputProblem: instance.inputProblem, + inputTraceMap: Object.fromEntries( + traces.map((trace: SolvedTracePath) => [trace.mspPairId, trace]), + ), + }, + ] + }, + ), + ] + + constructor(inputProblem: InputProblem) { + super() + this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem) + this.MAX_ITERATIONS = 1e6 + this.startTimeOfPhase = {} + this.endTimeOfPhase = {} + this.timeSpentOnPhase = {} + this.firstIterationOfPhase = {} + } + + getOutput(): { + traces: SolvedTracePath[] + netLabelPlacements: import("../NetLabelPlacementSolver/NetLabelPlacementSolver").NetLabelPlacement[] + } { + let traces: SolvedTracePath[] = [] + + if (this.traceCleanupSolver) { + traces = this.traceCleanupSolver.getOutput().traces + } else if (this.traceLabelOverlapAvoidanceSolver) { + traces = this.traceLabelOverlapAvoidanceSolver.getOutput().traces + } else if (this.traceOverlapShiftSolver?.correctedTraceMap) { + traces = Object.values(this.traceOverlapShiftSolver.correctedTraceMap) + } else if (this.longDistancePairSolver) { + traces = this.longDistancePairSolver.getOutput().allTracesMerged + } else if (this.schematicTraceLinesSolver) { + traces = this.schematicTraceLinesSolver.solvedTracePaths + } + + const netLabelPlacements = + this.netLabelPlacementSolver?.netLabelPlacements ?? [] + + return { + traces, + netLabelPlacements, + } + } + + override getConstructorParams(): ConstructorParameters< + typeof SchematicTracePipelineSolver + >[0] { + return this.inputProblem + } + + currentPipelineStepIndex = 0 + + private cloneAndCorrectInputProblem(original: InputProblem): InputProblem { + const cloned: InputProblem = structuredClone({ + ...original, + _chipObstacleSpatialIndex: undefined, + }) + + // First, expand chips so existing pin coordinates sit on or within their edges without shrinking. + expandChipsToFitPins(cloned) + // Then, for any remaining pins that are still inside due to mixed extremes, snap them to the nearest edge. + correctPinsInsideChips(cloned) + + return cloned + } + + override _step() { + const pipelineStepDef = this.pipelineDef[this.currentPipelineStepIndex] + if (!pipelineStepDef) { + this.solved = true + return + } + + if (this.activeSubSolver) { + this.activeSubSolver.step() + if (this.activeSubSolver.solved) { + this.endTimeOfPhase[pipelineStepDef.solverName] = performance.now() + this.timeSpentOnPhase[pipelineStepDef.solverName] = + this.endTimeOfPhase[pipelineStepDef.solverName]! - + this.startTimeOfPhase[pipelineStepDef.solverName]! + pipelineStepDef.onSolved?.(this) + this.activeSubSolver = null + this.currentPipelineStepIndex++ + } else if (this.activeSubSolver.failed) { + this.error = this.activeSubSolver?.error + this.failed = true + this.activeSubSolver = null + } + return + } + + const constructorParams = pipelineStepDef.getConstructorParams(this) + // @ts-ignore + this.activeSubSolver = new pipelineStepDef.solverClass(...constructorParams) + ;(this as any)[pipelineStepDef.solverName] = this.activeSubSolver + this.timeSpentOnPhase[pipelineStepDef.solverName] = 0 + this.startTimeOfPhase[pipelineStepDef.solverName] = performance.now() + this.firstIterationOfPhase[pipelineStepDef.solverName] = this.iterations + } + + solveUntilPhase(phase: string) { + while (this.getCurrentPhase().toLowerCase() !== phase.toLowerCase()) { + this.step() + } + } + + getCurrentPhase(): string { + return this.pipelineDef[this.currentPipelineStepIndex]?.solverName ?? "none" + } + + override visualize(): GraphicsObject { + if (!this.solved && this.activeSubSolver) + return this.activeSubSolver.visualize() + + const visualizations = [ + visualizeInputProblem(this.inputProblem), + ...(this.pipelineDef + .map((p) => (this as any)[p.solverName]?.visualize()) + .filter(Boolean) + .map((viz, stepIndex) => { + for (const rect of viz!.rects ?? []) { + rect.step = stepIndex + } + for (const point of viz!.points ?? []) { + point.step = stepIndex + } + for (const circle of viz!.circles ?? []) { + circle.step = stepIndex + } + for (const text of viz!.texts ?? []) { + text.step = stepIndex + } + for (const line of viz!.lines ?? []) { + line.step = stepIndex + } + return viz + }) as GraphicsObject[]), + ] + + if (visualizations.length === 1) { + return visualizations[0]! + } + + // Simple combination of visualizations + const finalGraphics = { + points: visualizations.flatMap((v) => v.points || []), + rects: visualizations.flatMap((v) => v.rects || []), + lines: visualizations.flatMap((v) => v.lines || []), + circles: visualizations.flatMap((v) => v.circles || []), + texts: visualizations.flatMap((v) => v.texts || []), + } + return finalGraphics + } + + /** + * A lightweight version of the visualize method that can be used to stream + * progress + */ + override preview(): GraphicsObject { + if (this.activeSubSolver) { + return this.activeSubSolver.preview() + } + + return super.preview() + } +} From 256bd3b7edb1cb68954bc71839277ffc65af5f7f Mon Sep 17 00:00:00 2001 From: Chronolapse411 Date: Tue, 31 Mar 2026 23:28:39 -0400 Subject: [PATCH 2/2] fix: apply biome formatting --- .../SchematicTracePipelineSolver.ts | 774 +++++++++--------- 1 file changed, 387 insertions(+), 387 deletions(-) diff --git a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts index 166b5141..ca23c343 100644 --- a/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts +++ b/lib/solvers/SchematicTracePipelineSolver/SchematicTracePipelineSolver.ts @@ -1,387 +1,387 @@ -/** - * Pipeline solver that runs a series of solvers to find the best schematic layout. - * Coordinates the entire layout process from chip partitioning through final packing. - */ - -import type { GraphicsObject } from "graphics-debug" -import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver" -import type { InputProblem } from "lib/types/InputProblem" -import { MspConnectionPairSolver } from "../MspConnectionPairSolver/MspConnectionPairSolver" -import { - SchematicTraceLinesSolver, - type SolvedTracePath, -} from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver" -import { TraceOverlapShiftSolver } from "../TraceOverlapShiftSolver/TraceOverlapShiftSolver" -import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver" -import { visualizeInputProblem } from "./visualizeInputProblem" -import { TraceLabelOverlapAvoidanceSolver } from "../TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver" -import { correctPinsInsideChips } from "./correctPinsInsideChip" -import { expandChipsToFitPins } from "./expandChipsToFitPins" -import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePairSolver" -import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver" -import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver" - -type PipelineStep BaseSolver> = { - solverName: string - solverClass: T - getConstructorParams: ( - instance: SchematicTracePipelineSolver, - ) => ConstructorParameters - onSolved?: (instance: SchematicTracePipelineSolver) => void - shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean -} - -function definePipelineStep< - T extends new ( - ...args: any[] - ) => BaseSolver, - const P extends ConstructorParameters, ->( - solverName: keyof SchematicTracePipelineSolver, - solverClass: T, - getConstructorParams: (instance: SchematicTracePipelineSolver) => P, - opts: { - onSolved?: (instance: SchematicTracePipelineSolver) => void - shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean - } = {}, -): PipelineStep { - return { - solverName, - solverClass, - getConstructorParams, - onSolved: opts.onSolved, - shouldSkip: opts.shouldSkip, - } -} - -export interface SchematicTracePipelineSolverParams { - inputProblem: InputProblem - allowLongDistanceTraces?: boolean -} - -export class SchematicTracePipelineSolver extends BaseSolver { - mspConnectionPairSolver?: MspConnectionPairSolver - // guidelinesSolver?: GuidelinesSolver - schematicTraceLinesSolver?: SchematicTraceLinesSolver - longDistancePairSolver?: LongDistancePairSolver - traceOverlapShiftSolver?: TraceOverlapShiftSolver - netLabelPlacementSolver?: NetLabelPlacementSolver - labelMergingSolver?: MergedNetLabelObstacleSolver - traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver - traceCleanupSolver?: TraceCleanupSolver - - startTimeOfPhase: Record - endTimeOfPhase: Record - timeSpentOnPhase: Record - firstIterationOfPhase: Record - - inputProblem: InputProblem - - pipelineDef = [ - definePipelineStep( - "mspConnectionPairSolver", - MspConnectionPairSolver, - () => [{ inputProblem: this.inputProblem }], - { - onSolved: (mspSolver) => {}, - }, - ), - // definePipelineStep( - // "guidelinesSolver", - // GuidelinesSolver, - // () => [ - // { - // inputProblem: this.inputProblem, - // }, - // ], - // { - // onSolved: (guidelinesSolver) => {}, - // }, - // ), - definePipelineStep( - "schematicTraceLinesSolver", - SchematicTraceLinesSolver, - () => [ - { - mspConnectionPairs: this.mspConnectionPairSolver!.mspConnectionPairs, - dcConnMap: this.mspConnectionPairSolver!.dcConnMap, - globalConnMap: this.mspConnectionPairSolver!.globalConnMap, - inputProblem: this.inputProblem, - // guidelines: this.guidelinesSolver!.guidelines, - chipMap: this.mspConnectionPairSolver!.chipMap, - }, - ], - ), - definePipelineStep( - "longDistancePairSolver", - LongDistancePairSolver, - (instance) => [ - { - inputProblem: instance.inputProblem, - primaryMspConnectionPairs: - instance.mspConnectionPairSolver!.mspConnectionPairs, - alreadySolvedTraces: - instance.schematicTraceLinesSolver!.solvedTracePaths, - }, - ], - { - onSolved: (schematicTraceLinesSolver) => {}, - }, - ), - definePipelineStep( - "traceOverlapShiftSolver", - TraceOverlapShiftSolver, - () => [ - { - inputProblem: this.inputProblem, - inputTracePaths: - this.longDistancePairSolver?.getOutput().allTracesMerged!, - globalConnMap: this.mspConnectionPairSolver!.globalConnMap, - }, - ], - { - onSolved: (_solver) => {}, - }, - ), - definePipelineStep( - "netLabelPlacementSolver", - NetLabelPlacementSolver, - () => [ - { - inputProblem: this.inputProblem, - inputTraceMap: - this.traceOverlapShiftSolver?.correctedTraceMap ?? - Object.fromEntries( - this.longDistancePairSolver!.getOutput().allTracesMerged.map( - (p) => [p.mspPairId, p], - ), - ), - }, - ], - { - onSolved: (_solver) => { - // TODO - }, - }, - ), - definePipelineStep( - "traceLabelOverlapAvoidanceSolver", - TraceLabelOverlapAvoidanceSolver, - (instance) => { - const traceMap = - instance.traceOverlapShiftSolver?.correctedTraceMap ?? - Object.fromEntries( - instance - .longDistancePairSolver!.getOutput() - .allTracesMerged.map((p) => [p.mspPairId, p]), - ) - const traces = Object.values(traceMap) - const netLabelPlacements = - instance.netLabelPlacementSolver!.netLabelPlacements - - return [ - { - inputProblem: instance.inputProblem, - traces, - netLabelPlacements, - }, - ] - }, - ), - definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => { - const prevSolverOutput = - instance.traceLabelOverlapAvoidanceSolver!.getOutput() - const traces = prevSolverOutput.traces - - const labelMergingOutput = - instance.traceLabelOverlapAvoidanceSolver!.labelMergingSolver!.getOutput() - - return [ - { - inputProblem: instance.inputProblem, - allTraces: traces, - allLabelPlacements: labelMergingOutput.netLabelPlacements, - mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap, - paddingBuffer: 0.1, - }, - ] - }), - definePipelineStep( - "netLabelPlacementSolver", - NetLabelPlacementSolver, - (instance) => { - const traces = - instance.traceCleanupSolver?.getOutput().traces ?? - instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces - - return [ - { - inputProblem: instance.inputProblem, - inputTraceMap: Object.fromEntries( - traces.map((trace: SolvedTracePath) => [trace.mspPairId, trace]), - ), - }, - ] - }, - ), - ] - - constructor(inputProblem: InputProblem) { - super() - this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem) - this.MAX_ITERATIONS = 1e6 - this.startTimeOfPhase = {} - this.endTimeOfPhase = {} - this.timeSpentOnPhase = {} - this.firstIterationOfPhase = {} - } - - getOutput(): { - traces: SolvedTracePath[] - netLabelPlacements: import("../NetLabelPlacementSolver/NetLabelPlacementSolver").NetLabelPlacement[] - } { - let traces: SolvedTracePath[] = [] - - if (this.traceCleanupSolver) { - traces = this.traceCleanupSolver.getOutput().traces - } else if (this.traceLabelOverlapAvoidanceSolver) { - traces = this.traceLabelOverlapAvoidanceSolver.getOutput().traces - } else if (this.traceOverlapShiftSolver?.correctedTraceMap) { - traces = Object.values(this.traceOverlapShiftSolver.correctedTraceMap) - } else if (this.longDistancePairSolver) { - traces = this.longDistancePairSolver.getOutput().allTracesMerged - } else if (this.schematicTraceLinesSolver) { - traces = this.schematicTraceLinesSolver.solvedTracePaths - } - - const netLabelPlacements = - this.netLabelPlacementSolver?.netLabelPlacements ?? [] - - return { - traces, - netLabelPlacements, - } - } - - override getConstructorParams(): ConstructorParameters< - typeof SchematicTracePipelineSolver - >[0] { - return this.inputProblem - } - - currentPipelineStepIndex = 0 - - private cloneAndCorrectInputProblem(original: InputProblem): InputProblem { - const cloned: InputProblem = structuredClone({ - ...original, - _chipObstacleSpatialIndex: undefined, - }) - - // First, expand chips so existing pin coordinates sit on or within their edges without shrinking. - expandChipsToFitPins(cloned) - // Then, for any remaining pins that are still inside due to mixed extremes, snap them to the nearest edge. - correctPinsInsideChips(cloned) - - return cloned - } - - override _step() { - const pipelineStepDef = this.pipelineDef[this.currentPipelineStepIndex] - if (!pipelineStepDef) { - this.solved = true - return - } - - if (this.activeSubSolver) { - this.activeSubSolver.step() - if (this.activeSubSolver.solved) { - this.endTimeOfPhase[pipelineStepDef.solverName] = performance.now() - this.timeSpentOnPhase[pipelineStepDef.solverName] = - this.endTimeOfPhase[pipelineStepDef.solverName]! - - this.startTimeOfPhase[pipelineStepDef.solverName]! - pipelineStepDef.onSolved?.(this) - this.activeSubSolver = null - this.currentPipelineStepIndex++ - } else if (this.activeSubSolver.failed) { - this.error = this.activeSubSolver?.error - this.failed = true - this.activeSubSolver = null - } - return - } - - const constructorParams = pipelineStepDef.getConstructorParams(this) - // @ts-ignore - this.activeSubSolver = new pipelineStepDef.solverClass(...constructorParams) - ;(this as any)[pipelineStepDef.solverName] = this.activeSubSolver - this.timeSpentOnPhase[pipelineStepDef.solverName] = 0 - this.startTimeOfPhase[pipelineStepDef.solverName] = performance.now() - this.firstIterationOfPhase[pipelineStepDef.solverName] = this.iterations - } - - solveUntilPhase(phase: string) { - while (this.getCurrentPhase().toLowerCase() !== phase.toLowerCase()) { - this.step() - } - } - - getCurrentPhase(): string { - return this.pipelineDef[this.currentPipelineStepIndex]?.solverName ?? "none" - } - - override visualize(): GraphicsObject { - if (!this.solved && this.activeSubSolver) - return this.activeSubSolver.visualize() - - const visualizations = [ - visualizeInputProblem(this.inputProblem), - ...(this.pipelineDef - .map((p) => (this as any)[p.solverName]?.visualize()) - .filter(Boolean) - .map((viz, stepIndex) => { - for (const rect of viz!.rects ?? []) { - rect.step = stepIndex - } - for (const point of viz!.points ?? []) { - point.step = stepIndex - } - for (const circle of viz!.circles ?? []) { - circle.step = stepIndex - } - for (const text of viz!.texts ?? []) { - text.step = stepIndex - } - for (const line of viz!.lines ?? []) { - line.step = stepIndex - } - return viz - }) as GraphicsObject[]), - ] - - if (visualizations.length === 1) { - return visualizations[0]! - } - - // Simple combination of visualizations - const finalGraphics = { - points: visualizations.flatMap((v) => v.points || []), - rects: visualizations.flatMap((v) => v.rects || []), - lines: visualizations.flatMap((v) => v.lines || []), - circles: visualizations.flatMap((v) => v.circles || []), - texts: visualizations.flatMap((v) => v.texts || []), - } - return finalGraphics - } - - /** - * A lightweight version of the visualize method that can be used to stream - * progress - */ - override preview(): GraphicsObject { - if (this.activeSubSolver) { - return this.activeSubSolver.preview() - } - - return super.preview() - } -} +/** + * Pipeline solver that runs a series of solvers to find the best schematic layout. + * Coordinates the entire layout process from chip partitioning through final packing. + */ + +import type { GraphicsObject } from "graphics-debug" +import { BaseSolver } from "lib/solvers/BaseSolver/BaseSolver" +import type { InputProblem } from "lib/types/InputProblem" +import { MspConnectionPairSolver } from "../MspConnectionPairSolver/MspConnectionPairSolver" +import { + SchematicTraceLinesSolver, + type SolvedTracePath, +} from "../SchematicTraceLinesSolver/SchematicTraceLinesSolver" +import { TraceOverlapShiftSolver } from "../TraceOverlapShiftSolver/TraceOverlapShiftSolver" +import { NetLabelPlacementSolver } from "../NetLabelPlacementSolver/NetLabelPlacementSolver" +import { visualizeInputProblem } from "./visualizeInputProblem" +import { TraceLabelOverlapAvoidanceSolver } from "../TraceLabelOverlapAvoidanceSolver/TraceLabelOverlapAvoidanceSolver" +import { correctPinsInsideChips } from "./correctPinsInsideChip" +import { expandChipsToFitPins } from "./expandChipsToFitPins" +import { LongDistancePairSolver } from "../LongDistancePairSolver/LongDistancePairSolver" +import { MergedNetLabelObstacleSolver } from "../TraceLabelOverlapAvoidanceSolver/sub-solvers/LabelMergingSolver/LabelMergingSolver" +import { TraceCleanupSolver } from "../TraceCleanupSolver/TraceCleanupSolver" + +type PipelineStep BaseSolver> = { + solverName: string + solverClass: T + getConstructorParams: ( + instance: SchematicTracePipelineSolver, + ) => ConstructorParameters + onSolved?: (instance: SchematicTracePipelineSolver) => void + shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean +} + +function definePipelineStep< + T extends new ( + ...args: any[] + ) => BaseSolver, + const P extends ConstructorParameters, +>( + solverName: keyof SchematicTracePipelineSolver, + solverClass: T, + getConstructorParams: (instance: SchematicTracePipelineSolver) => P, + opts: { + onSolved?: (instance: SchematicTracePipelineSolver) => void + shouldSkip?: (instance: SchematicTracePipelineSolver) => boolean + } = {}, +): PipelineStep { + return { + solverName, + solverClass, + getConstructorParams, + onSolved: opts.onSolved, + shouldSkip: opts.shouldSkip, + } +} + +export interface SchematicTracePipelineSolverParams { + inputProblem: InputProblem + allowLongDistanceTraces?: boolean +} + +export class SchematicTracePipelineSolver extends BaseSolver { + mspConnectionPairSolver?: MspConnectionPairSolver + // guidelinesSolver?: GuidelinesSolver + schematicTraceLinesSolver?: SchematicTraceLinesSolver + longDistancePairSolver?: LongDistancePairSolver + traceOverlapShiftSolver?: TraceOverlapShiftSolver + netLabelPlacementSolver?: NetLabelPlacementSolver + labelMergingSolver?: MergedNetLabelObstacleSolver + traceLabelOverlapAvoidanceSolver?: TraceLabelOverlapAvoidanceSolver + traceCleanupSolver?: TraceCleanupSolver + + startTimeOfPhase: Record + endTimeOfPhase: Record + timeSpentOnPhase: Record + firstIterationOfPhase: Record + + inputProblem: InputProblem + + pipelineDef = [ + definePipelineStep( + "mspConnectionPairSolver", + MspConnectionPairSolver, + () => [{ inputProblem: this.inputProblem }], + { + onSolved: (mspSolver) => {}, + }, + ), + // definePipelineStep( + // "guidelinesSolver", + // GuidelinesSolver, + // () => [ + // { + // inputProblem: this.inputProblem, + // }, + // ], + // { + // onSolved: (guidelinesSolver) => {}, + // }, + // ), + definePipelineStep( + "schematicTraceLinesSolver", + SchematicTraceLinesSolver, + () => [ + { + mspConnectionPairs: this.mspConnectionPairSolver!.mspConnectionPairs, + dcConnMap: this.mspConnectionPairSolver!.dcConnMap, + globalConnMap: this.mspConnectionPairSolver!.globalConnMap, + inputProblem: this.inputProblem, + // guidelines: this.guidelinesSolver!.guidelines, + chipMap: this.mspConnectionPairSolver!.chipMap, + }, + ], + ), + definePipelineStep( + "longDistancePairSolver", + LongDistancePairSolver, + (instance) => [ + { + inputProblem: instance.inputProblem, + primaryMspConnectionPairs: + instance.mspConnectionPairSolver!.mspConnectionPairs, + alreadySolvedTraces: + instance.schematicTraceLinesSolver!.solvedTracePaths, + }, + ], + { + onSolved: (schematicTraceLinesSolver) => {}, + }, + ), + definePipelineStep( + "traceOverlapShiftSolver", + TraceOverlapShiftSolver, + () => [ + { + inputProblem: this.inputProblem, + inputTracePaths: + this.longDistancePairSolver?.getOutput().allTracesMerged!, + globalConnMap: this.mspConnectionPairSolver!.globalConnMap, + }, + ], + { + onSolved: (_solver) => {}, + }, + ), + definePipelineStep( + "netLabelPlacementSolver", + NetLabelPlacementSolver, + () => [ + { + inputProblem: this.inputProblem, + inputTraceMap: + this.traceOverlapShiftSolver?.correctedTraceMap ?? + Object.fromEntries( + this.longDistancePairSolver!.getOutput().allTracesMerged.map( + (p) => [p.mspPairId, p], + ), + ), + }, + ], + { + onSolved: (_solver) => { + // TODO + }, + }, + ), + definePipelineStep( + "traceLabelOverlapAvoidanceSolver", + TraceLabelOverlapAvoidanceSolver, + (instance) => { + const traceMap = + instance.traceOverlapShiftSolver?.correctedTraceMap ?? + Object.fromEntries( + instance + .longDistancePairSolver!.getOutput() + .allTracesMerged.map((p) => [p.mspPairId, p]), + ) + const traces = Object.values(traceMap) + const netLabelPlacements = + instance.netLabelPlacementSolver!.netLabelPlacements + + return [ + { + inputProblem: instance.inputProblem, + traces, + netLabelPlacements, + }, + ] + }, + ), + definePipelineStep("traceCleanupSolver", TraceCleanupSolver, (instance) => { + const prevSolverOutput = + instance.traceLabelOverlapAvoidanceSolver!.getOutput() + const traces = prevSolverOutput.traces + + const labelMergingOutput = + instance.traceLabelOverlapAvoidanceSolver!.labelMergingSolver!.getOutput() + + return [ + { + inputProblem: instance.inputProblem, + allTraces: traces, + allLabelPlacements: labelMergingOutput.netLabelPlacements, + mergedLabelNetIdMap: labelMergingOutput.mergedLabelNetIdMap, + paddingBuffer: 0.1, + }, + ] + }), + definePipelineStep( + "netLabelPlacementSolver", + NetLabelPlacementSolver, + (instance) => { + const traces = + instance.traceCleanupSolver?.getOutput().traces ?? + instance.traceLabelOverlapAvoidanceSolver!.getOutput().traces + + return [ + { + inputProblem: instance.inputProblem, + inputTraceMap: Object.fromEntries( + traces.map((trace: SolvedTracePath) => [trace.mspPairId, trace]), + ), + }, + ] + }, + ), + ] + + constructor(inputProblem: InputProblem) { + super() + this.inputProblem = this.cloneAndCorrectInputProblem(inputProblem) + this.MAX_ITERATIONS = 1e6 + this.startTimeOfPhase = {} + this.endTimeOfPhase = {} + this.timeSpentOnPhase = {} + this.firstIterationOfPhase = {} + } + + getOutput(): { + traces: SolvedTracePath[] + netLabelPlacements: import("../NetLabelPlacementSolver/NetLabelPlacementSolver").NetLabelPlacement[] + } { + let traces: SolvedTracePath[] = [] + + if (this.traceCleanupSolver) { + traces = this.traceCleanupSolver.getOutput().traces + } else if (this.traceLabelOverlapAvoidanceSolver) { + traces = this.traceLabelOverlapAvoidanceSolver.getOutput().traces + } else if (this.traceOverlapShiftSolver?.correctedTraceMap) { + traces = Object.values(this.traceOverlapShiftSolver.correctedTraceMap) + } else if (this.longDistancePairSolver) { + traces = this.longDistancePairSolver.getOutput().allTracesMerged + } else if (this.schematicTraceLinesSolver) { + traces = this.schematicTraceLinesSolver.solvedTracePaths + } + + const netLabelPlacements = + this.netLabelPlacementSolver?.netLabelPlacements ?? [] + + return { + traces, + netLabelPlacements, + } + } + + override getConstructorParams(): ConstructorParameters< + typeof SchematicTracePipelineSolver + >[0] { + return this.inputProblem + } + + currentPipelineStepIndex = 0 + + private cloneAndCorrectInputProblem(original: InputProblem): InputProblem { + const cloned: InputProblem = structuredClone({ + ...original, + _chipObstacleSpatialIndex: undefined, + }) + + // First, expand chips so existing pin coordinates sit on or within their edges without shrinking. + expandChipsToFitPins(cloned) + // Then, for any remaining pins that are still inside due to mixed extremes, snap them to the nearest edge. + correctPinsInsideChips(cloned) + + return cloned + } + + override _step() { + const pipelineStepDef = this.pipelineDef[this.currentPipelineStepIndex] + if (!pipelineStepDef) { + this.solved = true + return + } + + if (this.activeSubSolver) { + this.activeSubSolver.step() + if (this.activeSubSolver.solved) { + this.endTimeOfPhase[pipelineStepDef.solverName] = performance.now() + this.timeSpentOnPhase[pipelineStepDef.solverName] = + this.endTimeOfPhase[pipelineStepDef.solverName]! - + this.startTimeOfPhase[pipelineStepDef.solverName]! + pipelineStepDef.onSolved?.(this) + this.activeSubSolver = null + this.currentPipelineStepIndex++ + } else if (this.activeSubSolver.failed) { + this.error = this.activeSubSolver?.error + this.failed = true + this.activeSubSolver = null + } + return + } + + const constructorParams = pipelineStepDef.getConstructorParams(this) + // @ts-ignore + this.activeSubSolver = new pipelineStepDef.solverClass(...constructorParams) + ;(this as any)[pipelineStepDef.solverName] = this.activeSubSolver + this.timeSpentOnPhase[pipelineStepDef.solverName] = 0 + this.startTimeOfPhase[pipelineStepDef.solverName] = performance.now() + this.firstIterationOfPhase[pipelineStepDef.solverName] = this.iterations + } + + solveUntilPhase(phase: string) { + while (this.getCurrentPhase().toLowerCase() !== phase.toLowerCase()) { + this.step() + } + } + + getCurrentPhase(): string { + return this.pipelineDef[this.currentPipelineStepIndex]?.solverName ?? "none" + } + + override visualize(): GraphicsObject { + if (!this.solved && this.activeSubSolver) + return this.activeSubSolver.visualize() + + const visualizations = [ + visualizeInputProblem(this.inputProblem), + ...(this.pipelineDef + .map((p) => (this as any)[p.solverName]?.visualize()) + .filter(Boolean) + .map((viz, stepIndex) => { + for (const rect of viz!.rects ?? []) { + rect.step = stepIndex + } + for (const point of viz!.points ?? []) { + point.step = stepIndex + } + for (const circle of viz!.circles ?? []) { + circle.step = stepIndex + } + for (const text of viz!.texts ?? []) { + text.step = stepIndex + } + for (const line of viz!.lines ?? []) { + line.step = stepIndex + } + return viz + }) as GraphicsObject[]), + ] + + if (visualizations.length === 1) { + return visualizations[0]! + } + + // Simple combination of visualizations + const finalGraphics = { + points: visualizations.flatMap((v) => v.points || []), + rects: visualizations.flatMap((v) => v.rects || []), + lines: visualizations.flatMap((v) => v.lines || []), + circles: visualizations.flatMap((v) => v.circles || []), + texts: visualizations.flatMap((v) => v.texts || []), + } + return finalGraphics + } + + /** + * A lightweight version of the visualize method that can be used to stream + * progress + */ + override preview(): GraphicsObject { + if (this.activeSubSolver) { + return this.activeSubSolver.preview() + } + + return super.preview() + } +}