@tscircuit/fanout-solver 0.0.48 → 0.0.49
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/fanout-solver.ts +847 -177
- package/lib/route-bus.ts +35 -4
- package/lib/route-single-layer-adaptive-exits.ts +323 -16
- package/lib/route-via-minimal-winding.ts +253 -8
- package/package.json +1 -1
package/lib/fanout-solver.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SimpleRouteJson } from "@tscircuit/capacity-autorouter"
|
|
2
2
|
import { BaseSolver } from "@tscircuit/solver-utils"
|
|
3
|
-
import type
|
|
3
|
+
import { mergeGraphics, type GraphicsObject } from "graphics-debug"
|
|
4
4
|
import { addViaLayerMetadataToSrj } from "./add-via-layer-metadata"
|
|
5
5
|
import { getCornerBandSide, getExitEdgeForDirection } from "./boundary-exit"
|
|
6
6
|
import { buildOutputSimpleRouteJson } from "./build-output"
|
|
@@ -27,9 +27,10 @@ import {
|
|
|
27
27
|
fanoutPlansAreClear,
|
|
28
28
|
type RouteBusStaticClearanceCache,
|
|
29
29
|
routeBus,
|
|
30
|
+
routeBusAlternativesSteps,
|
|
30
31
|
routeBusAlternatives,
|
|
31
32
|
} from "./route-bus"
|
|
32
|
-
import {
|
|
33
|
+
import { routeSingleLayerWithAdaptiveExitsSteps } from "./route-single-layer-adaptive-exits"
|
|
33
34
|
import { routeSingleLayerWithPushAndShove } from "./route-single-layer-push-shove"
|
|
34
35
|
import type {
|
|
35
36
|
AssignmentAttempt,
|
|
@@ -79,6 +80,94 @@ interface MixedTerminationState {
|
|
|
79
80
|
|
|
80
81
|
type RoutingStrategy = "default" | "group-by-layer" | "deep-first"
|
|
81
82
|
|
|
83
|
+
interface FanoutSubsolverRequest {
|
|
84
|
+
type: "subsolver"
|
|
85
|
+
solver: BaseSolver
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type FanoutWorkYield = void | FanoutSubsolverRequest
|
|
89
|
+
|
|
90
|
+
class FanoutWorkSolver<T> extends BaseSolver {
|
|
91
|
+
private output: T | undefined
|
|
92
|
+
private hasOutput = false
|
|
93
|
+
private nextInput: unknown
|
|
94
|
+
|
|
95
|
+
constructor(
|
|
96
|
+
private readonly solverName: string,
|
|
97
|
+
private readonly generator: Generator<unknown, T, unknown>,
|
|
98
|
+
private readonly getVisualization: () => GraphicsObject,
|
|
99
|
+
private readonly getStats: () => Record<string, unknown>,
|
|
100
|
+
private readonly getProgress: () => number,
|
|
101
|
+
) {
|
|
102
|
+
super()
|
|
103
|
+
this.MAX_ITERATIONS = 1_000_000
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
override getSolverName(): string {
|
|
107
|
+
return this.solverName
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
override _step(): void {
|
|
111
|
+
if (this.activeSubSolver) {
|
|
112
|
+
this.activeSubSolver.step()
|
|
113
|
+
if (this.activeSubSolver.failed) {
|
|
114
|
+
this.failedSubSolvers = [
|
|
115
|
+
...(this.failedSubSolvers ?? []),
|
|
116
|
+
this.activeSubSolver,
|
|
117
|
+
]
|
|
118
|
+
this.error = this.activeSubSolver.error
|
|
119
|
+
this.failed = true
|
|
120
|
+
this.activeSubSolver = null
|
|
121
|
+
return
|
|
122
|
+
}
|
|
123
|
+
if (this.activeSubSolver.solved) {
|
|
124
|
+
this.nextInput = this.activeSubSolver.getOutput()
|
|
125
|
+
this.activeSubSolver = null
|
|
126
|
+
}
|
|
127
|
+
this.stats = this.getStats()
|
|
128
|
+
return
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const result = this.generator.next(this.nextInput)
|
|
132
|
+
this.nextInput = undefined
|
|
133
|
+
this.stats = this.getStats()
|
|
134
|
+
if (result.done) {
|
|
135
|
+
this.output = result.value
|
|
136
|
+
this.hasOutput = true
|
|
137
|
+
this.solved = true
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
const yielded = result.value as Partial<FanoutSubsolverRequest> | undefined
|
|
141
|
+
if (yielded?.type === "subsolver" && yielded.solver instanceof BaseSolver) {
|
|
142
|
+
this.activeSubSolver = yielded.solver
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
computeProgress(): number {
|
|
147
|
+
return this.getProgress()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
override getConstructorParams(): [] {
|
|
151
|
+
return []
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
override getOutput(): T {
|
|
155
|
+
if (!this.solved || !this.hasOutput) {
|
|
156
|
+
throw new Error(`${this.solverName}: output requested before completion`)
|
|
157
|
+
}
|
|
158
|
+
return this.output as T
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
override visualize(): GraphicsObject {
|
|
162
|
+
return this.activeSubSolver?.visualize() ?? this.getVisualization()
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
interface ActiveFanoutOperation<T> {
|
|
167
|
+
solver: FanoutWorkSolver<T>
|
|
168
|
+
onSolved: (output: T) => void
|
|
169
|
+
}
|
|
170
|
+
|
|
82
171
|
function resolvePositiveNumber(label: string, value: number): number {
|
|
83
172
|
if (!Number.isFinite(value) || value <= 0) {
|
|
84
173
|
throw new Error(
|
|
@@ -756,12 +845,12 @@ function getCandidateEscapeLayersForBus(params: {
|
|
|
756
845
|
export class FanoutSolver extends BaseSolver {
|
|
757
846
|
readonly preparedBuses: PreparedBus[]
|
|
758
847
|
readonly attempts: FanoutAttemptSummary[] = []
|
|
759
|
-
readonly layerAssignments: Array<Readonly<Record<string, string>>>
|
|
848
|
+
readonly layerAssignments: Array<Readonly<Record<string, string>>> = []
|
|
760
849
|
readonly config: ResolvedFanoutConfig
|
|
761
850
|
private readonly routingSrj: SimpleRouteJson
|
|
762
|
-
private readonly escapeLayersByBusId:
|
|
763
|
-
|
|
764
|
-
|
|
851
|
+
private readonly escapeLayersByBusId: Record<string, readonly string[]> = {}
|
|
852
|
+
private readonly boundaryBuses: PreparedBus[]
|
|
853
|
+
private readonly fixedPlaneAssignments: Readonly<Record<string, string>>
|
|
765
854
|
private readonly evaluatedAssignmentKeys = new Set<string>()
|
|
766
855
|
private readonly queuedAssignmentKeys = new Set<string>()
|
|
767
856
|
private readonly assignmentRepairDepthByKey = new Map<string, number>()
|
|
@@ -779,8 +868,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
779
868
|
}
|
|
780
869
|
>()
|
|
781
870
|
private groupedBeamEvaluated = false
|
|
871
|
+
private routingInitialized = false
|
|
872
|
+
private nextCandidateLayerBusIndex = 0
|
|
782
873
|
private nextAssignmentIndex = 0
|
|
783
874
|
private nextGeneratedAssignmentIndex = 0
|
|
875
|
+
private activeOperation: ActiveFanoutOperation<unknown> | null = null
|
|
876
|
+
private inProgressPlans: FanoutRoutePlan[] = []
|
|
877
|
+
private activeRoutingVisualization: GraphicsObject | null = null
|
|
878
|
+
private activeAdaptiveVisualization: GraphicsObject | null = null
|
|
784
879
|
private bestAttempt: AssignmentAttempt | null = null
|
|
785
880
|
private lengthMatchingFailure: FanoutValidationIssue | null = null
|
|
786
881
|
private endpointCompletion: CompleteOriginalEndpointsResult | null = null
|
|
@@ -857,56 +952,330 @@ export class FanoutSolver extends BaseSolver {
|
|
|
857
952
|
)
|
|
858
953
|
}
|
|
859
954
|
}
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
955
|
+
this.boundaryBuses = this.preparedBuses.filter(
|
|
956
|
+
(bus) => bus.termination.type === "boundary",
|
|
957
|
+
)
|
|
958
|
+
this.fixedPlaneAssignments = Object.fromEntries(
|
|
864
959
|
this.preparedBuses.flatMap((bus) =>
|
|
865
960
|
bus.termination.type === "plane"
|
|
866
961
|
? [[bus.busId, bus.termination.layer] as const]
|
|
867
962
|
: [],
|
|
868
963
|
),
|
|
869
964
|
)
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
965
|
+
const workUnitsPerAssignment = this.preparedBuses.length * 3 + 8
|
|
966
|
+
const estimatedWorkUnitCount =
|
|
967
|
+
this.boundaryBuses.length +
|
|
968
|
+
1 +
|
|
969
|
+
this.config.maxLayerCombinations * workUnitsPerAssignment +
|
|
970
|
+
this.preparedBuses.length * 2 +
|
|
971
|
+
20
|
|
972
|
+
this.MAX_ITERATIONS = Math.max(10_000, estimatedWorkUnitCount)
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
override getSolverName(): string {
|
|
976
|
+
return "FanoutSolver"
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
private stepRoutingInitialization(): void {
|
|
980
|
+
const bus = this.boundaryBuses[this.nextCandidateLayerBusIndex]
|
|
981
|
+
if (bus) {
|
|
982
|
+
this.escapeLayersByBusId[bus.busId] = getCandidateEscapeLayersForBus({
|
|
983
|
+
bus,
|
|
984
|
+
srj: this.routingSrj,
|
|
985
|
+
config: this.config,
|
|
986
|
+
staticClearanceCache: this.routeStaticClearanceCache,
|
|
987
|
+
})
|
|
988
|
+
this.nextCandidateLayerBusIndex++
|
|
989
|
+
this.stats = {
|
|
990
|
+
phase: "discover-candidate-layers",
|
|
991
|
+
bus: bus.busId,
|
|
992
|
+
busIndex: this.nextCandidateLayerBusIndex,
|
|
993
|
+
busCount: this.boundaryBuses.length,
|
|
994
|
+
}
|
|
995
|
+
return
|
|
996
|
+
}
|
|
997
|
+
|
|
887
998
|
const generatedAssignments = generateLayerAssignments({
|
|
888
|
-
busIds:
|
|
999
|
+
busIds: this.boundaryBuses.map((candidate) => candidate.busId),
|
|
889
1000
|
layers: this.config.escapeLayers,
|
|
890
|
-
layersByBusId: escapeLayersByBusId,
|
|
1001
|
+
layersByBusId: this.escapeLayersByBusId,
|
|
891
1002
|
maxAssignments: this.config.maxLayerCombinations,
|
|
892
1003
|
}).map((assignment) => ({
|
|
893
1004
|
...assignment,
|
|
894
|
-
...fixedPlaneAssignments,
|
|
1005
|
+
...this.fixedPlaneAssignments,
|
|
895
1006
|
}))
|
|
896
|
-
this.layerAssignments
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
1007
|
+
this.layerAssignments.push(
|
|
1008
|
+
...prioritizeLayerAssignment({
|
|
1009
|
+
initialAssignment: createInitialLayerAssignment({
|
|
1010
|
+
buses: this.preparedBuses,
|
|
1011
|
+
escapeLayers: this.config.escapeLayers,
|
|
1012
|
+
escapeLayersByBusId: this.escapeLayersByBusId,
|
|
1013
|
+
}),
|
|
1014
|
+
generatedAssignments,
|
|
1015
|
+
maxAssignments: this.config.maxLayerCombinations,
|
|
901
1016
|
}),
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1017
|
+
)
|
|
1018
|
+
this.routingInitialized = true
|
|
1019
|
+
this.stats = {
|
|
1020
|
+
phase: "prepare-layer-assignments",
|
|
1021
|
+
assignmentCount: this.layerAssignments.length,
|
|
1022
|
+
}
|
|
906
1023
|
}
|
|
907
1024
|
|
|
908
|
-
|
|
909
|
-
|
|
1025
|
+
private *initializeRoutingSteps(): Generator<FanoutWorkYield, void, unknown> {
|
|
1026
|
+
while (!this.routingInitialized) {
|
|
1027
|
+
this.stepRoutingInitialization()
|
|
1028
|
+
if (!this.routingInitialized) yield
|
|
1029
|
+
}
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
private setInProgressPlans(params: {
|
|
1033
|
+
phase: string
|
|
1034
|
+
plans: readonly FanoutRoutePlan[]
|
|
1035
|
+
strategy?: RoutingStrategy | "grouped-beam"
|
|
1036
|
+
unitIndex?: number
|
|
1037
|
+
unitCount?: number
|
|
1038
|
+
busId?: string
|
|
1039
|
+
}): void {
|
|
1040
|
+
this.inProgressPlans = [...params.plans]
|
|
1041
|
+
this.stats = {
|
|
1042
|
+
...this.stats,
|
|
1043
|
+
phase: params.phase,
|
|
1044
|
+
...(params.strategy ? { routingStrategy: params.strategy } : {}),
|
|
1045
|
+
...(params.unitIndex !== undefined ? { workUnit: params.unitIndex } : {}),
|
|
1046
|
+
...(params.unitCount !== undefined
|
|
1047
|
+
? { workUnitCount: params.unitCount }
|
|
1048
|
+
: {}),
|
|
1049
|
+
...(params.busId ? { bus: params.busId } : {}),
|
|
1050
|
+
routedConnections: `${params.plans.length}/${this.inputSrj.connections.length}`,
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
private visualizeCurrentState(): GraphicsObject {
|
|
1055
|
+
const visualizedSrj =
|
|
1056
|
+
this.endpointCompletion?.simpleRouteJson ??
|
|
1057
|
+
(!this.solved && !this.failed && this.inProgressPlans.length > 0
|
|
1058
|
+
? buildOutputSimpleRouteJson({
|
|
1059
|
+
inputSrj: this.inputSrj,
|
|
1060
|
+
plans: this.inProgressPlans,
|
|
1061
|
+
layerNames: this.config.layerNames,
|
|
1062
|
+
})
|
|
1063
|
+
: undefined) ??
|
|
1064
|
+
this.bestAttempt?.outputSrj ??
|
|
1065
|
+
this.inputSrj
|
|
1066
|
+
return visualizeSimpleRouteJson(visualizedSrj)
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
private visualizeWorkState(solverName: string): GraphicsObject {
|
|
1070
|
+
const base = this.visualizeCurrentState()
|
|
1071
|
+
const boundary =
|
|
1072
|
+
this.preparedBuses[0]?.sharedBoundary ?? this.inputSrj.bounds
|
|
1073
|
+
const activeBusId =
|
|
1074
|
+
typeof this.stats.bus === "string" ? this.stats.bus : undefined
|
|
1075
|
+
const activeBus = activeBusId
|
|
1076
|
+
? this.preparedBuses.find((bus) => bus.busId === activeBusId)
|
|
1077
|
+
: undefined
|
|
1078
|
+
const width = boundary.maxX - boundary.minX
|
|
1079
|
+
const height = boundary.maxY - boundary.minY
|
|
1080
|
+
const annotationSize = Math.max(Math.min(width, height) * 0.025, 0.25)
|
|
1081
|
+
const phase =
|
|
1082
|
+
typeof this.stats.phase === "string" ? this.stats.phase : "starting"
|
|
1083
|
+
const detail = [
|
|
1084
|
+
typeof this.stats.routeConnection === "string"
|
|
1085
|
+
? `connection ${this.stats.routeConnection}`
|
|
1086
|
+
: undefined,
|
|
1087
|
+
typeof this.stats.searchBatch === "number"
|
|
1088
|
+
? `batch ${this.stats.searchBatch}`
|
|
1089
|
+
: undefined,
|
|
1090
|
+
typeof this.stats.expandedStates === "number"
|
|
1091
|
+
? `${this.stats.expandedStates.toLocaleString()} states`
|
|
1092
|
+
: undefined,
|
|
1093
|
+
]
|
|
1094
|
+
.filter(Boolean)
|
|
1095
|
+
.join(" · ")
|
|
1096
|
+
const title = `${solverName}: ${phase}`
|
|
1097
|
+
return {
|
|
1098
|
+
...mergeGraphics(base, {
|
|
1099
|
+
rects: [
|
|
1100
|
+
{
|
|
1101
|
+
center: {
|
|
1102
|
+
x: (boundary.minX + boundary.maxX) / 2,
|
|
1103
|
+
y: (boundary.minY + boundary.maxY) / 2,
|
|
1104
|
+
},
|
|
1105
|
+
width,
|
|
1106
|
+
height,
|
|
1107
|
+
fill: "rgba(0, 0, 0, 0)",
|
|
1108
|
+
stroke: "rgba(14, 165, 233, 0.8)",
|
|
1109
|
+
label: `${solverName} working boundary`,
|
|
1110
|
+
},
|
|
1111
|
+
],
|
|
1112
|
+
circles: (activeBus?.connections ?? []).map((connection) => ({
|
|
1113
|
+
center: connection.sourcePoint,
|
|
1114
|
+
radius: Math.max(
|
|
1115
|
+
annotationSize,
|
|
1116
|
+
Math.min(
|
|
1117
|
+
connection.sourceObstacle.width,
|
|
1118
|
+
connection.sourceObstacle.height,
|
|
1119
|
+
) * 0.6,
|
|
1120
|
+
),
|
|
1121
|
+
fill: "rgba(250, 204, 21, 0.25)",
|
|
1122
|
+
stroke: "#f59e0b",
|
|
1123
|
+
label: `active bus ${activeBusId}: ${connection.connection.name}`,
|
|
1124
|
+
})),
|
|
1125
|
+
texts: [
|
|
1126
|
+
{
|
|
1127
|
+
x: boundary.minX,
|
|
1128
|
+
y: boundary.maxY + annotationSize * 2,
|
|
1129
|
+
text: `${solverName} · ${phase}${detail ? ` · ${detail}` : ""}`,
|
|
1130
|
+
color: "#0f172a",
|
|
1131
|
+
fontSize: annotationSize * 1.5,
|
|
1132
|
+
anchorSide: "bottom_left",
|
|
1133
|
+
},
|
|
1134
|
+
],
|
|
1135
|
+
}),
|
|
1136
|
+
title,
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
private visualizeBoundaryRoutingState(): GraphicsObject {
|
|
1141
|
+
if (!this.activeRoutingVisualization) {
|
|
1142
|
+
return this.visualizeWorkState("BoundaryBusRoutingSolver")
|
|
1143
|
+
}
|
|
1144
|
+
return {
|
|
1145
|
+
...mergeGraphics(
|
|
1146
|
+
this.visualizeCurrentState(),
|
|
1147
|
+
this.activeRoutingVisualization,
|
|
1148
|
+
),
|
|
1149
|
+
title: this.activeRoutingVisualization.title,
|
|
1150
|
+
}
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1153
|
+
private visualizeAdaptiveRoutingState(): GraphicsObject {
|
|
1154
|
+
if (this.activeAdaptiveVisualization) {
|
|
1155
|
+
return this.activeAdaptiveVisualization
|
|
1156
|
+
}
|
|
1157
|
+
const boundary =
|
|
1158
|
+
this.preparedBuses[0]?.sharedBoundary ?? this.inputSrj.bounds
|
|
1159
|
+
const width = boundary.maxX - boundary.minX
|
|
1160
|
+
const height = boundary.maxY - boundary.minY
|
|
1161
|
+
const annotationSize = Math.max(Math.min(width, height) * 0.02, 0.2)
|
|
1162
|
+
return {
|
|
1163
|
+
title: "SingleLayerAdaptiveExitSolver: preparing flow grid",
|
|
1164
|
+
rects: [
|
|
1165
|
+
{
|
|
1166
|
+
center: {
|
|
1167
|
+
x: (boundary.minX + boundary.maxX) / 2,
|
|
1168
|
+
y: (boundary.minY + boundary.maxY) / 2,
|
|
1169
|
+
},
|
|
1170
|
+
width,
|
|
1171
|
+
height,
|
|
1172
|
+
fill: "rgba(0, 0, 0, 0)",
|
|
1173
|
+
stroke: "rgba(14, 165, 233, 0.9)",
|
|
1174
|
+
label: "adaptive flow grid boundary",
|
|
1175
|
+
},
|
|
1176
|
+
],
|
|
1177
|
+
points: this.preparedBuses.flatMap((bus) =>
|
|
1178
|
+
bus.connections.map((connection) => ({
|
|
1179
|
+
...connection.sourcePoint,
|
|
1180
|
+
color: "#f97316",
|
|
1181
|
+
label: "adaptive route source",
|
|
1182
|
+
})),
|
|
1183
|
+
),
|
|
1184
|
+
texts: [
|
|
1185
|
+
{
|
|
1186
|
+
x: boundary.minX,
|
|
1187
|
+
y: boundary.maxY + annotationSize * 2,
|
|
1188
|
+
text: "preparing adaptive flow grid",
|
|
1189
|
+
color: "#0f172a",
|
|
1190
|
+
fontSize: annotationSize * 1.5,
|
|
1191
|
+
anchorSide: "bottom_left",
|
|
1192
|
+
},
|
|
1193
|
+
],
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
|
|
1197
|
+
private startOperation<T>(params: {
|
|
1198
|
+
name: string
|
|
1199
|
+
generator: Generator<unknown, T, unknown>
|
|
1200
|
+
onSolved: (output: T) => void
|
|
1201
|
+
getProgress?: () => number
|
|
1202
|
+
}): void {
|
|
1203
|
+
const solver = this.createWorkSolver(
|
|
1204
|
+
params.name,
|
|
1205
|
+
params.generator,
|
|
1206
|
+
params.getProgress,
|
|
1207
|
+
)
|
|
1208
|
+
this.activeOperation = {
|
|
1209
|
+
solver,
|
|
1210
|
+
onSolved: params.onSolved,
|
|
1211
|
+
} as ActiveFanoutOperation<unknown>
|
|
1212
|
+
this.activeSubSolver = solver
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
private createWorkSolver<T>(
|
|
1216
|
+
name: string,
|
|
1217
|
+
generator: Generator<unknown, T, unknown>,
|
|
1218
|
+
getProgress?: () => number,
|
|
1219
|
+
getVisualization?: () => GraphicsObject,
|
|
1220
|
+
): FanoutWorkSolver<T> {
|
|
1221
|
+
return new FanoutWorkSolver(
|
|
1222
|
+
name,
|
|
1223
|
+
generator,
|
|
1224
|
+
getVisualization ?? (() => this.visualizeWorkState(name)),
|
|
1225
|
+
() => ({ ...this.stats }),
|
|
1226
|
+
getProgress ?? (() => 0),
|
|
1227
|
+
)
|
|
1228
|
+
}
|
|
1229
|
+
|
|
1230
|
+
private *routeBusAlternativesWorkSteps(
|
|
1231
|
+
params: Parameters<typeof routeBusAlternativesSteps>[0],
|
|
1232
|
+
maximumAlternatives: number,
|
|
1233
|
+
): Generator<FanoutWorkYield, FanoutRoutePlan[][], unknown> {
|
|
1234
|
+
const steps = routeBusAlternativesSteps(params, maximumAlternatives, true)
|
|
1235
|
+
let result = steps.next()
|
|
1236
|
+
while (!result.done) {
|
|
1237
|
+
const { winding } = result.value
|
|
1238
|
+
if (winding.visualization) {
|
|
1239
|
+
this.activeRoutingVisualization = winding.visualization
|
|
1240
|
+
}
|
|
1241
|
+
this.stats = {
|
|
1242
|
+
...this.stats,
|
|
1243
|
+
phase: "route-boundary-bus-connection",
|
|
1244
|
+
bus: result.value.busId,
|
|
1245
|
+
targetLayer: result.value.targetLayer,
|
|
1246
|
+
routeOrderAttempt: winding.routeOrderAttempt,
|
|
1247
|
+
routeConnection: `${winding.connectionIndex + 1}/${winding.connectionCount}`,
|
|
1248
|
+
connection: winding.connectionName,
|
|
1249
|
+
searchBatch: winding.searchBatch,
|
|
1250
|
+
expandedStates: winding.expandedStateCount,
|
|
1251
|
+
connectionComplete: winding.connectionComplete,
|
|
1252
|
+
}
|
|
1253
|
+
yield
|
|
1254
|
+
result = steps.next()
|
|
1255
|
+
}
|
|
1256
|
+
return result.value
|
|
1257
|
+
}
|
|
1258
|
+
|
|
1259
|
+
private stepActiveOperation(): void {
|
|
1260
|
+
const operation = this.activeOperation
|
|
1261
|
+
if (!operation) return
|
|
1262
|
+
operation.solver.step()
|
|
1263
|
+
if (operation.solver.failed) {
|
|
1264
|
+
this.failedSubSolvers = [
|
|
1265
|
+
...(this.failedSubSolvers ?? []),
|
|
1266
|
+
operation.solver,
|
|
1267
|
+
]
|
|
1268
|
+
this.error = operation.solver.error
|
|
1269
|
+
this.failed = true
|
|
1270
|
+
this.activeOperation = null
|
|
1271
|
+
this.activeSubSolver = null
|
|
1272
|
+
return
|
|
1273
|
+
}
|
|
1274
|
+
if (!operation.solver.solved) return
|
|
1275
|
+
const output = operation.solver.getOutput()
|
|
1276
|
+
this.activeOperation = null
|
|
1277
|
+
this.activeSubSolver = null
|
|
1278
|
+
operation.onSolved(output)
|
|
910
1279
|
}
|
|
911
1280
|
|
|
912
1281
|
private completeBestAttemptEndpoints(): void {
|
|
@@ -983,10 +1352,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
983
1352
|
* dogbones. This is intentionally bounded independently of the number of
|
|
984
1353
|
* plane drops so dense power fields cannot explode the general beam search.
|
|
985
1354
|
*/
|
|
986
|
-
private
|
|
1355
|
+
private *routeDenseThroughAllMixedTerminationSteps(params: {
|
|
987
1356
|
busLayerAssignments: Readonly<Record<string, string>>
|
|
988
1357
|
busesInRoutingOrder: readonly PreparedBus[]
|
|
989
|
-
}): MixedTerminationState | null {
|
|
1358
|
+
}): Generator<FanoutWorkYield, MixedTerminationState | null, unknown> {
|
|
990
1359
|
if (this.config.allowBlindAndBuriedVias) return null
|
|
991
1360
|
|
|
992
1361
|
const unsortedBoundaryBuses = params.busesInRoutingOrder.filter(
|
|
@@ -1276,6 +1645,16 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1276
1645
|
preferBoundaryOutwardByBusId,
|
|
1277
1646
|
canShareCopper,
|
|
1278
1647
|
})
|
|
1648
|
+
let denseWorkUnitIndex = 1
|
|
1649
|
+
const denseWorkUnitCount = boundaryBuses.length + planeBuses.length + 3
|
|
1650
|
+
this.setInProgressPlans({
|
|
1651
|
+
phase: "reserve-dense-via-sites",
|
|
1652
|
+
plans: [],
|
|
1653
|
+
strategy: "default",
|
|
1654
|
+
unitIndex: denseWorkUnitIndex,
|
|
1655
|
+
unitCount: denseWorkUnitCount,
|
|
1656
|
+
})
|
|
1657
|
+
yield
|
|
1279
1658
|
if (seedViaPoints) {
|
|
1280
1659
|
const denseBoundaryBusesInRoutingOrder = [
|
|
1281
1660
|
...leadingWideSingletonBuses,
|
|
@@ -1321,7 +1700,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1321
1700
|
})
|
|
1322
1701
|
})
|
|
1323
1702
|
}
|
|
1324
|
-
const
|
|
1703
|
+
const routeMatchedBoundaryBusSteps = function* (
|
|
1704
|
+
this: FanoutSolver,
|
|
1705
|
+
bus: PreparedBus,
|
|
1706
|
+
): Generator<FanoutWorkYield, boolean, unknown> {
|
|
1325
1707
|
const targetLayer = params.busLayerAssignments[bus.busId]
|
|
1326
1708
|
if (!targetLayer) {
|
|
1327
1709
|
return false
|
|
@@ -1345,7 +1727,23 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1345
1727
|
viaMinimalOnly: true,
|
|
1346
1728
|
cornerBandTargetTrackOffset: getCornerBandTargetTrackOffset(bus),
|
|
1347
1729
|
} as const
|
|
1348
|
-
|
|
1730
|
+
const routeAlternatives = function* (
|
|
1731
|
+
this: FanoutSolver,
|
|
1732
|
+
maximumAlternatives: number,
|
|
1733
|
+
): Generator<FanoutWorkYield, FanoutRoutePlan[][], unknown> {
|
|
1734
|
+
this.activeRoutingVisualization = null
|
|
1735
|
+
const solver = this.createWorkSolver(
|
|
1736
|
+
"BoundaryBusRoutingSolver",
|
|
1737
|
+
this.routeBusAlternativesWorkSteps(
|
|
1738
|
+
routeParams,
|
|
1739
|
+
maximumAlternatives,
|
|
1740
|
+
),
|
|
1741
|
+
undefined,
|
|
1742
|
+
() => this.visualizeBoundaryRoutingState(),
|
|
1743
|
+
)
|
|
1744
|
+
return (yield { type: "subsolver", solver }) as FanoutRoutePlan[][]
|
|
1745
|
+
}.bind(this)
|
|
1746
|
+
let busPlans = (yield* routeAlternatives(1))[0]
|
|
1349
1747
|
if (busPlans && bus.maxLengthSkew !== undefined) {
|
|
1350
1748
|
const lengths = busPlans.map((plan) => plan.length)
|
|
1351
1749
|
const rawSkew = Math.max(...lengths) - Math.min(...lengths)
|
|
@@ -1360,7 +1758,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1360
1758
|
// skewed that compact meanders are unlikely to absorb the deficit.
|
|
1361
1759
|
// This keeps already-near-matched buses on the single-attempt path.
|
|
1362
1760
|
if (needsRouteDiversity) {
|
|
1363
|
-
busPlans =
|
|
1761
|
+
busPlans = (yield* routeAlternatives(3)).toSorted(
|
|
1364
1762
|
(first, second) => {
|
|
1365
1763
|
const firstLengths = first.map((plan) => plan.length)
|
|
1366
1764
|
const secondLengths = second.map((plan) => plan.length)
|
|
@@ -1378,11 +1776,22 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1378
1776
|
}
|
|
1379
1777
|
matchedPlans.push(...busPlans)
|
|
1380
1778
|
return true
|
|
1381
|
-
}
|
|
1779
|
+
}.bind(this)
|
|
1382
1780
|
|
|
1383
1781
|
const firstBoundaryBus = denseBoundaryBusesInRoutingOrder[0]!
|
|
1384
1782
|
const routedBoundaryBuses: PreparedBus[] = []
|
|
1385
|
-
|
|
1783
|
+
const firstBoundaryBusRouted =
|
|
1784
|
+
yield* routeMatchedBoundaryBusSteps(firstBoundaryBus)
|
|
1785
|
+
this.setInProgressPlans({
|
|
1786
|
+
phase: "route-dense-boundary-buses",
|
|
1787
|
+
plans: matchedPlans,
|
|
1788
|
+
strategy: "default",
|
|
1789
|
+
unitIndex: ++denseWorkUnitIndex,
|
|
1790
|
+
unitCount: denseWorkUnitCount,
|
|
1791
|
+
busId: firstBoundaryBus.busId,
|
|
1792
|
+
})
|
|
1793
|
+
yield
|
|
1794
|
+
if (firstBoundaryBusRouted) {
|
|
1386
1795
|
routedBoundaryBuses.push(firstBoundaryBus)
|
|
1387
1796
|
} else {
|
|
1388
1797
|
matchedRoutingSucceeded = false
|
|
@@ -1428,7 +1837,27 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1428
1837
|
canShareCopper,
|
|
1429
1838
|
},
|
|
1430
1839
|
)
|
|
1431
|
-
if (!extendedViaPoints)
|
|
1840
|
+
if (!extendedViaPoints) {
|
|
1841
|
+
this.setInProgressPlans({
|
|
1842
|
+
phase: "reserve-next-dense-boundary-bus",
|
|
1843
|
+
plans: matchedPlans,
|
|
1844
|
+
strategy: "default",
|
|
1845
|
+
unitIndex: denseWorkUnitIndex,
|
|
1846
|
+
unitCount: denseWorkUnitCount,
|
|
1847
|
+
busId: candidateBus.busId,
|
|
1848
|
+
})
|
|
1849
|
+
yield
|
|
1850
|
+
continue
|
|
1851
|
+
}
|
|
1852
|
+
this.setInProgressPlans({
|
|
1853
|
+
phase: "reserve-next-dense-boundary-bus",
|
|
1854
|
+
plans: matchedPlans,
|
|
1855
|
+
strategy: "default",
|
|
1856
|
+
unitIndex: denseWorkUnitIndex,
|
|
1857
|
+
unitCount: denseWorkUnitCount,
|
|
1858
|
+
busId: candidateBus.busId,
|
|
1859
|
+
})
|
|
1860
|
+
yield
|
|
1432
1861
|
const previousFixedViaPoints = fixedViaPointsByConnectionIndex
|
|
1433
1862
|
const previousPlanCount = matchedPlans.length
|
|
1434
1863
|
const laterBuses = remainingBoundaryBuses.filter(
|
|
@@ -1499,7 +1928,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1499
1928
|
}
|
|
1500
1929
|
}
|
|
1501
1930
|
fixedViaPointsByConnectionIndex = candidateFixedViaPoints
|
|
1502
|
-
if (
|
|
1931
|
+
if (yield* routeMatchedBoundaryBusSteps(candidateBus)) {
|
|
1503
1932
|
const candidateLeavesAFeasibleExtension =
|
|
1504
1933
|
laterBuses.length === 0 ||
|
|
1505
1934
|
laterBuses.some((laterBus) => {
|
|
@@ -1536,11 +1965,29 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1536
1965
|
if (candidateLeavesAFeasibleExtension) {
|
|
1537
1966
|
selectedBusIndex = candidateIndex
|
|
1538
1967
|
routedBoundaryBuses.push(candidateBus)
|
|
1968
|
+
this.setInProgressPlans({
|
|
1969
|
+
phase: "route-dense-boundary-buses",
|
|
1970
|
+
plans: matchedPlans,
|
|
1971
|
+
strategy: "default",
|
|
1972
|
+
unitIndex: ++denseWorkUnitIndex,
|
|
1973
|
+
unitCount: denseWorkUnitCount,
|
|
1974
|
+
busId: candidateBus.busId,
|
|
1975
|
+
})
|
|
1976
|
+
yield
|
|
1539
1977
|
break
|
|
1540
1978
|
}
|
|
1541
1979
|
matchedPlans.splice(previousPlanCount)
|
|
1542
1980
|
}
|
|
1543
1981
|
fixedViaPointsByConnectionIndex = previousFixedViaPoints
|
|
1982
|
+
this.setInProgressPlans({
|
|
1983
|
+
phase: "retry-dense-boundary-bus",
|
|
1984
|
+
plans: matchedPlans,
|
|
1985
|
+
strategy: "default",
|
|
1986
|
+
unitIndex: denseWorkUnitIndex,
|
|
1987
|
+
unitCount: denseWorkUnitCount,
|
|
1988
|
+
busId: candidateBus.busId,
|
|
1989
|
+
})
|
|
1990
|
+
yield
|
|
1544
1991
|
}
|
|
1545
1992
|
if (selectedBusIndex < 0) {
|
|
1546
1993
|
matchedRoutingSucceeded = false
|
|
@@ -1598,6 +2045,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1598
2045
|
return true
|
|
1599
2046
|
},
|
|
1600
2047
|
})
|
|
2048
|
+
this.setInProgressPlans({
|
|
2049
|
+
phase: "match-dense-boundary-lengths",
|
|
2050
|
+
plans: matchedLengthResult.plans ?? matchedPlans,
|
|
2051
|
+
strategy: "default",
|
|
2052
|
+
unitIndex: ++denseWorkUnitIndex,
|
|
2053
|
+
unitCount: denseWorkUnitCount,
|
|
2054
|
+
})
|
|
2055
|
+
yield
|
|
1601
2056
|
if (matchedLengthResult.plans) {
|
|
1602
2057
|
matchedPlans = matchedLengthResult.plans
|
|
1603
2058
|
const rematchedViaPoints =
|
|
@@ -1610,6 +2065,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1610
2065
|
} else {
|
|
1611
2066
|
matchedRoutingSucceeded = false
|
|
1612
2067
|
}
|
|
2068
|
+
this.setInProgressPlans({
|
|
2069
|
+
phase: "rematch-dense-via-sites",
|
|
2070
|
+
plans: matchedPlans,
|
|
2071
|
+
strategy: "default",
|
|
2072
|
+
unitIndex: denseWorkUnitIndex,
|
|
2073
|
+
unitCount: denseWorkUnitCount,
|
|
2074
|
+
})
|
|
2075
|
+
yield
|
|
1613
2076
|
}
|
|
1614
2077
|
if (matchedRoutingSucceeded) {
|
|
1615
2078
|
for (const bus of planeBuses) {
|
|
@@ -1637,6 +2100,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1637
2100
|
break
|
|
1638
2101
|
}
|
|
1639
2102
|
matchedPlans.push(...busPlans)
|
|
2103
|
+
this.setInProgressPlans({
|
|
2104
|
+
phase: "route-dense-plane-buses",
|
|
2105
|
+
plans: matchedPlans,
|
|
2106
|
+
strategy: "default",
|
|
2107
|
+
unitIndex: ++denseWorkUnitIndex,
|
|
2108
|
+
unitCount: denseWorkUnitCount,
|
|
2109
|
+
busId: bus.busId,
|
|
2110
|
+
})
|
|
2111
|
+
yield
|
|
1640
2112
|
}
|
|
1641
2113
|
}
|
|
1642
2114
|
if (
|
|
@@ -2003,11 +2475,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2003
2475
|
return bestState
|
|
2004
2476
|
}
|
|
2005
2477
|
|
|
2006
|
-
private
|
|
2478
|
+
private *evaluateAssignmentWithStrategySteps(
|
|
2007
2479
|
assignmentIndex: number,
|
|
2008
2480
|
busLayerAssignments: Readonly<Record<string, string>>,
|
|
2009
2481
|
routingStrategy: RoutingStrategy,
|
|
2010
|
-
): EvaluatedAssignment {
|
|
2482
|
+
): Generator<FanoutWorkYield, EvaluatedAssignment, unknown> {
|
|
2011
2483
|
let plans: AssignmentAttempt["plans"] = []
|
|
2012
2484
|
let failedBusIds: string[] = []
|
|
2013
2485
|
let blockingBusCounts = new Map<string, number>()
|
|
@@ -2026,21 +2498,53 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2026
2498
|
clearance: this.config.clearance,
|
|
2027
2499
|
borderDistribution: this.config.borderDistribution,
|
|
2028
2500
|
}
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
:
|
|
2501
|
+
let singleLayerPlans = routeSingleLayerWithPushAndShove(singleLayerParams)
|
|
2502
|
+
if (!singleLayerPlans && this.config.singleLayerAdaptiveExits) {
|
|
2503
|
+
this.setInProgressPlans({
|
|
2504
|
+
phase: "prepare-single-layer-adaptive-exits",
|
|
2505
|
+
plans,
|
|
2506
|
+
strategy: routingStrategy,
|
|
2507
|
+
})
|
|
2508
|
+
yield
|
|
2509
|
+
this.setInProgressPlans({
|
|
2510
|
+
phase: "route-single-layer-adaptive-exits",
|
|
2511
|
+
plans,
|
|
2512
|
+
strategy: routingStrategy,
|
|
2513
|
+
})
|
|
2514
|
+
this.activeAdaptiveVisualization = null
|
|
2515
|
+
const adaptiveSolver = this.createWorkSolver(
|
|
2516
|
+
"SingleLayerAdaptiveExitSolver",
|
|
2517
|
+
routeSingleLayerWithAdaptiveExitsSteps({
|
|
2518
|
+
...singleLayerParams,
|
|
2519
|
+
availableBoundaryRegions: resolveAvailableBoundaryRegions(
|
|
2520
|
+
this.options.availableCornersAndSides,
|
|
2521
|
+
),
|
|
2522
|
+
onProgress: (visualization, adaptiveStats) => {
|
|
2523
|
+
this.activeAdaptiveVisualization = visualization
|
|
2524
|
+
this.stats = { ...this.stats, ...adaptiveStats }
|
|
2525
|
+
},
|
|
2526
|
+
}),
|
|
2527
|
+
undefined,
|
|
2528
|
+
() => this.visualizeAdaptiveRoutingState(),
|
|
2529
|
+
)
|
|
2530
|
+
singleLayerPlans = (yield {
|
|
2531
|
+
type: "subsolver",
|
|
2532
|
+
solver: adaptiveSolver,
|
|
2533
|
+
}) as FanoutRoutePlan[] | null
|
|
2534
|
+
}
|
|
2039
2535
|
if (singleLayerPlans) {
|
|
2040
2536
|
plans.push(...singleLayerPlans)
|
|
2041
2537
|
} else {
|
|
2042
2538
|
failedBusIds.push(...this.preparedBuses.map((bus) => bus.busId))
|
|
2043
2539
|
}
|
|
2540
|
+
this.setInProgressPlans({
|
|
2541
|
+
phase: "route-single-layer",
|
|
2542
|
+
plans,
|
|
2543
|
+
strategy: routingStrategy,
|
|
2544
|
+
unitIndex: 1,
|
|
2545
|
+
unitCount: 1,
|
|
2546
|
+
})
|
|
2547
|
+
yield
|
|
2044
2548
|
}
|
|
2045
2549
|
const busesInRoutingOrder = [...this.preparedBuses].sort((a, b) => {
|
|
2046
2550
|
const aUsesCoordinatedWinding = busUsesCoordinatedWinding(a)
|
|
@@ -2076,23 +2580,45 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2076
2580
|
)
|
|
2077
2581
|
})
|
|
2078
2582
|
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2583
|
+
let mixedTerminationState: MixedTerminationState | null = null
|
|
2584
|
+
if (!useSingleLayerPushAndShove && routingStrategy === "default") {
|
|
2585
|
+
const denseSolver = this.createWorkSolver(
|
|
2586
|
+
"DenseMixedTerminationSolver",
|
|
2587
|
+
this.routeDenseThroughAllMixedTerminationSteps({
|
|
2588
|
+
busLayerAssignments,
|
|
2589
|
+
busesInRoutingOrder,
|
|
2590
|
+
}),
|
|
2591
|
+
() => {
|
|
2592
|
+
const workUnit = Number(this.stats.workUnit ?? 0)
|
|
2593
|
+
const workUnitCount = Number(this.stats.workUnitCount ?? 0)
|
|
2594
|
+
return workUnitCount > 0 ? workUnit / workUnitCount : 0
|
|
2595
|
+
},
|
|
2596
|
+
)
|
|
2597
|
+
mixedTerminationState = (yield {
|
|
2598
|
+
type: "subsolver",
|
|
2599
|
+
solver: denseSolver,
|
|
2600
|
+
}) as MixedTerminationState | null
|
|
2601
|
+
}
|
|
2086
2602
|
|
|
2087
2603
|
if (mixedTerminationState) {
|
|
2088
2604
|
plans = mixedTerminationState.plans
|
|
2089
2605
|
failedBusIds = mixedTerminationState.failedBusIds
|
|
2606
|
+
this.setInProgressPlans({
|
|
2607
|
+
phase: "route-dense-mixed-terminations",
|
|
2608
|
+
plans,
|
|
2609
|
+
strategy: routingStrategy,
|
|
2610
|
+
unitIndex: this.preparedBuses.length,
|
|
2611
|
+
unitCount: this.preparedBuses.length,
|
|
2612
|
+
})
|
|
2613
|
+
yield
|
|
2090
2614
|
}
|
|
2091
2615
|
|
|
2092
2616
|
let routingPrefixKey = `${routingStrategy}|`
|
|
2617
|
+
let routedBusIndex = 0
|
|
2093
2618
|
for (const bus of useSingleLayerPushAndShove || mixedTerminationState
|
|
2094
2619
|
? []
|
|
2095
2620
|
: busesInRoutingOrder) {
|
|
2621
|
+
routedBusIndex++
|
|
2096
2622
|
const targetLayer = busLayerAssignments[bus.busId]
|
|
2097
2623
|
if (!targetLayer) {
|
|
2098
2624
|
throw new Error(
|
|
@@ -2110,6 +2636,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2110
2636
|
plans = [...cachedPrefix.plans]
|
|
2111
2637
|
failedBusIds = [...cachedPrefix.failedBusIds]
|
|
2112
2638
|
blockingBusCounts = new Map(cachedPrefix.blockingBusCounts)
|
|
2639
|
+
this.setInProgressPlans({
|
|
2640
|
+
phase: "route-assignment",
|
|
2641
|
+
plans,
|
|
2642
|
+
strategy: routingStrategy,
|
|
2643
|
+
unitIndex: routedBusIndex,
|
|
2644
|
+
unitCount: busesInRoutingOrder.length,
|
|
2645
|
+
busId: bus.busId,
|
|
2646
|
+
})
|
|
2647
|
+
yield
|
|
2113
2648
|
continue
|
|
2114
2649
|
}
|
|
2115
2650
|
const currentBusBlockingCounts = new Map<string, number>()
|
|
@@ -2145,6 +2680,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2145
2680
|
failedBusIds: [...failedBusIds],
|
|
2146
2681
|
blockingBusCounts: new Map(blockingBusCounts),
|
|
2147
2682
|
})
|
|
2683
|
+
this.setInProgressPlans({
|
|
2684
|
+
phase: "route-assignment",
|
|
2685
|
+
plans,
|
|
2686
|
+
strategy: routingStrategy,
|
|
2687
|
+
unitIndex: routedBusIndex,
|
|
2688
|
+
unitCount: busesInRoutingOrder.length,
|
|
2689
|
+
busId: bus.busId,
|
|
2690
|
+
})
|
|
2691
|
+
yield
|
|
2148
2692
|
}
|
|
2149
2693
|
|
|
2150
2694
|
let validationIssues: FanoutAttemptSummary["validationIssues"]
|
|
@@ -2218,6 +2762,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2218
2762
|
score,
|
|
2219
2763
|
...(validationIssues ? { validationIssues } : {}),
|
|
2220
2764
|
}
|
|
2765
|
+
this.setInProgressPlans({
|
|
2766
|
+
phase: "finalize-assignment-strategy",
|
|
2767
|
+
plans,
|
|
2768
|
+
strategy: routingStrategy,
|
|
2769
|
+
})
|
|
2221
2770
|
return {
|
|
2222
2771
|
summary,
|
|
2223
2772
|
plans,
|
|
@@ -2228,11 +2777,11 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2228
2777
|
}
|
|
2229
2778
|
}
|
|
2230
2779
|
|
|
2231
|
-
private
|
|
2780
|
+
private *evaluateAssignmentSteps(
|
|
2232
2781
|
assignmentIndex: number,
|
|
2233
2782
|
busLayerAssignments: Readonly<Record<string, string>>,
|
|
2234
|
-
): EvaluatedAssignment {
|
|
2235
|
-
let bestAttempt = this.
|
|
2783
|
+
): Generator<FanoutWorkYield, EvaluatedAssignment, unknown> {
|
|
2784
|
+
let bestAttempt = yield* this.evaluateAssignmentWithStrategySteps(
|
|
2236
2785
|
assignmentIndex,
|
|
2237
2786
|
busLayerAssignments,
|
|
2238
2787
|
"default",
|
|
@@ -2246,7 +2795,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2246
2795
|
}
|
|
2247
2796
|
|
|
2248
2797
|
for (const routingStrategy of ["group-by-layer", "deep-first"] as const) {
|
|
2249
|
-
const attempt = this.
|
|
2798
|
+
const attempt = yield* this.evaluateAssignmentWithStrategySteps(
|
|
2250
2799
|
assignmentIndex,
|
|
2251
2800
|
busLayerAssignments,
|
|
2252
2801
|
routingStrategy,
|
|
@@ -2274,10 +2823,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2274
2823
|
* power or signal lane cannot starve a later bus before the solver explores
|
|
2275
2824
|
* an alternate layer/track combination.
|
|
2276
2825
|
*/
|
|
2277
|
-
private
|
|
2826
|
+
private *evaluateGroupedBeamSteps(
|
|
2278
2827
|
assignmentIndex: number,
|
|
2279
2828
|
groupByDirection = false,
|
|
2280
|
-
): EvaluatedAssignment | null {
|
|
2829
|
+
): Generator<FanoutWorkYield, EvaluatedAssignment | null, unknown> {
|
|
2281
2830
|
if (this.config.escapeLayers.length < 2) return null
|
|
2282
2831
|
if (this.preparedBuses.length > 56) return null
|
|
2283
2832
|
const totalConnections = this.inputSrj.connections.length
|
|
@@ -2386,7 +2935,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2386
2935
|
)
|
|
2387
2936
|
}
|
|
2388
2937
|
|
|
2938
|
+
let searchedBusIndex = 0
|
|
2389
2939
|
for (const bus of busesInSearchOrder) {
|
|
2940
|
+
searchedBusIndex++
|
|
2390
2941
|
const nextStates: GroupedBeamState[] = []
|
|
2391
2942
|
for (const state of states) {
|
|
2392
2943
|
const candidateLayers =
|
|
@@ -2474,6 +3025,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2474
3025
|
states.push(state)
|
|
2475
3026
|
if (states.length >= beamWidth) break
|
|
2476
3027
|
}
|
|
3028
|
+
this.setInProgressPlans({
|
|
3029
|
+
phase: "route-grouped-beam",
|
|
3030
|
+
plans: states[0]?.plans ?? [],
|
|
3031
|
+
strategy: "grouped-beam",
|
|
3032
|
+
unitIndex: searchedBusIndex,
|
|
3033
|
+
unitCount: busesInSearchOrder.length,
|
|
3034
|
+
busId: bus.busId,
|
|
3035
|
+
})
|
|
3036
|
+
yield
|
|
2477
3037
|
}
|
|
2478
3038
|
|
|
2479
3039
|
let bestState: GroupedBeamState | undefined
|
|
@@ -2493,9 +3053,15 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2493
3053
|
(bus) => bus.maxLengthSkew !== undefined,
|
|
2494
3054
|
)
|
|
2495
3055
|
for (const state of states) {
|
|
2496
|
-
if (state.plans.length !== this.inputSrj.connections.length)
|
|
3056
|
+
if (state.plans.length !== this.inputSrj.connections.length) {
|
|
3057
|
+
yield
|
|
3058
|
+
continue
|
|
3059
|
+
}
|
|
2497
3060
|
const lengthMatching = this.matchCompletePlanLengths(state.plans)
|
|
2498
|
-
if (!lengthMatching.plans)
|
|
3061
|
+
if (!lengthMatching.plans) {
|
|
3062
|
+
yield
|
|
3063
|
+
continue
|
|
3064
|
+
}
|
|
2499
3065
|
const lengthMatchedPlans = lengthMatching.plans
|
|
2500
3066
|
const candidateOutput = buildOutputSimpleRouteJson({
|
|
2501
3067
|
inputSrj: this.inputSrj,
|
|
@@ -2505,6 +3071,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2505
3071
|
if (
|
|
2506
3072
|
!this.validateCompletePlans(lengthMatchedPlans, candidateOutput).valid
|
|
2507
3073
|
) {
|
|
3074
|
+
yield
|
|
2508
3075
|
continue
|
|
2509
3076
|
}
|
|
2510
3077
|
const candidateState = { ...state, plans: lengthMatchedPlans }
|
|
@@ -2523,6 +3090,12 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2523
3090
|
bestAdditionalViaCount = candidateAdditionalViaCount
|
|
2524
3091
|
}
|
|
2525
3092
|
if (!hasLengthConstraints) break
|
|
3093
|
+
this.setInProgressPlans({
|
|
3094
|
+
phase: "validate-grouped-beam",
|
|
3095
|
+
plans: lengthMatchedPlans,
|
|
3096
|
+
strategy: "grouped-beam",
|
|
3097
|
+
})
|
|
3098
|
+
yield
|
|
2526
3099
|
}
|
|
2527
3100
|
if (!bestState || !outputSrj) return null
|
|
2528
3101
|
const score = bestMatchedScore
|
|
@@ -2544,6 +3117,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2544
3117
|
}
|
|
2545
3118
|
}
|
|
2546
3119
|
|
|
3120
|
+
private *evaluateGroupedBeamAlternativesSteps(
|
|
3121
|
+
assignmentIndex: number,
|
|
3122
|
+
): Generator<FanoutWorkYield, EvaluatedAssignment | null, unknown> {
|
|
3123
|
+
const primaryAttempt = yield* this.evaluateGroupedBeamSteps(assignmentIndex)
|
|
3124
|
+
if (primaryAttempt) return primaryAttempt
|
|
3125
|
+
return yield* this.evaluateGroupedBeamSteps(assignmentIndex, true)
|
|
3126
|
+
}
|
|
3127
|
+
|
|
2547
3128
|
private prioritizeFailedBusRepairs(
|
|
2548
3129
|
assignment: Readonly<Record<string, string>>,
|
|
2549
3130
|
failedBusIds: readonly string[],
|
|
@@ -2710,52 +3291,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2710
3291
|
return targetedRepairSearchFinished
|
|
2711
3292
|
}
|
|
2712
3293
|
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
) {
|
|
2718
|
-
this.completeBestAttemptEndpoints()
|
|
2719
|
-
this.solved = true
|
|
2720
|
-
return
|
|
2721
|
-
}
|
|
2722
|
-
// Try the deterministic assignment and only its targeted repair queue
|
|
2723
|
-
// before paying for the grouped beam. If the beam cannot solve, continue
|
|
2724
|
-
// with the broader generated-assignment search below.
|
|
2725
|
-
if (this.shouldEvaluateGroupedBeam()) {
|
|
2726
|
-
this.groupedBeamEvaluated = true
|
|
2727
|
-
let beamAttempt = this.evaluateGroupedBeam(-1)
|
|
2728
|
-
if (!beamAttempt) {
|
|
2729
|
-
beamAttempt = this.evaluateGroupedBeam(-1, true)
|
|
2730
|
-
}
|
|
2731
|
-
if (beamAttempt) {
|
|
2732
|
-
this.attempts.push(beamAttempt.summary)
|
|
2733
|
-
if (
|
|
2734
|
-
!this.bestAttempt ||
|
|
2735
|
-
this.isAttemptBetter(beamAttempt, this.bestAttempt)
|
|
2736
|
-
) {
|
|
2737
|
-
this.bestAttempt = beamAttempt
|
|
2738
|
-
}
|
|
2739
|
-
const bestSummary = this.bestAttempt.summary
|
|
2740
|
-
this.stats = {
|
|
2741
|
-
assignment:
|
|
2742
|
-
bestSummary.assignmentIndex < 0
|
|
2743
|
-
? 0
|
|
2744
|
-
: bestSummary.assignmentIndex + 1,
|
|
2745
|
-
assignmentCount: this.config.maxLayerCombinations,
|
|
2746
|
-
routedBuses: `${bestSummary.routedBusCount}/${this.preparedBuses.length}`,
|
|
2747
|
-
routedConnections: `${bestSummary.routedConnectionCount}/${this.inputSrj.connections.length}`,
|
|
2748
|
-
failedBuses: "none",
|
|
2749
|
-
bestScore: bestSummary.score,
|
|
2750
|
-
}
|
|
2751
|
-
if (
|
|
2752
|
-
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
2753
|
-
) {
|
|
2754
|
-
this.completeBestAttemptEndpoints()
|
|
2755
|
-
this.solved = true
|
|
2756
|
-
return
|
|
2757
|
-
}
|
|
2758
|
-
}
|
|
3294
|
+
private commitGroupedBeamAttempt(
|
|
3295
|
+
beamAttempt: EvaluatedAssignment | null,
|
|
3296
|
+
): void {
|
|
3297
|
+
if (!beamAttempt) {
|
|
2759
3298
|
if (
|
|
2760
3299
|
this.hasCompleteBestAttempt() &&
|
|
2761
3300
|
this.bestAttempt &&
|
|
@@ -2763,10 +3302,75 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2763
3302
|
) {
|
|
2764
3303
|
this.completeBestAttemptEndpoints()
|
|
2765
3304
|
this.solved = true
|
|
2766
|
-
return
|
|
2767
3305
|
}
|
|
3306
|
+
return
|
|
3307
|
+
}
|
|
3308
|
+
this.attempts.push(beamAttempt.summary)
|
|
3309
|
+
if (
|
|
3310
|
+
!this.bestAttempt ||
|
|
3311
|
+
this.isAttemptBetter(beamAttempt, this.bestAttempt)
|
|
3312
|
+
) {
|
|
3313
|
+
this.bestAttempt = beamAttempt
|
|
3314
|
+
}
|
|
3315
|
+
const bestSummary = this.bestAttempt.summary
|
|
3316
|
+
this.stats = {
|
|
3317
|
+
phase: "complete-grouped-beam",
|
|
3318
|
+
assignment:
|
|
3319
|
+
bestSummary.assignmentIndex < 0 ? 0 : bestSummary.assignmentIndex + 1,
|
|
3320
|
+
assignmentCount: this.config.maxLayerCombinations,
|
|
3321
|
+
routedBuses: `${bestSummary.routedBusCount}/${this.preparedBuses.length}`,
|
|
3322
|
+
routedConnections: `${bestSummary.routedConnectionCount}/${this.inputSrj.connections.length}`,
|
|
3323
|
+
failedBuses: "none",
|
|
3324
|
+
bestScore: bestSummary.score,
|
|
3325
|
+
}
|
|
3326
|
+
if (this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0) {
|
|
3327
|
+
this.completeBestAttemptEndpoints()
|
|
3328
|
+
this.solved = true
|
|
2768
3329
|
}
|
|
3330
|
+
}
|
|
2769
3331
|
|
|
3332
|
+
private commitAssignmentAttempt(
|
|
3333
|
+
assignment: Readonly<Record<string, string>>,
|
|
3334
|
+
attempt: EvaluatedAssignment,
|
|
3335
|
+
): void {
|
|
3336
|
+
this.nextAssignmentIndex++
|
|
3337
|
+
this.evaluatedAssignmentKeys.add(JSON.stringify(assignment))
|
|
3338
|
+
if (
|
|
3339
|
+
!this.bestAttempt ||
|
|
3340
|
+
attempt.summary.routedConnectionCount >=
|
|
3341
|
+
this.bestAttempt.summary.routedConnectionCount
|
|
3342
|
+
) {
|
|
3343
|
+
this.prioritizeFailedBusRepairs(
|
|
3344
|
+
assignment,
|
|
3345
|
+
attempt.summary.failedBusIds,
|
|
3346
|
+
attempt.blockingBusIds,
|
|
3347
|
+
)
|
|
3348
|
+
}
|
|
3349
|
+
this.attempts.push(attempt.summary)
|
|
3350
|
+
if (!this.bestAttempt || this.isAttemptBetter(attempt, this.bestAttempt)) {
|
|
3351
|
+
this.bestAttempt = attempt
|
|
3352
|
+
}
|
|
3353
|
+
this.stats = {
|
|
3354
|
+
phase: "complete-assignment",
|
|
3355
|
+
assignment: attempt.summary.assignmentIndex + 1,
|
|
3356
|
+
assignmentCount: this.config.maxLayerCombinations,
|
|
3357
|
+
routedBuses: `${attempt.summary.routedBusCount}/${this.preparedBuses.length}`,
|
|
3358
|
+
routedConnections: `${attempt.summary.routedConnectionCount}/${this.inputSrj.connections.length}`,
|
|
3359
|
+
failedBuses: attempt.summary.failedBusIds.join(", ") || "none",
|
|
3360
|
+
bestScore: this.bestAttempt.summary.score,
|
|
3361
|
+
}
|
|
3362
|
+
if (
|
|
3363
|
+
this.groupedBeamEvaluated &&
|
|
3364
|
+
attempt.summary.routedConnectionCount ===
|
|
3365
|
+
this.inputSrj.connections.length &&
|
|
3366
|
+
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
3367
|
+
) {
|
|
3368
|
+
this.completeBestAttemptEndpoints()
|
|
3369
|
+
this.solved = true
|
|
3370
|
+
}
|
|
3371
|
+
}
|
|
3372
|
+
|
|
3373
|
+
private getNextAssignment(): Readonly<Record<string, string>> | undefined {
|
|
2770
3374
|
let assignment: Readonly<Record<string, string>> | undefined
|
|
2771
3375
|
while (
|
|
2772
3376
|
!assignment &&
|
|
@@ -2802,68 +3406,138 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2802
3406
|
if (this.evaluatedAssignmentKeys.has(candidateKey)) continue
|
|
2803
3407
|
assignment = candidate
|
|
2804
3408
|
}
|
|
2805
|
-
|
|
2806
|
-
|
|
2807
|
-
|
|
2808
|
-
|
|
2809
|
-
|
|
2810
|
-
|
|
2811
|
-
|
|
2812
|
-
const validationMessage =
|
|
2813
|
-
this.lengthMatchingFailure?.message ??
|
|
2814
|
-
this.bestAttempt?.summary.validationIssues?.[0]?.message
|
|
2815
|
-
this.error = validationMessage
|
|
2816
|
-
? `FanoutSolver: ${validationMessage}`
|
|
2817
|
-
: this.bestAttempt
|
|
2818
|
-
? `FanoutSolver: best layer assignment routed ${this.bestAttempt.summary.routedConnectionCount}/${this.inputSrj.connections.length} connections`
|
|
2819
|
-
: "FanoutSolver: no layer assignment could be evaluated"
|
|
2820
|
-
}
|
|
3409
|
+
return assignment
|
|
3410
|
+
}
|
|
3411
|
+
|
|
3412
|
+
private finishWithoutAnotherAssignment(): void {
|
|
3413
|
+
if (this.hasCompleteBestAttempt()) {
|
|
3414
|
+
this.completeBestAttemptEndpoints()
|
|
3415
|
+
this.solved = true
|
|
2821
3416
|
return
|
|
2822
3417
|
}
|
|
3418
|
+
this.failed = true
|
|
3419
|
+
const validationMessage =
|
|
3420
|
+
this.lengthMatchingFailure?.message ??
|
|
3421
|
+
this.bestAttempt?.summary.validationIssues?.[0]?.message
|
|
3422
|
+
this.error = validationMessage
|
|
3423
|
+
? `FanoutSolver: ${validationMessage}`
|
|
3424
|
+
: this.bestAttempt
|
|
3425
|
+
? `FanoutSolver: best layer assignment routed ${this.bestAttempt.summary.routedConnectionCount}/${this.inputSrj.connections.length} connections`
|
|
3426
|
+
: "FanoutSolver: no layer assignment could be evaluated"
|
|
3427
|
+
}
|
|
2823
3428
|
|
|
2824
|
-
|
|
2825
|
-
|
|
2826
|
-
|
|
2827
|
-
|
|
2828
|
-
this.nextAssignmentIndex++
|
|
2829
|
-
this.evaluatedAssignmentKeys.add(JSON.stringify(assignment))
|
|
2830
|
-
if (
|
|
2831
|
-
!this.bestAttempt ||
|
|
2832
|
-
attempt.summary.routedConnectionCount >=
|
|
2833
|
-
this.bestAttempt.summary.routedConnectionCount
|
|
2834
|
-
) {
|
|
2835
|
-
this.prioritizeFailedBusRepairs(
|
|
2836
|
-
assignment,
|
|
2837
|
-
attempt.summary.failedBusIds,
|
|
2838
|
-
attempt.blockingBusIds,
|
|
2839
|
-
)
|
|
2840
|
-
}
|
|
2841
|
-
this.attempts.push(attempt.summary)
|
|
2842
|
-
if (!this.bestAttempt || this.isAttemptBetter(attempt, this.bestAttempt)) {
|
|
2843
|
-
this.bestAttempt = attempt
|
|
3429
|
+
override _step(): void {
|
|
3430
|
+
if (this.activeOperation) {
|
|
3431
|
+
this.stepActiveOperation()
|
|
3432
|
+
return
|
|
2844
3433
|
}
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
3434
|
+
|
|
3435
|
+
if (!this.routingInitialized) {
|
|
3436
|
+
this.startOperation({
|
|
3437
|
+
name: "FanoutCandidateLayerSolver",
|
|
3438
|
+
generator: this.initializeRoutingSteps(),
|
|
3439
|
+
onSolved: () => {},
|
|
3440
|
+
getProgress: () =>
|
|
3441
|
+
this.nextCandidateLayerBusIndex /
|
|
3442
|
+
Math.max(1, this.boundaryBuses.length + 1),
|
|
3443
|
+
})
|
|
3444
|
+
return
|
|
2852
3445
|
}
|
|
3446
|
+
|
|
2853
3447
|
if (
|
|
2854
|
-
this.
|
|
2855
|
-
|
|
2856
|
-
this.inputSrj.connections.length &&
|
|
2857
|
-
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
3448
|
+
this.nextAssignmentIndex > 0 &&
|
|
3449
|
+
this.hasGloballyViaMinimalBestAttempt()
|
|
2858
3450
|
) {
|
|
2859
3451
|
this.completeBestAttemptEndpoints()
|
|
2860
3452
|
this.solved = true
|
|
3453
|
+
return
|
|
3454
|
+
}
|
|
3455
|
+
// Try the deterministic assignment and only its targeted repair queue
|
|
3456
|
+
// before paying for the grouped beam. If the beam cannot solve, continue
|
|
3457
|
+
// with the broader generated-assignment search below.
|
|
3458
|
+
if (this.shouldEvaluateGroupedBeam()) {
|
|
3459
|
+
this.groupedBeamEvaluated = true
|
|
3460
|
+
this.startOperation({
|
|
3461
|
+
name: "FanoutGroupedBeamSolver",
|
|
3462
|
+
generator: this.evaluateGroupedBeamAlternativesSteps(-1),
|
|
3463
|
+
onSolved: (attempt) => this.commitGroupedBeamAttempt(attempt),
|
|
3464
|
+
getProgress: () => {
|
|
3465
|
+
const workUnit = Number(this.stats.workUnit ?? 0)
|
|
3466
|
+
const workUnitCount = Number(this.stats.workUnitCount ?? 0)
|
|
3467
|
+
return workUnitCount > 0 ? workUnit / workUnitCount : 0
|
|
3468
|
+
},
|
|
3469
|
+
})
|
|
3470
|
+
this.stats = { ...this.stats, phase: "prepare-grouped-beam" }
|
|
3471
|
+
return
|
|
3472
|
+
}
|
|
3473
|
+
|
|
3474
|
+
const assignment = this.getNextAssignment()
|
|
3475
|
+
if (!assignment && !this.groupedBeamEvaluated) return
|
|
3476
|
+
if (!assignment) {
|
|
3477
|
+
this.finishWithoutAnotherAssignment()
|
|
3478
|
+
return
|
|
3479
|
+
}
|
|
3480
|
+
|
|
3481
|
+
this.startOperation({
|
|
3482
|
+
name: "FanoutAssignmentSolver",
|
|
3483
|
+
generator: this.evaluateAssignmentSteps(
|
|
3484
|
+
this.nextAssignmentIndex,
|
|
3485
|
+
assignment,
|
|
3486
|
+
),
|
|
3487
|
+
onSolved: (attempt) => this.commitAssignmentAttempt(assignment, attempt),
|
|
3488
|
+
getProgress: () => {
|
|
3489
|
+
const strategyIndex =
|
|
3490
|
+
this.stats.routingStrategy === "group-by-layer"
|
|
3491
|
+
? 1
|
|
3492
|
+
: this.stats.routingStrategy === "deep-first"
|
|
3493
|
+
? 2
|
|
3494
|
+
: 0
|
|
3495
|
+
const workUnit = Number(this.stats.workUnit ?? 0)
|
|
3496
|
+
const workUnitCount = Number(this.stats.workUnitCount ?? 0)
|
|
3497
|
+
const strategyFraction =
|
|
3498
|
+
workUnitCount > 0 ? Math.min(1, workUnit / workUnitCount) : 0
|
|
3499
|
+
return (strategyIndex + strategyFraction) / 3
|
|
3500
|
+
},
|
|
3501
|
+
})
|
|
3502
|
+
this.stats = {
|
|
3503
|
+
...this.stats,
|
|
3504
|
+
phase: "prepare-assignment",
|
|
3505
|
+
assignment: this.nextAssignmentIndex + 1,
|
|
3506
|
+
assignmentCount: this.config.maxLayerCombinations,
|
|
3507
|
+
routedConnections: `0/${this.inputSrj.connections.length}`,
|
|
2861
3508
|
}
|
|
2862
3509
|
}
|
|
2863
3510
|
|
|
2864
3511
|
computeProgress(): number {
|
|
2865
3512
|
if (this.solved || this.failed) return 1
|
|
2866
|
-
|
|
3513
|
+
if (!this.routingInitialized) {
|
|
3514
|
+
return (
|
|
3515
|
+
0.05 *
|
|
3516
|
+
(this.nextCandidateLayerBusIndex /
|
|
3517
|
+
Math.max(1, this.boundaryBuses.length + 1))
|
|
3518
|
+
)
|
|
3519
|
+
}
|
|
3520
|
+
let activeAssignmentFraction = 0
|
|
3521
|
+
if (this.activeSubSolver?.getSolverName() === "FanoutAssignmentSolver") {
|
|
3522
|
+
const strategyIndex =
|
|
3523
|
+
this.stats.routingStrategy === "group-by-layer"
|
|
3524
|
+
? 1
|
|
3525
|
+
: this.stats.routingStrategy === "deep-first"
|
|
3526
|
+
? 2
|
|
3527
|
+
: 0
|
|
3528
|
+
const workUnit = Number(this.stats.workUnit ?? 0)
|
|
3529
|
+
const workUnitCount = Number(this.stats.workUnitCount ?? 0)
|
|
3530
|
+
const strategyFraction =
|
|
3531
|
+
workUnitCount > 0 ? Math.min(1, workUnit / workUnitCount) : 0
|
|
3532
|
+
activeAssignmentFraction = (strategyIndex + strategyFraction) / 3
|
|
3533
|
+
}
|
|
3534
|
+
return Math.min(
|
|
3535
|
+
0.99,
|
|
3536
|
+
0.05 +
|
|
3537
|
+
0.95 *
|
|
3538
|
+
((this.nextAssignmentIndex + activeAssignmentFraction) /
|
|
3539
|
+
this.config.maxLayerCombinations),
|
|
3540
|
+
)
|
|
2867
3541
|
}
|
|
2868
3542
|
|
|
2869
3543
|
override getConstructorParams(): [SimpleRouteJson, FanoutSolverOptions] {
|
|
@@ -2937,10 +3611,6 @@ export class FanoutSolver extends BaseSolver {
|
|
|
2937
3611
|
}
|
|
2938
3612
|
|
|
2939
3613
|
override visualize(): GraphicsObject {
|
|
2940
|
-
|
|
2941
|
-
this.endpointCompletion?.simpleRouteJson ??
|
|
2942
|
-
this.bestAttempt?.outputSrj ??
|
|
2943
|
-
this.inputSrj
|
|
2944
|
-
return visualizeSimpleRouteJson(visualizedSrj)
|
|
3614
|
+
return this.activeSubSolver?.visualize() ?? this.visualizeCurrentState()
|
|
2945
3615
|
}
|
|
2946
3616
|
}
|