Skip to content

Commit

Permalink
finish goal box implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
seveibar committed Sep 15, 2024
1 parent 794976a commit 180a868
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 31 deletions.
30 changes: 29 additions & 1 deletion algos/infinite-grid-ijump-astar/v2/lib/GeneralizedAstar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,13 +204,29 @@ export class GeneralizedAstarAutorouter {
return "top"
}

/**
* Add a preprocessing step before solving a connection to do adjust points
* based on previous iterations. For example, if a previous connection solved
* for a trace on the same net, you may want to preprocess the connection to
* solve for an easier start and end point
*
* The simplest way to do this is to run getConnectionWithAlternativeGoalBoxes
* with any pcb_traces created by previous iterations
*/
preprocessConnectionBeforeSolving(
connection: SimpleRouteConnection,
): SimpleRouteConnection {
return connection
}

solveConnection(connection: SimpleRouteConnection): ConnectionSolveResult {
const { pointsToConnect } = connection
if (pointsToConnect.length > 2) {
throw new Error(
"GeneralizedAstarAutorouter doesn't currently support 2+ points in a connection",
)
}
connection = this.preprocessConnectionBeforeSolving(connection)

this.iterations = 0
this.closedSet = new Set()
Expand Down Expand Up @@ -282,6 +298,17 @@ export class GeneralizedAstarAutorouter {
)
}

/**
* Override this to implement smoothing strategies or incorporate new traces
* into a connectivity map
*/
postprocessConnectionSolveResult(
connection: SimpleRouteConnection,
result: ConnectionSolveResult,
): ConnectionSolveResult {
return result
}

/**
* By default, this will solve the connections in the order they are given,
* and add obstacles for each successfully solved connection. Override this
Expand All @@ -299,7 +326,8 @@ export class GeneralizedAstarAutorouter {
connection,
obstaclesFromTraces,
})
const result = this.solveConnection(connection)
let result = this.solveConnection(connection)
result = this.postprocessConnectionSolveResult(connection, result)
solutions.push(result)

if (debug.enabled) {
Expand Down
93 changes: 90 additions & 3 deletions algos/multi-layer-ijump/MultilayerIjump.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { GeneralizedAstarAutorouter } from "algos/infinite-grid-ijump-astar/v2/lib/GeneralizedAstar"
import {
GeneralizedAstarAutorouter,
type ConnectionSolveResult,
} from "algos/infinite-grid-ijump-astar/v2/lib/GeneralizedAstar"
import { getDistanceToOvercomeObstacle } from "algos/infinite-grid-ijump-astar/v2/lib/getDistanceToOvercomeObstacle"
import type {
Direction,
Expand All @@ -25,6 +28,17 @@ import type {
} from "autorouting-dataset/lib/solver-utils/SimpleRouteJson"
import { ObstacleList3d } from "./ObstacleList3d"
import type { Obstacle } from "autorouting-dataset/lib/types"
import {
PcbConnectivityMap,
type ConnectivityMap,
} from "circuit-json-to-connectivity-map"
import type { ConnectionWithGoalAlternatives } from "autorouting-dataset/lib/solver-utils/ConnectionWithAlternatives"
import { nanoid } from "nanoid"
import type { LayerRef, PCBTrace } from "@tscircuit/soup"
import {
getAlternativeGoalBoxes,
getConnectionWithAlternativeGoalBoxes,
} from "autorouting-dataset/lib/solver-utils/getAlternativeGoalBoxes"

export class MultilayerIjump extends GeneralizedAstarAutorouter {
MAX_ITERATIONS: number = 200
Expand All @@ -33,6 +47,18 @@ export class MultilayerIjump extends GeneralizedAstarAutorouter {
allowLayerChange: boolean = true // Flag to allow layer changes
layerCount: number
obstacles: ObstacleList3d
optimizeWithGoalBoxes: boolean
/**
* Use this to convert ids into "net ids", obstacles will have a net id in
* their connectedTo array most of the time
*/
connMap: ConnectivityMap | undefined

/**
* Use this to track what traces have been connected to a net while routing,
* this is required for generating alternative goal boxes while routing
*/
pcbConnMap: PcbConnectivityMap

GOAL_RUSH_FACTOR: number = 1.1

Expand Down Expand Up @@ -70,12 +96,17 @@ export class MultilayerIjump extends GeneralizedAstarAutorouter {
MAX_ITERATIONS?: number
VIA_COST?: number
isRemovePathLoopsEnabled?: boolean
connMap?: ConnectivityMap
pcbConnMap?: PcbConnectivityMap
optimizeWithGoalBoxes?: boolean
debug?: boolean
}) {
super(opts)
this.layerCount = opts.input.layerCount ?? 2
this.VIA_COST = opts.VIA_COST ?? this.VIA_COST

this.connMap = opts.connMap
this.pcbConnMap = opts.pcbConnMap ?? new PcbConnectivityMap()
this.optimizeWithGoalBoxes = opts.optimizeWithGoalBoxes ?? false
// obstacle lists are created when solving currently
this.obstacles = null as any // new ObstacleList3d(this.layerCount, this.allObstacles)

Expand All @@ -93,6 +124,52 @@ export class MultilayerIjump extends GeneralizedAstarAutorouter {
]
}

preprocessConnectionBeforeSolving(
connection: SimpleRouteConnection,
): ConnectionWithGoalAlternatives {
if (!this.optimizeWithGoalBoxes) return connection as any
return getConnectionWithAlternativeGoalBoxes({
connection,
pcbConnMap: this.pcbConnMap!,
})
}

/**
* Add solved traces to pcbConnMap
*/
postprocessConnectionSolveResult(
connection: SimpleRouteConnection,
result: ConnectionSolveResult,
): ConnectionSolveResult {
if (!result.solved) return result

// Add the trace to the pcbConnMap
if (this.optimizeWithGoalBoxes) {
const traceRoute = result.route.map(
(rp) =>
({
x: rp.x,
y: rp.y,
route_type: "wire",
layer: rp.layer as LayerRef,
width: 0.1,
}) as Extract<PCBTrace["route"][number], { route_type: "wire" }>,
)
traceRoute[0].start_pcb_port_id =
connection.pointsToConnect[0].pcb_port_id
traceRoute[traceRoute.length - 1].end_pcb_port_id =
connection.pointsToConnect[1].pcb_port_id

this.pcbConnMap.addTrace({
type: "pcb_trace",
pcb_trace_id: `postprocess_trace_${nanoid(8)}`,
route: traceRoute,
})
}

return result
}

createObstacleList({
dominantLayer,
connection,
Expand All @@ -102,10 +179,20 @@ export class MultilayerIjump extends GeneralizedAstarAutorouter {
connection: SimpleRouteConnection
obstaclesFromTraces: Obstacle[]
}): ObstacleList3d {
const bestConnectionId = this.connMap
? this.connMap.getNetConnectedToId(connection.name)
: connection.name

if (!bestConnectionId) {
throw new Error(
`The connection.name "${connection.name}" wasn't present in the full connectivity map`,
)
}

return new ObstacleList3d(
this.layerCount,
this.allObstacles
.filter((obstacle) => !obstacle.connectedTo.includes(connection.name))
.filter((obstacle) => !obstacle.connectedTo.includes(bestConnectionId))
.concat(obstaclesFromTraces ?? []),
)
}
Expand Down
9 changes: 8 additions & 1 deletion algos/multi-layer-ijump/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@ import type { AnySoupElement } from "@tscircuit/soup"
import type { SolutionWithDebugInfo } from "autorouting-dataset/lib/solver-utils/ProblemSolver"
import { getSimpleRouteJson } from "autorouting-dataset/lib/solver-utils/getSimpleRouteJson"
import { MultilayerIjump } from "./MultilayerIjump"
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"

export function autoroute(soup: AnySoupElement[]): SolutionWithDebugInfo {
const input = getSimpleRouteJson(soup, { layerCount: 2 })
const connMap = getFullConnectivityMapFromCircuitJson(soup)
const input = getSimpleRouteJson(soup, {
layerCount: 2,
optimizeWithGoalBoxes: true,
connMap,
})

const autorouter = new MultilayerIjump({
input,
connMap,
})

const solution = autorouter.solveAndMapToTraces()
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 14 additions & 2 deletions algos/multi-layer-ijump/tests/same-net-many-pads.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { translate } from "transformation-matrix"
import type { AnySoupElement } from "@tscircuit/soup"
import { getDebugSvg } from "../../infinite-grid-ijump-astar/tests/fixtures/get-debug-svg"
import { MultilayerIjump } from "../MultilayerIjump"
import { getFullConnectivityMapFromCircuitJson } from "circuit-json-to-connectivity-map"

test("multimargin-ijump-astar simple", () => {
const circuit = new Circuit()
Expand Down Expand Up @@ -47,16 +48,27 @@ test("multimargin-ijump-astar simple", () => {

const inputCircuitJson = circuit.getCircuitJson()

const input = getSimpleRouteJson(inputCircuitJson, { layerCount: 2 })
const connMap = getFullConnectivityMapFromCircuitJson(inputCircuitJson)

const input = getSimpleRouteJson(inputCircuitJson, {
layerCount: 2,
connMap,
optimizeWithGoalBoxes: true,
})

const autorouter = new MultilayerIjump({
input,
connMap,
optimizeWithGoalBoxes: true,
debug: true,
})

const solution = autorouter.solveAndMapToTraces()

expect(
circuitJsonToPcbSvg(inputCircuitJson.concat(solution as any)),
circuitJsonToPcbSvg(
inputCircuitJson, //.concat(solution as any),
// .map((a) => (a.type === "pcb_smtpad" ? { ...a, layer: "bottom" } : a)),
),
).toMatchSvgSnapshot(import.meta.path)
})
Binary file modified bun.lockb
Binary file not shown.
56 changes: 44 additions & 12 deletions module/lib/solver-utils/getAlternativeGoalBoxes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,24 +34,28 @@ export function getAlternativeGoalBoxes(params: {

const goalTraces = pcbConnMap.getAllTracesConnectedToPort(goalElementId)

return getObstaclesFromCircuitJson(goalTraces)
return getObstaclesFromCircuitJson(goalTraces).map((obs) => ({
...obs,
connectedTo: [goalElementId],
}))
}

export const getConnectionWithAlternativeGoalBoxes = (params: {
connection: SimpleRouteConnection
soup: AnySoupElement[]
pcbConnMap: PcbConnectivityMap
}): ConnectionWithGoalAlternatives => {
let { connection, soup } = params
let { connection, pcbConnMap } = params

if (connection.pointsToConnect.length !== 2) {
throw new Error(
`Connection must have exactly 2 points for alternative goal boxes (got ${connection.pointsToConnect.length})`,
)
}
const pcbConnMap = new PcbConnectivityMap(soup)

const [a, b] = connection.pointsToConnect

// TODO fix only one of them needs to have a pcb port id defined, or even
// neither of them and we return an empty goal box array
if (!a.pcb_port_id || !b.pcb_port_id) {
throw new Error(
`Connection points must have pcb_port_id for alternative goal box calculation (got ${a.pcb_port_id} and ${b.pcb_port_id})`,
Expand All @@ -61,26 +65,54 @@ export const getConnectionWithAlternativeGoalBoxes = (params: {
const goalBoxesA = getAlternativeGoalBoxes({
goalElementId: a.pcb_port_id,
pcbConnMap,
})
}).concat([
{
center: a,
width: 0.01,
height: 0.01,
connectedTo: [a.pcb_port_id],
layers: [a.layer],
type: "rect",
},
])
const goalBoxesB = getAlternativeGoalBoxes({
goalElementId: b.pcb_port_id,
pcbConnMap,
})
}).concat([
{
center: b,
width: 0.01,
height: 0.01,
connectedTo: [b.pcb_port_id],
layers: [b.layer],
type: "rect",
},
])

if (goalBoxesA.length === 1 && goalBoxesB.length === 1) {
return {
...connection,
startPoint: a,
endPoint: b,
goalBoxes: [],
}
}

// Find new points to connect based on the alternative goal boxes
const nearestPoints = findNearestPointsBetweenBoxSets(goalBoxesA, goalBoxesB)

let startPoint: PointWithLayer
let endPoint: PointWithLayer
let goalBoxes: Obstacle[]

if (goalBoxesA.length >= goalBoxesB.length) {
startPoint = { ...nearestPoints.pointA, layer: a.layer }
endPoint = { ...nearestPoints.pointB, layer: b.layer }
goalBoxes = goalBoxesB
} else {
startPoint = { ...nearestPoints.pointB, layer: b.layer }
endPoint = { ...nearestPoints.pointA, layer: a.layer }
startPoint = { ...b, ...nearestPoints.pointB }
endPoint = { ...a, ...nearestPoints.pointA }
goalBoxes = goalBoxesA
} else {
startPoint = { ...a, ...nearestPoints.pointA }
endPoint = { ...b, ...nearestPoints.pointB }
goalBoxes = goalBoxesB
}

return {
Expand Down
Loading

0 comments on commit 180a868

Please sign in to comment.