@tscircuit/fanout-solver 0.0.36 → 0.0.37
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/build-output.ts +32 -10
- package/lib/fanout-solver.ts +214 -30
- package/lib/prepare-buses.ts +6 -2
- package/lib/route-bus.ts +378 -81
- package/lib/route-via-minimal-winding.ts +898 -0
- package/lib/types.ts +14 -3
- package/lib/validate-fanout-solution.ts +12 -10
- package/package.json +1 -1
package/lib/build-output.ts
CHANGED
|
@@ -5,10 +5,9 @@ import type { FanoutRoutePlan, SimpleRouteJsonWithFanoutPlanes } from "./types"
|
|
|
5
5
|
function createViaObstacle(
|
|
6
6
|
plan: FanoutRoutePlan,
|
|
7
7
|
layerNames: string[],
|
|
8
|
-
|
|
8
|
+
via: NonNullable<FanoutRoutePlan["via"]>,
|
|
9
|
+
viaIndex: number | "endpoint",
|
|
9
10
|
): Obstacle | null {
|
|
10
|
-
const via = endpoint ? plan.planeEndpointVia : plan.via
|
|
11
|
-
if (!via) return null
|
|
12
11
|
const zLayers = via.spanLayers.map((layer) => {
|
|
13
12
|
const layerIndex = layerNames.indexOf(layer)
|
|
14
13
|
if (layerIndex < 0) {
|
|
@@ -20,9 +19,12 @@ function createViaObstacle(
|
|
|
20
19
|
})
|
|
21
20
|
const outputIds = createFanoutOutputIds(plan)
|
|
22
21
|
return {
|
|
23
|
-
obstacleId:
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
obstacleId:
|
|
23
|
+
viaIndex === "endpoint"
|
|
24
|
+
? outputIds.planeEndpointViaObstacleId
|
|
25
|
+
: viaIndex === 0
|
|
26
|
+
? outputIds.viaObstacleId
|
|
27
|
+
: `${outputIds.viaObstacleId}:${viaIndex}`,
|
|
26
28
|
type: "rect",
|
|
27
29
|
center: via.center,
|
|
28
30
|
width: via.diameter,
|
|
@@ -67,10 +69,30 @@ export function buildOutputSimpleRouteJson(params: {
|
|
|
67
69
|
if (plan.termination.type === "plane") {
|
|
68
70
|
planeTerminatedConnectionNames.add(plan.connectionName)
|
|
69
71
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
72
|
+
if (plan.via) {
|
|
73
|
+
const viaObstacle = createViaObstacle(plan, layerNames, plan.via, 0)
|
|
74
|
+
if (viaObstacle) viaObstacles.push(viaObstacle)
|
|
75
|
+
}
|
|
76
|
+
for (const [additionalViaIndex, via] of (
|
|
77
|
+
plan.additionalVias ?? []
|
|
78
|
+
).entries()) {
|
|
79
|
+
const viaObstacle = createViaObstacle(
|
|
80
|
+
plan,
|
|
81
|
+
layerNames,
|
|
82
|
+
via,
|
|
83
|
+
additionalViaIndex + 1,
|
|
84
|
+
)
|
|
85
|
+
if (viaObstacle) viaObstacles.push(viaObstacle)
|
|
86
|
+
}
|
|
87
|
+
if (plan.planeEndpointVia) {
|
|
88
|
+
const endpointViaObstacle = createViaObstacle(
|
|
89
|
+
plan,
|
|
90
|
+
layerNames,
|
|
91
|
+
plan.planeEndpointVia,
|
|
92
|
+
"endpoint",
|
|
93
|
+
)
|
|
94
|
+
if (endpointViaObstacle) viaObstacles.push(endpointViaObstacle)
|
|
95
|
+
}
|
|
74
96
|
}
|
|
75
97
|
const planTraces = plans.flatMap((plan) => [
|
|
76
98
|
plan.trace,
|
package/lib/fanout-solver.ts
CHANGED
|
@@ -219,6 +219,7 @@ function getPlanViaCount(plans: readonly FanoutRoutePlan[]): number {
|
|
|
219
219
|
(count, plan) =>
|
|
220
220
|
count +
|
|
221
221
|
Number(Boolean(plan.via)) +
|
|
222
|
+
(plan.additionalVias?.length ?? 0) +
|
|
222
223
|
Number(Boolean(plan.planeEndpointVia)),
|
|
223
224
|
0,
|
|
224
225
|
)
|
|
@@ -257,6 +258,27 @@ function busUsesDestinationGuidedTracks(bus: PreparedBus): boolean {
|
|
|
257
258
|
})
|
|
258
259
|
}
|
|
259
260
|
|
|
261
|
+
function getCommonExplicitExitTargetLayer(
|
|
262
|
+
bus: PreparedBus,
|
|
263
|
+
): string | undefined {
|
|
264
|
+
if (
|
|
265
|
+
bus.connections.length === 0 ||
|
|
266
|
+
bus.connections.some(
|
|
267
|
+
(connection) =>
|
|
268
|
+
!connection.hasExplicitLayeredExitTarget ||
|
|
269
|
+
!connection.exitTargetPoint?.layer,
|
|
270
|
+
)
|
|
271
|
+
) {
|
|
272
|
+
return undefined
|
|
273
|
+
}
|
|
274
|
+
const targetLayers = new Set(
|
|
275
|
+
bus.connections.map((connection) => connection.exitTargetPoint!.layer!),
|
|
276
|
+
)
|
|
277
|
+
if (targetLayers.size !== 1) return undefined
|
|
278
|
+
const [targetLayer] = targetLayers
|
|
279
|
+
return targetLayer
|
|
280
|
+
}
|
|
281
|
+
|
|
260
282
|
function busIsOnOutwardComponentEdge(bus: PreparedBus): boolean {
|
|
261
283
|
const isHorizontal = bus.direction === "left" || bus.direction === "right"
|
|
262
284
|
const directionalCoordinates = isHorizontal
|
|
@@ -327,7 +349,14 @@ function createInitialLayerAssignment(params: {
|
|
|
327
349
|
const viaLayers = routableEscapeLayers.filter(
|
|
328
350
|
(layer) => layer !== sourceLayer,
|
|
329
351
|
)
|
|
352
|
+
const commonExitTargetLayer = getCommonExplicitExitTargetLayer(bus)
|
|
330
353
|
if (
|
|
354
|
+
commonExitTargetLayer &&
|
|
355
|
+
routableEscapeLayers.includes(commonExitTargetLayer)
|
|
356
|
+
) {
|
|
357
|
+
assignment[bus.busId] = commonExitTargetLayer
|
|
358
|
+
} else if (
|
|
359
|
+
!busUsesCoordinatedWinding(bus) &&
|
|
331
360
|
routableEscapeLayers.includes(sourceLayer) &&
|
|
332
361
|
(busUsesDestinationGuidedTracks(bus) || busIsOnOutwardComponentEdge(bus))
|
|
333
362
|
) {
|
|
@@ -368,6 +397,17 @@ function prioritizeLayerAssignment(params: {
|
|
|
368
397
|
].slice(0, maxAssignments)
|
|
369
398
|
}
|
|
370
399
|
|
|
400
|
+
function busUsesCoordinatedWinding(bus: PreparedBus): boolean {
|
|
401
|
+
return Boolean(
|
|
402
|
+
bus.exitEdge &&
|
|
403
|
+
bus.termination.type === "boundary" &&
|
|
404
|
+
bus.connections.length > 0 &&
|
|
405
|
+
bus.connections.every(
|
|
406
|
+
(connection) => connection.hasExplicitLayeredExitTarget === true,
|
|
407
|
+
),
|
|
408
|
+
)
|
|
409
|
+
}
|
|
410
|
+
|
|
371
411
|
function getCandidateEscapeLayersForBus(params: {
|
|
372
412
|
bus: PreparedBus
|
|
373
413
|
srj: SimpleRouteJson
|
|
@@ -380,6 +420,10 @@ function getCandidateEscapeLayersForBus(params: {
|
|
|
380
420
|
busAllowedLayers === undefined
|
|
381
421
|
? config.escapeLayers
|
|
382
422
|
: config.escapeLayers.filter((layer) => busAllowedLayers.includes(layer))
|
|
423
|
+
// A coordinated winding route is deliberately planned with the other buses'
|
|
424
|
+
// committed escape vias present. Testing it in isolation is both expensive
|
|
425
|
+
// and can reject a layer whose shared via field guides a valid bus ordering.
|
|
426
|
+
if (busUsesCoordinatedWinding(bus)) return allowedEscapeLayers
|
|
383
427
|
const individuallyRoutableLayers = allowedEscapeLayers.filter(
|
|
384
428
|
(targetLayer) =>
|
|
385
429
|
routeBus({
|
|
@@ -453,6 +497,19 @@ export class FanoutSolver extends BaseSolver {
|
|
|
453
497
|
this.preparedBuses = prepareFanoutBuses(this.routingSrj, options)
|
|
454
498
|
validateCornerBandCapacities(this.preparedBuses, this.config)
|
|
455
499
|
for (const bus of this.preparedBuses) {
|
|
500
|
+
for (const connection of bus.connections) {
|
|
501
|
+
if (!connection.hasExplicitLayeredExitTarget) continue
|
|
502
|
+
const targetLayer = connection.exitTargetPoint?.layer
|
|
503
|
+
if (
|
|
504
|
+
typeof targetLayer !== "string" ||
|
|
505
|
+
targetLayer.length === 0 ||
|
|
506
|
+
!this.config.layerNames.includes(targetLayer)
|
|
507
|
+
) {
|
|
508
|
+
throw new Error(
|
|
509
|
+
`FanoutSolver: connection exit target for "${connection.connection.name}" uses unavailable layer "${String(targetLayer)}"`,
|
|
510
|
+
)
|
|
511
|
+
}
|
|
512
|
+
}
|
|
456
513
|
for (const allowedLayer of bus.allowedLayers ?? []) {
|
|
457
514
|
if (!this.config.layerNames.includes(allowedLayer)) {
|
|
458
515
|
throw new Error(
|
|
@@ -471,6 +528,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
471
528
|
`FanoutSolver: bus "${bus.busId}" has no allowed layer in escapeLayers`,
|
|
472
529
|
)
|
|
473
530
|
}
|
|
531
|
+
bus.routableEscapeLayers = this.config.escapeLayers.filter(
|
|
532
|
+
(layer) => bus.allowedLayers?.includes(layer) ?? true,
|
|
533
|
+
)
|
|
474
534
|
if (bus.termination.type !== "plane") continue
|
|
475
535
|
const planeLayer = bus.termination.layer
|
|
476
536
|
if (!this.config.layerNames.includes(planeLayer)) {
|
|
@@ -637,10 +697,22 @@ export class FanoutSolver extends BaseSolver {
|
|
|
637
697
|
failedBusIds.push(...this.preparedBuses.map((bus) => bus.busId))
|
|
638
698
|
}
|
|
639
699
|
}
|
|
640
|
-
const busesInRoutingOrder = [...this.preparedBuses].sort(
|
|
641
|
-
(a
|
|
700
|
+
const busesInRoutingOrder = [...this.preparedBuses].sort((a, b) => {
|
|
701
|
+
const aUsesCoordinatedWinding = busUsesCoordinatedWinding(a)
|
|
702
|
+
const bUsesCoordinatedWinding = busUsesCoordinatedWinding(b)
|
|
703
|
+
const aLayerIndex = this.config.layerNames.indexOf(
|
|
704
|
+
busLayerAssignments[a.busId] ?? "",
|
|
705
|
+
)
|
|
706
|
+
const bLayerIndex = this.config.layerNames.indexOf(
|
|
707
|
+
busLayerAssignments[b.busId] ?? "",
|
|
708
|
+
)
|
|
709
|
+
return (
|
|
642
710
|
Number(b.termination.type === "plane") -
|
|
643
711
|
Number(a.termination.type === "plane") ||
|
|
712
|
+
Number(bUsesCoordinatedWinding) - Number(aUsesCoordinatedWinding) ||
|
|
713
|
+
(aUsesCoordinatedWinding && bUsesCoordinatedWinding
|
|
714
|
+
? bLayerIndex - aLayerIndex
|
|
715
|
+
: 0) ||
|
|
644
716
|
(routingStrategy === "group-by-layer"
|
|
645
717
|
? (busLayerAssignments[a.busId] ?? "").localeCompare(
|
|
646
718
|
busLayerAssignments[b.busId] ?? "",
|
|
@@ -652,8 +724,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
652
724
|
: b.connections.length - a.connections.length ||
|
|
653
725
|
(routingStrategy === "deep-first"
|
|
654
726
|
? getBusDistanceToBoundary(b) - getBusDistanceToBoundary(a)
|
|
655
|
-
: getBusDistanceToBoundary(a) - getBusDistanceToBoundary(b)))
|
|
656
|
-
|
|
727
|
+
: getBusDistanceToBoundary(a) - getBusDistanceToBoundary(b)))
|
|
728
|
+
)
|
|
729
|
+
})
|
|
657
730
|
|
|
658
731
|
let routingPrefixKey = `${routingStrategy}|`
|
|
659
732
|
for (const bus of useSingleLayerPushAndShove ? [] : busesInRoutingOrder) {
|
|
@@ -780,7 +853,8 @@ export class FanoutSolver extends BaseSolver {
|
|
|
780
853
|
)
|
|
781
854
|
if (
|
|
782
855
|
bestAttempt.summary.routedConnectionCount ===
|
|
783
|
-
|
|
856
|
+
this.inputSrj.connections.length &&
|
|
857
|
+
this.getCoordinatedAdditionalViaCount(bestAttempt.plans) === 0
|
|
784
858
|
) {
|
|
785
859
|
return bestAttempt
|
|
786
860
|
}
|
|
@@ -791,12 +865,13 @@ export class FanoutSolver extends BaseSolver {
|
|
|
791
865
|
busLayerAssignments,
|
|
792
866
|
routingStrategy,
|
|
793
867
|
)
|
|
794
|
-
if (attempt
|
|
868
|
+
if (this.isAttemptBetter(attempt, bestAttempt)) {
|
|
795
869
|
bestAttempt = attempt
|
|
796
870
|
}
|
|
797
871
|
if (
|
|
798
872
|
bestAttempt.summary.routedConnectionCount ===
|
|
799
|
-
|
|
873
|
+
this.inputSrj.connections.length &&
|
|
874
|
+
this.getCoordinatedAdditionalViaCount(bestAttempt.plans) === 0
|
|
800
875
|
) {
|
|
801
876
|
return bestAttempt
|
|
802
877
|
}
|
|
@@ -829,7 +904,24 @@ export class FanoutSolver extends BaseSolver {
|
|
|
829
904
|
return null
|
|
830
905
|
}
|
|
831
906
|
|
|
907
|
+
const getMaximumViaSpan = (bus: PreparedBus): number => {
|
|
908
|
+
const sourceLayerIndex = this.config.layerNames.indexOf(
|
|
909
|
+
bus.connections[0]?.sourceLayer ?? "",
|
|
910
|
+
)
|
|
911
|
+
const candidateLayers =
|
|
912
|
+
bus.termination.type === "plane"
|
|
913
|
+
? [bus.termination.layer]
|
|
914
|
+
: (this.escapeLayersByBusId[bus.busId] ?? this.config.escapeLayers)
|
|
915
|
+
return Math.max(
|
|
916
|
+
0,
|
|
917
|
+
...candidateLayers.map((layer) =>
|
|
918
|
+
Math.abs(this.config.layerNames.indexOf(layer) - sourceLayerIndex),
|
|
919
|
+
),
|
|
920
|
+
)
|
|
921
|
+
}
|
|
832
922
|
const busesInSearchOrder = [...this.preparedBuses].sort((a, b) => {
|
|
923
|
+
const aUsesCoordinatedWinding = busUsesCoordinatedWinding(a)
|
|
924
|
+
const bUsesCoordinatedWinding = busUsesCoordinatedWinding(b)
|
|
833
925
|
const aLayerCount =
|
|
834
926
|
a.termination.type === "plane"
|
|
835
927
|
? 1
|
|
@@ -843,6 +935,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
843
935
|
return (
|
|
844
936
|
Number(b.termination.type === "plane") -
|
|
845
937
|
Number(a.termination.type === "plane") ||
|
|
938
|
+
Number(bUsesCoordinatedWinding) - Number(aUsesCoordinatedWinding) ||
|
|
939
|
+
(aUsesCoordinatedWinding && bUsesCoordinatedWinding
|
|
940
|
+
? getMaximumViaSpan(b) - getMaximumViaSpan(a)
|
|
941
|
+
: 0) ||
|
|
846
942
|
(groupByDirection ? a.direction.localeCompare(b.direction) : 0) ||
|
|
847
943
|
aLayerCount - bLayerCount ||
|
|
848
944
|
b.componentObstacles.length - a.componentObstacles.length ||
|
|
@@ -880,7 +976,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
880
976
|
return count
|
|
881
977
|
}
|
|
882
978
|
const sourceLayer = bus.connections[0]?.sourceLayer
|
|
883
|
-
|
|
979
|
+
const preferredLayer =
|
|
980
|
+
getCommonExplicitExitTargetLayer(bus) ?? sourceLayer
|
|
981
|
+
return state.assignment[bus.busId] === preferredLayer
|
|
884
982
|
? count
|
|
885
983
|
: count + bus.connections.length
|
|
886
984
|
},
|
|
@@ -920,10 +1018,13 @@ export class FanoutSolver extends BaseSolver {
|
|
|
920
1018
|
)
|
|
921
1019
|
}
|
|
922
1020
|
const sourceLayer = bus.connections[0]?.sourceLayer
|
|
1021
|
+
const commonExitTargetLayer = getCommonExplicitExitTargetLayer(bus)
|
|
923
1022
|
const preferSourceLayer = busUsesDestinationGuidedTracks(bus)
|
|
924
1023
|
const orderedLayers = candidateLayers.toSorted(
|
|
925
1024
|
(first, second) =>
|
|
926
1025
|
(layerLoads.get(first) ?? 0) - (layerLoads.get(second) ?? 0) ||
|
|
1026
|
+
Number(second === commonExitTargetLayer) -
|
|
1027
|
+
Number(first === commonExitTargetLayer) ||
|
|
927
1028
|
(preferSourceLayer
|
|
928
1029
|
? Number(second === sourceLayer) - Number(first === sourceLayer)
|
|
929
1030
|
: Number(first === sourceLayer) -
|
|
@@ -963,6 +1064,10 @@ export class FanoutSolver extends BaseSolver {
|
|
|
963
1064
|
|
|
964
1065
|
if (nextStates.length === 0) return null
|
|
965
1066
|
nextStates.sort((first, second) => {
|
|
1067
|
+
const additionalViaDifference =
|
|
1068
|
+
this.getCoordinatedAdditionalViaCount(first.plans) -
|
|
1069
|
+
this.getCoordinatedAdditionalViaCount(second.plans)
|
|
1070
|
+
if (additionalViaDifference !== 0) return additionalViaDifference
|
|
966
1071
|
const scoreDifference = getStateScore(first) - getStateScore(second)
|
|
967
1072
|
if (Math.abs(scoreDifference) > 1e-9) return scoreDifference
|
|
968
1073
|
return JSON.stringify(first.assignment).localeCompare(
|
|
@@ -981,19 +1086,23 @@ export class FanoutSolver extends BaseSolver {
|
|
|
981
1086
|
}
|
|
982
1087
|
}
|
|
983
1088
|
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
const
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
!this.validateCompletePlans(
|
|
994
|
-
|
|
995
|
-
|
|
1089
|
+
let bestState: GroupedBeamState | undefined
|
|
1090
|
+
let outputSrj: SimpleRouteJson | undefined
|
|
1091
|
+
for (const state of states) {
|
|
1092
|
+
if (state.plans.length !== this.inputSrj.connections.length) continue
|
|
1093
|
+
const candidateOutput = buildOutputSimpleRouteJson({
|
|
1094
|
+
inputSrj: this.inputSrj,
|
|
1095
|
+
plans: state.plans,
|
|
1096
|
+
layerNames: this.config.layerNames,
|
|
1097
|
+
})
|
|
1098
|
+
if (!this.validateCompletePlans(state.plans, candidateOutput).valid) {
|
|
1099
|
+
continue
|
|
1100
|
+
}
|
|
1101
|
+
bestState = state
|
|
1102
|
+
outputSrj = candidateOutput
|
|
1103
|
+
break
|
|
996
1104
|
}
|
|
1105
|
+
if (!bestState || !outputSrj) return null
|
|
997
1106
|
const score =
|
|
998
1107
|
bestState.plans.length === this.inputSrj.connections.length
|
|
999
1108
|
? bestState.plans.reduce((total, plan) => total + plan.length, 0) +
|
|
@@ -1116,6 +1225,66 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1116
1225
|
)
|
|
1117
1226
|
}
|
|
1118
1227
|
|
|
1228
|
+
private getCoordinatedAdditionalViaCount(
|
|
1229
|
+
plans: readonly FanoutRoutePlan[],
|
|
1230
|
+
): number {
|
|
1231
|
+
const coordinatedBusIds = new Set(
|
|
1232
|
+
this.preparedBuses
|
|
1233
|
+
.filter(busUsesCoordinatedWinding)
|
|
1234
|
+
.map((bus) => bus.busId),
|
|
1235
|
+
)
|
|
1236
|
+
return plans.reduce(
|
|
1237
|
+
(count, plan) =>
|
|
1238
|
+
count +
|
|
1239
|
+
(coordinatedBusIds.has(plan.busId)
|
|
1240
|
+
? (plan.additionalVias?.length ?? 0)
|
|
1241
|
+
: 0),
|
|
1242
|
+
0,
|
|
1243
|
+
)
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
private isAttemptBetter(
|
|
1247
|
+
candidate: AssignmentAttempt,
|
|
1248
|
+
current: AssignmentAttempt,
|
|
1249
|
+
): boolean {
|
|
1250
|
+
if (
|
|
1251
|
+
candidate.summary.routedConnectionCount !==
|
|
1252
|
+
current.summary.routedConnectionCount
|
|
1253
|
+
) {
|
|
1254
|
+
return (
|
|
1255
|
+
candidate.summary.routedConnectionCount >
|
|
1256
|
+
current.summary.routedConnectionCount
|
|
1257
|
+
)
|
|
1258
|
+
}
|
|
1259
|
+
if (candidate.summary.routedBusCount !== current.summary.routedBusCount) {
|
|
1260
|
+
return candidate.summary.routedBusCount > current.summary.routedBusCount
|
|
1261
|
+
}
|
|
1262
|
+
const candidateAdditionalVias = this.getCoordinatedAdditionalViaCount(
|
|
1263
|
+
candidate.plans,
|
|
1264
|
+
)
|
|
1265
|
+
const currentAdditionalVias = this.getCoordinatedAdditionalViaCount(
|
|
1266
|
+
current.plans,
|
|
1267
|
+
)
|
|
1268
|
+
if (candidateAdditionalVias !== currentAdditionalVias) {
|
|
1269
|
+
return candidateAdditionalVias < currentAdditionalVias
|
|
1270
|
+
}
|
|
1271
|
+
return candidate.summary.score < current.summary.score
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
private hasGloballyViaMinimalBestAttempt(): boolean {
|
|
1275
|
+
if (!this.hasCompleteBestAttempt() || !this.bestAttempt) return false
|
|
1276
|
+
if (
|
|
1277
|
+
this.preparedBuses.length === 0 ||
|
|
1278
|
+
!this.preparedBuses.every(busUsesCoordinatedWinding)
|
|
1279
|
+
) {
|
|
1280
|
+
return false
|
|
1281
|
+
}
|
|
1282
|
+
return this.bestAttempt.plans.every(
|
|
1283
|
+
(plan) =>
|
|
1284
|
+
plan.via !== undefined && (plan.additionalVias?.length ?? 0) === 0,
|
|
1285
|
+
)
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1119
1288
|
private shouldEvaluateGroupedBeam(): boolean {
|
|
1120
1289
|
if (this.groupedBeamEvaluated || this.nextAssignmentIndex === 0) {
|
|
1121
1290
|
return false
|
|
@@ -1130,6 +1299,14 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1130
1299
|
}
|
|
1131
1300
|
|
|
1132
1301
|
override _step(): void {
|
|
1302
|
+
if (
|
|
1303
|
+
this.nextAssignmentIndex > 0 &&
|
|
1304
|
+
this.hasGloballyViaMinimalBestAttempt()
|
|
1305
|
+
) {
|
|
1306
|
+
this.completeBestAttemptEndpoints()
|
|
1307
|
+
this.solved = true
|
|
1308
|
+
return
|
|
1309
|
+
}
|
|
1133
1310
|
// Try the deterministic assignment and only its targeted repair queue
|
|
1134
1311
|
// before paying for the grouped beam. If the beam cannot solve, continue
|
|
1135
1312
|
// with the broader generated-assignment search below.
|
|
@@ -1143,7 +1320,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1143
1320
|
this.attempts.push(beamAttempt.summary)
|
|
1144
1321
|
if (
|
|
1145
1322
|
!this.bestAttempt ||
|
|
1146
|
-
beamAttempt
|
|
1323
|
+
this.isAttemptBetter(beamAttempt, this.bestAttempt)
|
|
1147
1324
|
) {
|
|
1148
1325
|
this.bestAttempt = beamAttempt
|
|
1149
1326
|
}
|
|
@@ -1159,11 +1336,19 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1159
1336
|
failedBuses: "none",
|
|
1160
1337
|
bestScore: bestSummary.score,
|
|
1161
1338
|
}
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1339
|
+
if (
|
|
1340
|
+
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
1341
|
+
) {
|
|
1342
|
+
this.completeBestAttemptEndpoints()
|
|
1343
|
+
this.solved = true
|
|
1344
|
+
return
|
|
1345
|
+
}
|
|
1165
1346
|
}
|
|
1166
|
-
if (
|
|
1347
|
+
if (
|
|
1348
|
+
this.hasCompleteBestAttempt() &&
|
|
1349
|
+
this.bestAttempt &&
|
|
1350
|
+
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
1351
|
+
) {
|
|
1167
1352
|
this.completeBestAttemptEndpoints()
|
|
1168
1353
|
this.solved = true
|
|
1169
1354
|
return
|
|
@@ -1237,10 +1422,7 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1237
1422
|
)
|
|
1238
1423
|
}
|
|
1239
1424
|
this.attempts.push(attempt.summary)
|
|
1240
|
-
if (
|
|
1241
|
-
!this.bestAttempt ||
|
|
1242
|
-
attempt.summary.score < this.bestAttempt.summary.score
|
|
1243
|
-
) {
|
|
1425
|
+
if (!this.bestAttempt || this.isAttemptBetter(attempt, this.bestAttempt)) {
|
|
1244
1426
|
this.bestAttempt = attempt
|
|
1245
1427
|
}
|
|
1246
1428
|
this.stats = {
|
|
@@ -1253,7 +1435,9 @@ export class FanoutSolver extends BaseSolver {
|
|
|
1253
1435
|
}
|
|
1254
1436
|
if (
|
|
1255
1437
|
this.groupedBeamEvaluated &&
|
|
1256
|
-
attempt.summary.routedConnectionCount ===
|
|
1438
|
+
attempt.summary.routedConnectionCount ===
|
|
1439
|
+
this.inputSrj.connections.length &&
|
|
1440
|
+
this.getCoordinatedAdditionalViaCount(this.bestAttempt.plans) === 0
|
|
1257
1441
|
) {
|
|
1258
1442
|
this.completeBestAttemptEndpoints()
|
|
1259
1443
|
this.solved = true
|
package/lib/prepare-buses.ts
CHANGED
|
@@ -819,7 +819,7 @@ function prepareConnection(params: {
|
|
|
819
819
|
sourceGrid: ComponentGrid
|
|
820
820
|
componentGrids: ComponentGrid[]
|
|
821
821
|
termination: FanoutBusTermination
|
|
822
|
-
exitTargetPoint?: { x: number; y: number }
|
|
822
|
+
exitTargetPoint?: { x: number; y: number; layer?: string }
|
|
823
823
|
}): PreparedConnection {
|
|
824
824
|
const {
|
|
825
825
|
connection,
|
|
@@ -863,7 +863,11 @@ function prepareConnection(params: {
|
|
|
863
863
|
sourceLayer,
|
|
864
864
|
sourceObstacle: sourceMatch.obstacle,
|
|
865
865
|
targetPoint,
|
|
866
|
-
exitTargetPoint: exitTargetPoint ??
|
|
866
|
+
exitTargetPoint: exitTargetPoint ?? {
|
|
867
|
+
x: targetPoint.x,
|
|
868
|
+
y: targetPoint.y,
|
|
869
|
+
},
|
|
870
|
+
hasExplicitLayeredExitTarget: exitTargetPoint?.layer !== undefined,
|
|
867
871
|
}
|
|
868
872
|
}
|
|
869
873
|
throw new Error(
|