Skip to content

Commit b9ef1d6

Browse files
authored
Merge pull request #621 from JuliaRobotics/tkoolen/remove-subtree
Add remove_subtree!
2 parents 04aeaa1 + 2ea73b4 commit b9ef1d6

6 files changed

+132
-2
lines changed

src/RigidBodyDynamics.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ export
8787
replace_joint!,
8888
maximal_coordinates,
8989
submechanism,
90-
remove_fixed_tree_joints!
90+
remove_fixed_tree_joints!,
91+
remove_subtree!
9192

9293
# contact-related functionality
9394
export # note: contact-related functionality may be changed significantly in the future

src/graphs/Graphs.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export
3939
tree_index,
4040
ancestors,
4141
lowest_common_ancestor,
42+
subtree_vertices,
4243
direction,
4344
directions
4445

src/graphs/spanning_tree.jl

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ function SpanningTree(g::DirectedGraph{V, E}, root::V, flipped_edge_map::Union{A
8282
SpanningTree(g, root, tree_edges)
8383
end
8484

85-
# adds an edge and vertex to both the tree and the underlying graph
85+
"""
86+
Add an edge and vertex to both the tree and the underlying graph.
87+
"""
8688
function add_edge!(tree::SpanningTree{V, E}, source::V, target::V, edge::E) where {V, E}
8789
@assert target vertices(tree)
8890
add_edge!(tree.graph, source, target, edge)
@@ -96,6 +98,9 @@ function add_edge!(tree::SpanningTree{V, E}, source::V, target::V, edge::E) wher
9698
tree
9799
end
98100

101+
"""
102+
Replace an edge in both the tree and the underlying graph.
103+
"""
99104
function replace_edge!(tree::SpanningTree{V, E}, old_edge::E, new_edge::E) where {V, E}
100105
@assert old_edge edges(tree)
101106
src = source(old_edge, tree)
@@ -145,3 +150,21 @@ function lowest_common_ancestor(v1::V, v2::V, tree::SpanningTree{V, E}) where {V
145150
end
146151
v1
147152
end
153+
154+
"""
155+
Return a list of vertices in the subtree rooted at `subtree_root`, including `subtree_root` itself.
156+
The list is guaranteed to be topologically sorted.
157+
"""
158+
function subtree_vertices(subtree_root::V, tree::SpanningTree{V, E}) where {V, E}
159+
@assert subtree_root vertices(tree)
160+
frontier = [subtree_root]
161+
subtree_vertices = V[]
162+
while !isempty(frontier)
163+
parent = pop!(frontier)
164+
push!(subtree_vertices, parent)
165+
for child in out_neighbors(parent, tree)
166+
push!(frontier, child)
167+
end
168+
end
169+
return subtree_vertices
170+
end

src/mechanism_modification.jl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,41 @@ function submechanism(mechanism::Mechanism{T}, submechanismroot::RigidBody{T};
152152
ret
153153
end
154154

155+
"""
156+
Remove all bodies in the subtree rooted at `subtree_root`, including `subtree_root` itself,
157+
as well as all joints connected to these bodies.
158+
159+
The ordering of the joints that remain in the mechanism is retained.
160+
"""
161+
function remove_subtree!(mechanism::Mechanism{T}, subtree_root::RigidBody{T}) where {T}
162+
@assert subtree_root != root_body(mechanism)
163+
tree = mechanism.tree
164+
graph = mechanism.graph
165+
bodies_to_remove = subtree_vertices(subtree_root, tree)
166+
new_tree_joints = copy(edges(tree))
167+
for body in bodies_to_remove
168+
# Remove the tree joint from our new ordered list of joints.
169+
tree_joint = edge_to_parent(body, tree)
170+
deleteat!(new_tree_joints, findfirst(isequal(tree_joint), new_tree_joints))
171+
end
172+
for body in bodies_to_remove
173+
# Remove all edges to and from the vertex in the graph.
174+
for joint in copy(in_edges(body, graph))
175+
remove_edge!(graph, joint)
176+
end
177+
for joint in copy(out_edges(body, graph))
178+
remove_edge!(graph, joint)
179+
end
180+
181+
# Remove the vertex itself.
182+
remove_vertex!(graph, body)
183+
end
184+
# Construct a new spanning tree with the new list of tree joints.
185+
mechanism.tree = SpanningTree(graph, root_body(mechanism), new_tree_joints)
186+
canonicalize_graph!(mechanism)
187+
mechanism
188+
end
189+
155190
"""
156191
$(SIGNATURES)
157192

test/test_graph.jl

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,25 @@ Graphs.flip_direction(edge::Edge{Int32}) = Edge(-edge.data)
326326
end
327327
end
328328

329+
@testset "subtree_vertices" begin
330+
graph = DirectedGraph{Vertex{Int64}, Edge{Int32}}()
331+
root = Vertex(0)
332+
add_vertex!(graph, root)
333+
tree = SpanningTree(graph, root)
334+
for i = 1 : 30
335+
parent = vertices(tree)[rand(1 : num_vertices(graph))]
336+
child = Vertex(i)
337+
edge = Edge(Int32(i + 3))
338+
add_edge!(tree, parent, child, edge)
339+
end
340+
for subtree_root in vertices(tree)
341+
subtree = subtree_vertices(subtree_root, tree)
342+
for vertex in vertices(tree)
343+
@test (vertex subtree) == (subtree_root ancestors(vertex, tree))
344+
end
345+
end
346+
end
347+
329348
@testset "reindex!" begin
330349
Random.seed!(15)
331350
graph = DirectedGraph{Vertex{Int64}, Edge{Float64}}()

test/test_mechanism_modification.jl

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,4 +357,55 @@
357357
showerror(devnull, e)
358358
end
359359
end
360+
361+
@testset "remove_subtree! - tree mechanism" begin
362+
mechanism = parse_urdf(joinpath(@__DIR__, "urdf", "atlas.urdf"), floating=true)
363+
@test_throws AssertionError remove_subtree!(mechanism, root_body(mechanism))
364+
365+
original_joints = copy(joints(mechanism))
366+
367+
# Behead.
368+
num_bodies = length(bodies(mechanism))
369+
num_joints = length(joints(mechanism))
370+
head = findbody(mechanism, "head")
371+
neck_joint = joint_to_parent(head, mechanism)
372+
remove_subtree!(mechanism, head)
373+
@test length(bodies(mechanism)) == num_bodies - 1
374+
@test length(joints(mechanism)) == num_joints - 1
375+
@test head bodies(mechanism)
376+
@test neck_joint joints(mechanism)
377+
378+
# Lop off an arm.
379+
num_bodies = length(bodies(mechanism))
380+
num_joints = length(joints(mechanism))
381+
r_clav = findbody(mechanism, "r_clav")
382+
r_hand = findbody(mechanism, "r_hand")
383+
r_arm = path(mechanism, r_clav, r_hand)
384+
arm_joints = collect(r_arm)
385+
arm_bodies = [r_clav; map(joint -> successor(joint, mechanism), arm_joints)]
386+
remove_subtree!(mechanism, r_clav)
387+
@test length(joints(mechanism)) == num_joints - length(arm_joints) - 1
388+
@test length(bodies(mechanism)) == num_bodies - length(arm_bodies)
389+
@test isempty(intersect(arm_joints, joints(mechanism)))
390+
@test isempty(intersect(arm_bodies, bodies(mechanism)))
391+
@test issorted(joints(mechanism), by=joint ->findfirst(isequal(joint), original_joints))
392+
end
393+
394+
@testset "remove_subtree! - maximal coordinates" begin
395+
original = parse_urdf(joinpath(@__DIR__, "urdf", "atlas.urdf"), floating=true)
396+
mechanism = maximal_coordinates(original)
397+
num_bodies = length(bodies(mechanism))
398+
num_joints = length(joints(mechanism))
399+
@test_throws AssertionError remove_subtree!(mechanism, findbody(original, "head")) # body not in tree
400+
head = findbody(mechanism, "head")
401+
head_joints = copy(in_joints(head, mechanism))
402+
@test length(head_joints) == 2 # floating joint + neck loop joint
403+
remove_subtree!(mechanism, head)
404+
@test length(joints(mechanism)) == num_joints - length(head_joints)
405+
@test length(bodies(mechanism)) == num_bodies - 1
406+
for joint in head_joints
407+
@test joint joints(mechanism)
408+
end
409+
@test head bodies(mechanism)
410+
end
360411
end # mechanism modification

0 commit comments

Comments
 (0)