@tscircuit/fanout-solver 0.0.25 → 0.0.27

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.
@@ -1,4 +1,5 @@
1
1
  import type { Obstacle, SimpleRouteJson } from "@tscircuit/capacity-autorouter"
2
+ import { createFanoutOutputIds } from "./fanout-output-ids"
2
3
  import type { FanoutRoutePlan, SimpleRouteJsonWithFanoutPlanes } from "./types"
3
4
 
4
5
  function createViaObstacle(
@@ -17,8 +18,11 @@ function createViaObstacle(
17
18
  }
18
19
  return layerIndex
19
20
  })
21
+ const outputIds = createFanoutOutputIds(plan)
20
22
  return {
21
- obstacleId: `${endpoint ? "fanout-plane-endpoint-via" : "fanout-via"}:${plan.connectionName}`,
23
+ obstacleId: endpoint
24
+ ? outputIds.planeEndpointViaObstacleId
25
+ : outputIds.viaObstacleId,
22
26
  type: "rect",
23
27
  center: via.center,
24
28
  width: via.diameter,
@@ -50,14 +54,15 @@ export function buildOutputSimpleRouteJson(params: {
50
54
  `FanoutSolver: output connection index ${plan.connectionIndex} is missing`,
51
55
  )
52
56
  }
57
+ const outputIds = createFanoutOutputIds(plan)
53
58
  connection.pointsToConnect[plan.sourcePointIndex] = {
54
59
  x: plan.exitPoint.x,
55
60
  y: plan.exitPoint.y,
56
61
  layer: plan.targetLayer,
57
62
  pointId:
58
63
  plan.termination.type === "plane"
59
- ? `fanout-plane:${plan.connectionName}`
60
- : `fanout-exit:${plan.connectionName}`,
64
+ ? outputIds.planeExitPointId
65
+ : outputIds.boundaryExitPointId,
61
66
  }
62
67
  if (plan.termination.type === "plane") {
63
68
  planeTerminatedConnectionNames.add(plan.connectionName)
@@ -5,6 +5,7 @@ import {
5
5
  type SimpleRouteJson,
6
6
  type SimplifiedPcbTrace,
7
7
  } from "@tscircuit/capacity-autorouter"
8
+ import { createFanoutCompletionTraceId } from "./fanout-output-ids"
8
9
  import {
9
10
  distance,
10
11
  distancePointToSegment,
@@ -226,7 +227,11 @@ function createBranchTrace(params: {
226
227
 
227
228
  return {
228
229
  type: "pcb_trace",
229
- pcb_trace_id: `fanout-completion:${plan.connectionName}:${candidateIndex}`,
230
+ pcb_trace_id: createFanoutCompletionTraceId({
231
+ connectionName: plan.connectionName,
232
+ sourcePointIndex: plan.sourcePointIndex,
233
+ candidateIndex,
234
+ }),
230
235
  connection_name: plan.connectionName,
231
236
  route,
232
237
  }
@@ -0,0 +1,33 @@
1
+ export interface FanoutOutputIds {
2
+ boundaryExitPointId: string
3
+ planeExitPointId: string
4
+ traceId: string
5
+ viaObstacleId: string
6
+ planeEndpointPointId: string
7
+ planeEndpointTraceId: string
8
+ planeEndpointViaObstacleId: string
9
+ }
10
+
11
+ export function createFanoutOutputIds(input: {
12
+ connectionName: string
13
+ sourcePointIndex: number
14
+ }): FanoutOutputIds {
15
+ const endpointKey = `${input.connectionName}:source-${input.sourcePointIndex}`
16
+ return {
17
+ boundaryExitPointId: `fanout-exit:${endpointKey}`,
18
+ planeExitPointId: `fanout-plane:${endpointKey}`,
19
+ traceId: `fanout:${endpointKey}`,
20
+ viaObstacleId: `fanout-via:${endpointKey}`,
21
+ planeEndpointPointId: `fanout-plane-endpoint-point:${endpointKey}`,
22
+ planeEndpointTraceId: `fanout-plane-endpoint:${endpointKey}`,
23
+ planeEndpointViaObstacleId: `fanout-plane-endpoint-via:${endpointKey}`,
24
+ }
25
+ }
26
+
27
+ export function createFanoutCompletionTraceId(input: {
28
+ connectionName: string
29
+ sourcePointIndex: number
30
+ candidateIndex: number
31
+ }): string {
32
+ return `fanout-completion:${input.connectionName}:source-${input.sourcePointIndex}:${input.candidateIndex}`
33
+ }
@@ -1028,8 +1028,31 @@ export class FanoutSolver extends BaseSolver {
1028
1028
  this.pendingRepairAssignments.push(...repairs)
1029
1029
  }
1030
1030
 
1031
+ private hasCompleteBestAttempt(): boolean {
1032
+ return (
1033
+ this.bestAttempt?.summary.routedConnectionCount ===
1034
+ this.inputSrj.connections.length
1035
+ )
1036
+ }
1037
+
1038
+ private shouldEvaluateGroupedBeam(): boolean {
1039
+ if (this.groupedBeamEvaluated || this.nextAssignmentIndex === 0) {
1040
+ return false
1041
+ }
1042
+
1043
+ const targetedRepairSearchFinished =
1044
+ this.hasCompleteBestAttempt() ||
1045
+ this.pendingRepairAssignments.length === 0 ||
1046
+ this.nextAssignmentIndex >= this.config.maxLayerCombinations
1047
+
1048
+ return targetedRepairSearchFinished
1049
+ }
1050
+
1031
1051
  override _step(): void {
1032
- if (!this.groupedBeamEvaluated) {
1052
+ // Try the deterministic assignment and only its targeted repair queue
1053
+ // before paying for the grouped beam. If the beam cannot solve, continue
1054
+ // with the broader generated-assignment search below.
1055
+ if (this.shouldEvaluateGroupedBeam()) {
1033
1056
  this.groupedBeamEvaluated = true
1034
1057
  let beamAttempt = this.evaluateGroupedBeam(-1)
1035
1058
  if (!beamAttempt) {
@@ -1037,19 +1060,33 @@ export class FanoutSolver extends BaseSolver {
1037
1060
  }
1038
1061
  if (beamAttempt) {
1039
1062
  this.attempts.push(beamAttempt.summary)
1040
- this.bestAttempt = beamAttempt
1063
+ if (
1064
+ !this.bestAttempt ||
1065
+ beamAttempt.summary.score < this.bestAttempt.summary.score
1066
+ ) {
1067
+ this.bestAttempt = beamAttempt
1068
+ }
1069
+ const bestSummary = this.bestAttempt.summary
1041
1070
  this.stats = {
1042
- assignment: 0,
1071
+ assignment:
1072
+ bestSummary.assignmentIndex < 0
1073
+ ? 0
1074
+ : bestSummary.assignmentIndex + 1,
1043
1075
  assignmentCount: this.config.maxLayerCombinations,
1044
- routedBuses: `${beamAttempt.summary.routedBusCount}/${this.preparedBuses.length}`,
1045
- routedConnections: `${beamAttempt.summary.routedConnectionCount}/${this.inputSrj.connections.length}`,
1076
+ routedBuses: `${bestSummary.routedBusCount}/${this.preparedBuses.length}`,
1077
+ routedConnections: `${bestSummary.routedConnectionCount}/${this.inputSrj.connections.length}`,
1046
1078
  failedBuses: "none",
1047
- bestScore: beamAttempt.summary.score,
1079
+ bestScore: bestSummary.score,
1048
1080
  }
1049
1081
  this.completeBestAttemptEndpoints()
1050
1082
  this.solved = true
1051
1083
  return
1052
1084
  }
1085
+ if (this.hasCompleteBestAttempt()) {
1086
+ this.completeBestAttemptEndpoints()
1087
+ this.solved = true
1088
+ return
1089
+ }
1053
1090
  }
1054
1091
 
1055
1092
  let assignment: Readonly<Record<string, string>> | undefined
@@ -1060,13 +1097,19 @@ export class FanoutSolver extends BaseSolver {
1060
1097
  const preferGeneratedAssignment = this.nextAssignmentIndex % 3 === 0
1061
1098
  let candidate: Readonly<Record<string, string>> | undefined
1062
1099
  let candidateCameFromRepairQueue = false
1063
- if (preferGeneratedAssignment) {
1100
+ if (!this.groupedBeamEvaluated && this.nextAssignmentIndex > 0) {
1101
+ candidate = this.pendingRepairAssignments.pop()
1102
+ candidateCameFromRepairQueue = candidate !== undefined
1103
+ } else if (preferGeneratedAssignment) {
1064
1104
  candidate = this.layerAssignments[this.nextGeneratedAssignmentIndex++]
1065
1105
  } else {
1066
1106
  candidate = this.pendingRepairAssignments.pop()
1067
1107
  candidateCameFromRepairQueue = candidate !== undefined
1068
1108
  }
1069
- if (!candidate) {
1109
+ if (
1110
+ !candidate &&
1111
+ (this.groupedBeamEvaluated || this.nextAssignmentIndex === 0)
1112
+ ) {
1070
1113
  candidate = preferGeneratedAssignment
1071
1114
  ? this.pendingRepairAssignments.pop()
1072
1115
  : this.layerAssignments[this.nextGeneratedAssignmentIndex++]
@@ -1081,12 +1124,9 @@ export class FanoutSolver extends BaseSolver {
1081
1124
  if (this.evaluatedAssignmentKeys.has(candidateKey)) continue
1082
1125
  assignment = candidate
1083
1126
  }
1127
+ if (!assignment && !this.groupedBeamEvaluated) return
1084
1128
  if (!assignment) {
1085
- if (
1086
- this.bestAttempt &&
1087
- this.bestAttempt.summary.routedConnectionCount ===
1088
- this.inputSrj.connections.length
1089
- ) {
1129
+ if (this.hasCompleteBestAttempt()) {
1090
1130
  this.completeBestAttemptEndpoints()
1091
1131
  this.solved = true
1092
1132
  } else {
@@ -1131,6 +1171,7 @@ export class FanoutSolver extends BaseSolver {
1131
1171
  bestScore: this.bestAttempt.summary.score,
1132
1172
  }
1133
1173
  if (
1174
+ this.groupedBeamEvaluated &&
1134
1175
  attempt.summary.routedConnectionCount === this.inputSrj.connections.length
1135
1176
  ) {
1136
1177
  this.completeBestAttemptEndpoints()
package/lib/route-bus.ts CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  distanceSegmentToObstacle,
11
11
  segmentsAreClear,
12
12
  } from "./geometry"
13
+ import { createFanoutOutputIds } from "./fanout-output-ids"
13
14
  import { getLayerSpan } from "./layer-names"
14
15
  import {
15
16
  connectionsShareElectricalNet,
@@ -636,6 +637,10 @@ function buildPlan(params: {
636
637
  })
637
638
  }
638
639
 
640
+ const outputIds = createFanoutOutputIds({
641
+ connectionName: preparedConnection.connection.name,
642
+ sourcePointIndex: preparedConnection.sourcePointIndex,
643
+ })
639
644
  return {
640
645
  busId: bus.busId,
641
646
  connectionName: preparedConnection.connection.name,
@@ -651,16 +656,16 @@ function buildPlan(params: {
651
656
  exitPoint,
652
657
  trace: {
653
658
  type: "pcb_trace",
654
- pcb_trace_id: `fanout:${preparedConnection.connection.name}`,
659
+ pcb_trace_id: outputIds.traceId,
655
660
  connection_name: preparedConnection.connection.name,
656
661
  connectsTo: [
657
- preparedConnection.connection.name,
658
662
  ...(preparedConnection.sourcePoint.pointId
659
663
  ? [preparedConnection.sourcePoint.pointId]
660
664
  : []),
661
665
  ...(preparedConnection.sourcePoint.pcb_port_id
662
666
  ? [preparedConnection.sourcePoint.pcb_port_id]
663
667
  : []),
668
+ outputIds.boundaryExitPointId,
664
669
  ],
665
670
  route,
666
671
  },
@@ -804,18 +809,22 @@ function addPlaneEndpointTerminal(params: {
804
809
  toLayer: targetEndpointLayer,
805
810
  spanLayers,
806
811
  }
812
+ const outputIds = createFanoutOutputIds({
813
+ connectionName: preparedConnection.connection.name,
814
+ sourcePointIndex: preparedConnection.sourcePointIndex,
815
+ })
807
816
  const planeEndpointTrace: SimplifiedPcbTrace = {
808
817
  type: "pcb_trace",
809
- pcb_trace_id: `fanout-plane-endpoint:${preparedConnection.connection.name}`,
818
+ pcb_trace_id: outputIds.planeEndpointTraceId,
810
819
  connection_name: preparedConnection.connection.name,
811
820
  connectsTo: [
812
- preparedConnection.connection.name,
813
821
  ...(preparedConnection.targetPoint.pointId
814
822
  ? [preparedConnection.targetPoint.pointId]
815
823
  : []),
816
824
  ...(preparedConnection.targetPoint.pcb_port_id
817
825
  ? [preparedConnection.targetPoint.pcb_port_id]
818
826
  : []),
827
+ outputIds.planeEndpointPointId,
819
828
  ],
820
829
  route: [
821
830
  {
@@ -9,6 +9,7 @@ import {
9
9
  distanceSegmentToObstacle,
10
10
  distanceSegmentToSegment,
11
11
  } from "./geometry"
12
+ import { createFanoutOutputIds } from "./fanout-output-ids"
12
13
  import { type AvailableBoundaryRegion, getRegionAnchor } from "./prepare-buses"
13
14
  import type {
14
15
  FanoutDirection,
@@ -815,6 +816,10 @@ function buildPlan(route: FlowRoute, traceWidth: number): FanoutRoutePlan {
815
816
  : {}),
816
817
  }),
817
818
  )
819
+ const outputIds = createFanoutOutputIds({
820
+ connectionName: item.connection.connection.name,
821
+ sourcePointIndex: item.connection.sourcePointIndex,
822
+ })
818
823
  return {
819
824
  busId: item.bus.busId,
820
825
  connectionName: item.connection.connection.name,
@@ -830,17 +835,16 @@ function buildPlan(route: FlowRoute, traceWidth: number): FanoutRoutePlan {
830
835
  exitPoint: points.at(-1)!,
831
836
  trace: {
832
837
  type: "pcb_trace",
833
- pcb_trace_id: `fanout:${item.connection.connection.name}`,
838
+ pcb_trace_id: outputIds.traceId,
834
839
  connection_name: item.connection.connection.name,
835
840
  connectsTo: [
836
- item.connection.connection.name,
837
- item.netKey,
838
841
  ...(item.connection.sourcePoint.pointId
839
842
  ? [item.connection.sourcePoint.pointId]
840
843
  : []),
841
844
  ...(item.connection.sourcePoint.pcb_port_id
842
845
  ? [item.connection.sourcePoint.pcb_port_id]
843
846
  : []),
847
+ outputIds.boundaryExitPointId,
844
848
  ],
845
849
  route: traceRoute,
846
850
  },
@@ -8,6 +8,7 @@ import {
8
8
  distanceSegmentToObstacle,
9
9
  distanceSegmentToSegment,
10
10
  } from "./geometry"
11
+ import { createFanoutOutputIds } from "./fanout-output-ids"
11
12
  import type {
12
13
  FanoutBorderDistribution,
13
14
  FanoutCorner,
@@ -751,6 +752,10 @@ function buildPlan(path: RoutedPath): FanoutRoutePlan {
751
752
  ? { start_pcb_port_id: item.connection.sourcePoint.pcb_port_id }
752
753
  : {}),
753
754
  }))
755
+ const outputIds = createFanoutOutputIds({
756
+ connectionName: item.connection.connection.name,
757
+ sourcePointIndex: item.connection.sourcePointIndex,
758
+ })
754
759
  return {
755
760
  busId: item.bus.busId,
756
761
  connectionName: item.connection.connection.name,
@@ -766,16 +771,16 @@ function buildPlan(path: RoutedPath): FanoutRoutePlan {
766
771
  exitPoint: points.at(-1)!,
767
772
  trace: {
768
773
  type: "pcb_trace",
769
- pcb_trace_id: `fanout:${item.connection.connection.name}`,
774
+ pcb_trace_id: outputIds.traceId,
770
775
  connection_name: item.connection.connection.name,
771
776
  connectsTo: [
772
- item.connection.connection.name,
773
777
  ...(item.connection.sourcePoint.pointId
774
778
  ? [item.connection.sourcePoint.pointId]
775
779
  : []),
776
780
  ...(item.connection.sourcePoint.pcb_port_id
777
781
  ? [item.connection.sourcePoint.pcb_port_id]
778
782
  : []),
783
+ outputIds.boundaryExitPointId,
779
784
  ],
780
785
  route,
781
786
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tscircuit/fanout-solver",
3
- "version": "0.0.25",
3
+ "version": "0.0.27",
4
4
  "description": "BGA fanout solver with coordinated bus-layer escapes for SimpleRouteJson",
5
5
  "module": "lib/index.ts",
6
6
  "type": "module",
@@ -42,16 +42,22 @@
42
42
  "@biomejs/biome": "2.5.5",
43
43
  "@tsci/tscircuit.dataset-srj19-bga-passive-overlays": "github:tscircuit/dataset-srj19#c1206f3",
44
44
  "@tsci/tscircuit.dataset-srj29-bga-decoupling": "github:tscircuit/dataset-srj29-bga-decoupling#bfdf7bc",
45
+ "@tscircuit/alphabet": "0.0.25",
46
+ "@tscircuit/circuit-json-util": "0.0.97",
45
47
  "@tscircuit/footprinter": "0.0.394",
46
48
  "@types/bun": "1.3.14",
47
49
  "@types/react": "19.2.14",
48
50
  "@types/react-dom": "19.2.3",
49
51
  "bun-match-svg": "^0.0.16",
50
52
  "circuit-json": "0.0.455",
53
+ "circuit-json-to-connectivity-map": "0.0.23",
54
+ "circuit-to-svg": "0.0.364",
55
+ "format-si-unit": "0.0.7",
51
56
  "react": "19.2.8",
52
57
  "react-cosmos": "7.3.0",
53
58
  "react-cosmos-plugin-vite": "7.3.0",
54
59
  "react-dom": "19.2.8",
60
+ "schematic-symbols": "0.0.231",
55
61
  "vite": "8.1.5"
56
62
  },
57
63
  "peerDependencies": {