Skip to content

Commit e4a808b

Browse files
committed
Demo pathfinding with A*
1 parent 1afc876 commit e4a808b

File tree

5 files changed

+180
-5
lines changed

5 files changed

+180
-5
lines changed

src/paths/pathfinding.jl

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
import Graphs: a_star, grid, weights, add_edge!
2+
import DeviceLayout: Rectangle, Polygon
3+
4+
abstract type PathfindingRule{T <: RouteRule} <: RouteRule end
5+
6+
struct AStarRouting{T, U <: Coordinate} <: PathfindingRule{T}
7+
leg_rule::T
8+
domain::Rectangle{U}
9+
grid_step::U
10+
exclusion_fn::Function
11+
exclusion::Vector{Polygon{U}}
12+
excluded
13+
14+
function AStarRouting(l, d::Rectangle{U}, s, f::Function) where {U}
15+
return new{typeof(l), U}(l, d, s, f, Polygon{U}[], [])
16+
end
17+
end
18+
19+
leg_rule(pr::PathfindingRule) = pr.leg_rule
20+
21+
function _route_leg!(
22+
p::Path,
23+
next::Point,
24+
nextdir,
25+
rule::PathfindingRule,
26+
sty::Paths.Style=Paths.contstyle1(p)
27+
)
28+
# Just use underlying rule
29+
return _route_leg!(p, next, nextdir, leg_rule(rule), sty)
30+
end
31+
32+
function reconcile!(
33+
path::Path,
34+
endpoint::Point,
35+
end_direction,
36+
rule::AStarRouting,
37+
waypoints,
38+
waydirs;
39+
initialize_waydirs=false
40+
)
41+
# Calculate and insert waypoints
42+
# Construct grid graph
43+
bbox = bounds(rule.domain)
44+
grid_x = range(bbox.ll.x, step=rule.grid_step, stop=bbox.ur.x)
45+
grid_y = range(bbox.ll.y, step=rule.grid_step, stop=bbox.ur.y)
46+
in_poly = DeviceLayout.gridpoints_in_polygon(rule.exclusion, grid_x, grid_y)
47+
nx = length(grid_x)
48+
ny = length(grid_y)
49+
grid_graph = grid((nx, ny))
50+
51+
# Functions to convert from real space/cartesian indices to graph indices
52+
v_to_ix_iy(v::Int) = Tuple(CartesianIndices((1:nx, 1:ny))[v])
53+
ix_iy_to_v(ix::Int, iy::Int) = LinearIndices((1:nx, 1:ny))[ix, iy]
54+
v_to_xy(v::Int) = Point(grid_x[v_to_ix_iy(v)[1]], grid_y[v_to_ix_iy(v)[2]])
55+
xy_to_v(p::Point) = ix_iy_to_v(
56+
findfirst(xi -> xi + rule.grid_step / 2 > p.x, grid_x),
57+
findfirst(yi -> yi + rule.grid_step / 2 > p.y, grid_y)
58+
)
59+
60+
# Set up pathfinding
61+
start_v = xy_to_v(p0(path))
62+
end_v = xy_to_v(endpoint)
63+
source = xy_to_v(p0(path) + # pathfinding starts with grid box index in front of start
64+
rule.grid_step * Point(cos(α0(path)), sin(α0(path))))
65+
target = xy_to_v(endpoint - # pathfinding ends with grid box index in front of end
66+
rule.grid_step * Point(cos(end_direction), sin(end_direction)))
67+
heuristic(v) = uconvert(NoUnits, norm(endpoint - v_to_xy(v)) / rule.grid_step)
68+
69+
w = Matrix{Float64}(weights(grid_graph))
70+
## Add diagonals
71+
# for v1 = 1:(nx*ny)
72+
# for v2 = (v1+1):(nx*ny)
73+
# ix1, iy1 = v_to_ix_iy(v1)
74+
# ix2, iy2 = v_to_ix_iy(v2)
75+
# if abs(ix1 - ix2) == 1 && abs(iy1 - iy2) == 1
76+
# add_edge!(grid_graph, v1, v2)
77+
# w[v1, v2] = w[v2, v1] = sqrt(2)
78+
# end
79+
# end
80+
# end
81+
82+
## Set edges to excluded grid cells to infinite weight
83+
## (deleting vertices would renumber them)
84+
in_poly[start_v] = true
85+
in_poly[end_v] = true
86+
for (v, excluded) in enumerate(in_poly)
87+
!excluded && continue
88+
w[v, :] .= Inf
89+
w[:, v] .= Inf
90+
end
91+
92+
# Find shortest path
93+
## Not deterministic!
94+
edges = a_star(grid_graph, source, target, w, heuristic)
95+
# Create waypoints
96+
## Whenever we change direction, add a waypoint
97+
edge_direction(v1, v2) = (v_to_ix_iy(v2) .- v_to_ix_iy(v1))
98+
current_direction = edge_direction(start_v, source)
99+
for e in edges
100+
direction = edge_direction(e.src, e.dst)
101+
if direction != current_direction
102+
push!(waypoints, (v_to_xy(e.src) + v_to_xy(e.dst)) / 2)
103+
push!(waydirs, atan(direction[2], direction[1]))
104+
current_direction = direction
105+
end
106+
end
107+
final_direction = edge_direction(target, end_v)
108+
if current_direction == final_direction
109+
pop!(waypoints) # Don't need the last one
110+
pop!(waydirs)
111+
end
112+
end
113+
114+
function _update_rule!(sch, node, rule::RouteRule) end
115+
116+
function _update_rule!(sch, node, rule::PathfindingRule)
117+
for planned_node in keys(sch.ref_dict) # Only components already planned
118+
(planned_node in rule.excluded) && continue
119+
append!(
120+
rule.exclusion,
121+
reduce(
122+
vcat,
123+
DeviceLayout.to_polygons.(
124+
transformation(
125+
sch,
126+
planned_node
127+
).(flatten(rule.exclusion_fn(planned_node.component)).elements)
128+
),
129+
init=Polygon{coordinatetype(sch)}[]
130+
)
131+
)
132+
push!(rule.excluded, planned_node)
133+
end
134+
end

src/paths/paths.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ include("segments/offset.jl")
690690
include("segments/bspline_approximation.jl")
691691

692692
include("routes.jl")
693+
include("pathfinding.jl")
693694

694695
function change_handedness!(seg::Union{Turn, Corner})
695696
return seg.α = -seg.α

src/paths/routes.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ of the following two methods:
88
99
```
1010
_route!(p::Path, p1::Point, α1, rule::MyRouteRule, sty, waypoints, waydirs)
11-
_route_leg!(p::Path, next::Point, rule::MyRouteRule,
11+
_route_leg!(p::Path, next::Point, nextdir, rule::MyRouteRule,
1212
sty::Paths.Style=Paths.contstyle1(p))
1313
```
1414
@@ -427,15 +427,15 @@ function route!(
427427
initialize_waydirs=initialize_waydirs
428428
)
429429
_route!(path, p_end, α_end, rule, sty, waypoints, waydirs)
430-
isapprox(rem(α1(path) - α_end, 360°, RoundNearest), 0, atol=1e-9) || error("""
430+
isapprox(rem(α1(path) - α_end, 360°, RoundNearest), 0, atol=1e-9) || @error """
431431
Could not automatically route to destination with the correct arrival angle \
432432
(got $(α1(path)) instead of $α_end). Try adding or adjusting waypoints.\
433-
""")
433+
"""
434434
pts = promote(p1(path), p_end)
435-
return isapprox(pts...; atol=atol) || error("""
435+
return isapprox(pts...; atol=atol) || @error """
436436
Could not automatically route to destination with the correct arrival point \
437437
(got $(p1(path)) instead of $p_end). Try adding or adjusting waypoints.\
438-
""")
438+
"""
439439
end
440440

441441
"""

src/schematics/schematics.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,6 +1041,7 @@ function _plan_route!(sch::Schematic, node_cs, node, hooks_fn=hooks)
10411041
comp1.r.waypoints = rot.(comp1.r.waypoints) .+ Ref(comp1.r.p0)
10421042
comp1.r.waydirs = comp1.r.waydirs .+ comp1.r.α0
10431043
end
1044+
Paths._update_rule!(sch, node, comp1.r.rule)
10441045
return addref!(node_cs, comp1)
10451046
end
10461047

test/test_pathfinding.jl

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using DeviceLayout, .SchematicDrivenLayout, .PreferredUnits
2+
using FileIO
3+
4+
g = SchematicGraph("test")
5+
6+
cs = CoordinateSystem("basic")
7+
place!(cs, centered(Rectangle(0.100mm, 0.100mm)), :obstacle)
8+
hooks = (;
9+
port1=PointHook(0.05mm, -0.0mm, 180°),
10+
port2=PointHook(-0.05mm, 0.0mm, 0°),
11+
north=PointHook(0mm, 0.5mm, -90°),
12+
south=PointHook(0mm, -0.5mm, 90°),
13+
west=PointHook(-0.5mm, 0mm, 0°),
14+
east=PointHook(0.5mm, 0mm, 180°)
15+
)
16+
comp = BasicComponent(cs, hooks)
17+
n1 = add_node!(g, comp)
18+
n2 = fuse!(g, n1 => :north, comp => :south)
19+
n3 = fuse!(g, n1 => :south, comp => :north)
20+
n4 = fuse!(g, n1 => :east, comp => :west)
21+
n5 = fuse!(g, n1 => :west, comp => :east)
22+
23+
rule = Paths.AStarRouting(
24+
Paths.StraightAnd90(min_bend_radius=50μm),
25+
centered(Rectangle(5e6nm, 5e6nm)),
26+
0.25mm,
27+
make_halo(0.05mm)
28+
)
29+
route!(g, rule, n5 => :port2, n1 => :port1, Paths.CPW(10μm, 6μm), SemanticMeta(:route))
30+
route!(g, rule, n1 => :port2, n3 => :port2, Paths.CPW(10μm, 6μm), SemanticMeta(:route))
31+
route!(g, rule, n5 => :port1, n4 => :port2, Paths.CPW(10μm, 6μm), SemanticMeta(:route))
32+
route!(g, rule, n2 => :port1, n4 => :port1, Paths.CPW(10μm, 6μm), SemanticMeta(:route))
33+
route!(g, rule, n3 => :port1, n2 => :port2, Paths.CPW(10μm, 6μm), SemanticMeta(:route))
34+
35+
@time sch = plan(g; log_dir=nothing, strict=:no)
36+
37+
c = Cell("test")
38+
render!(c, sch.coordinate_system; map_meta=_ -> GDSMeta())
39+
save("test.gds", flatten(c; name="flatten"))

0 commit comments

Comments
 (0)