Skip to content

Commit 14aa479

Browse files
authored
Fix copy and deepcopy for PolygonTrees and PolygonHierarchys (#199)
1 parent 296255b commit 14aa479

File tree

3 files changed

+81
-63
lines changed

3 files changed

+81
-63
lines changed

NEWS.md

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
# Changelog
22

33
## 1.6.0
4-
- Feature: Define `reverse` for `AbstractParametricCurve`s, making it easier to reverse the orientation of a curve. See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
5-
- Bugfix: Fixed an issue with `LineSegment` not returning the exact endpoints at `t=1`, which can be problematic when joining boundary nodes. This has been fixed. See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
6-
- Bugfix: Introduced `is_linear` to fix issues with boundary enrichment of domains with `LineSegment`s. In particular, `LineSegment`s are no longer enriched. See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
7-
- Bugfix: `orientation_markers` now uses `uniquetol` instead of `unique` for the final set of markers (it already did it for the intermediate markers). See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
8-
- Bugfix: For large `Tuple`s, functions like `eval_fnc_at_het_tuple_two_elements` are problematic and allocate more than their non-type-stable counterparts. To get around this, for `Tuple`s of length `N > 32`, the non-type-stable version is used. See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
9-
- Bigfix: Fixed issue with `use_barriers` when a ghost edge is selected at random during point location. See [#196](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/196).
10-
- Feature: Introduced the (currently internal) function `get_positive_curve_indices` for finding curves with positive orientation in a `Triangulation`. [#196](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/196).
4+
- Define `reverse` for `AbstractParametricCurve`s, making it easier to reverse the orientation of a curve. See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
5+
- Fixed an issue with `LineSegment` not returning the exact endpoints at `t=1`, which can be problematic when joining boundary nodes. This has been fixed. See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
6+
- Introduced `is_linear` to fix issues with boundary enrichment of domains with `LineSegment`s. In particular, `LineSegment`s are no longer enriched. See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
7+
- `orientation_markers` now uses `uniquetol` instead of `unique` for the final set of markers (it already did it for the intermediate markers). See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
8+
- For large `Tuple`s, functions like `eval_fnc_at_het_tuple_two_elements` are problematic and allocate more than their non-type-stable counterparts. To get around this, for `Tuple`s of length `N > 32`, the non-type-stable version is used. See [#195](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/195).
9+
- Fixed issue with `use_barriers` when a ghost edge is selected at random during point location. See [#196](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/196).
10+
- Introduced the (currently internal) function `get_positive_curve_indices` for finding curves with positive orientation in a `Triangulation`. [#196](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/196).
1111
- `is_exterior_curve`, `is_interior_curve`, `num_exterior_curves`, and `is_disjoint` are now defined based on `get_positive_curve_indices` rather than `get_exterior_curve_indices`. See [#196](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/196).
12+
- `copy` and `deepcopy` are now correctly implemented for `PolygonTree`s and `PolygonHierarchy`s. See [#199](https://github.com/JuliaGeometry/DelaunayTriangulation.jl/pull/199)
1213

1314
## 1.5.0
1415

src/data_structures/trees/polygon_hierarchy.jl

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,19 @@ A tree structure used to define a polygon hierarchy.
1616
Constructs a [`PolygonTree`](@ref) with `parent`, `index`, and `height`, and no children.
1717
"""
1818
mutable struct PolygonTree{I}
19-
parent::Union{Nothing, PolygonTree{I}}
19+
parent::Union{Nothing,PolygonTree{I}}
2020
@const children::Set{PolygonTree{I}}
2121
@const index::I
2222
height::Int
2323
end
24-
PolygonTree{I}(parent::Union{Nothing, PolygonTree{I}}, index, height) where {I} = PolygonTree{I}(parent, Set{PolygonTree{I}}(), index, height)
24+
PolygonTree{I}(parent::Union{Nothing,PolygonTree{I}}, index, height) where {I} = PolygonTree{I}(parent, Set{PolygonTree{I}}(), index, height)
2525
function hash_tree(tree::PolygonTree)
2626
height = get_height(tree)
2727
index = get_index(tree)
2828
parent_index = has_parent(tree) ? get_index(get_parent(tree)) : 0
2929
h = hash((parent_index, index, height))
3030
children = collect(get_children(tree))
31-
sort!(children, by = get_index)
31+
sort!(children, by=get_index)
3232
for child in children
3333
h = hash((h, hash_tree(child)))
3434
end
@@ -46,22 +46,23 @@ function Base.:(==)(tree1::PolygonTree, tree2::PolygonTree)
4646
height1 height2 && return false
4747
return hash_tree(tree1) == hash_tree(tree2)
4848
end
49-
@static if VERSION v"1.10"
50-
function Base.deepcopy(tree::PolygonTree) # without this definition, deepcopy would occassionally segfault
51-
@warn "Deepcopy on PolygonTrees is not currently supported. Returning the tree without copying." maxlog = 1 # https://github.com/JuliaGeometry/DelaunayTriangulation.jl/issues/129
52-
return tree
53-
#=
54-
parent = get_parent(tree)
55-
children = get_children(tree)
56-
index = get_index(tree)
57-
height = get_height(tree)
58-
new_parent = isnothing(parent) ? nothing : deepcopy(parent)
59-
new_children = deepcopy(children)
60-
new_index = deepcopy(index)
61-
return PolygonTree(new_parent, new_children, new_index, height)
62-
=# # Why does the above still segfault sometimes?
63-
# TODO: Fix
49+
function Base.deepcopy_internal(tree::PolygonTree{I}, dict::IdDict) where {I}
50+
haskey(dict, tree) && return dict[tree]
51+
copy_tree = PolygonTree{I}(nothing, get_index(tree), get_height(tree))
52+
dict[tree] = copy_tree
53+
new_parent = Base.deepcopy_internal(get_parent(tree), dict)
54+
if !isnothing(new_parent)
55+
set_parent!(copy_tree, new_parent)
6456
end
57+
for child in get_children(tree)
58+
add_child!(copy_tree, Base.deepcopy_internal(child, dict))
59+
end
60+
return copy_tree
61+
end
62+
function Base.copy(tree::PolygonTree{I}) where {I}
63+
parent = get_parent(tree)
64+
new_parent = isnothing(parent) ? nothing : copy(parent)
65+
return PolygonTree{I}(new_parent, copy(get_children(tree)), copy(get_index(tree)), copy(get_height(tree)))
6566
end
6667
function Base.show(io::IO, ::MIME"text/plain", tree::PolygonTree)
6768
print(io, "PolygonTree at height $(get_height(tree)) with index $(get_index(tree)) and $(length(get_children(tree))) children")
@@ -158,7 +159,7 @@ Struct used to define a polygon hierarchy. The hierarchy is represented as a for
158159
- `polygon_orientations::BitVector`: A `BitVector` of length `n` where `n` is the number of polygons in the hierarchy. The `i`th entry is `true` if the `i`th polygon is positively oriented, and `false` otherwise.
159160
- `bounding_boxes::Vector{BoundingBox}`: A `Vector` of [`BoundingBox`](@ref)s of length `n` where `n` is the number of polygons in the hierarchy. The `i`th entry is the [`BoundingBox`](@ref) of the `i`th polygon.
160161
- `trees::Dict{I,PolygonTree{I}}`: A `Dict` mapping the index of a polygon to its [`PolygonTree`](@ref). The keys of `trees` are the roots of each individual tree, i.e. the outer-most polygons.
161-
- `reorder_cache::Vector{PolygonTree{I}}`: A `Vector used for caching trees to be deleted in [`reorder_subtree!`](@ref).
162+
- `reorder_cache::Vector{PolygonTree{I}}`: A `Vector` used for caching trees to be deleted in [`reorder_subtree!`](@ref).
162163
163164
!!! note "One-based indexing"
164165
@@ -174,29 +175,40 @@ Constructs a [`PolygonHierarchy`](@ref) with no polygons.
174175
struct PolygonHierarchy{I}
175176
polygon_orientations::BitVector
176177
bounding_boxes::Vector{BoundingBox}
177-
trees::Dict{I, PolygonTree{I}}
178+
trees::Dict{I,PolygonTree{I}}
178179
reorder_cache::Vector{PolygonTree{I}}
179180
end
180-
PolygonHierarchy{I}() where {I} = PolygonHierarchy{I}(BitVector(), BoundingBox[], Dict{I, PolygonTree{I}}(), PolygonTree{I}[])
181-
@static if VERSION v"1.10"
182-
function Base.deepcopy(hierarchy::PolygonHierarchy{I}) where {I} # without this definition, deepcopy would occassionally segfault
183-
polygon_orientations = get_polygon_orientations(hierarchy)
184-
bounding_boxes = get_bounding_boxes(hierarchy)
185-
trees = get_trees(hierarchy)
186-
reorder_cache = get_reorder_cache(hierarchy)
187-
new_polygon_orientations = copy(polygon_orientations)
188-
new_bounding_boxes = copy(bounding_boxes)
189-
new_trees = Dict{I, PolygonTree{I}}()
190-
for (index, tree) in trees
191-
new_trees[index] = deepcopy(tree)
192-
end
193-
new_reorder_cache = similar(reorder_cache)
194-
for (index, tree) in enumerate(reorder_cache)
195-
new_reorder_cache[index] = deepcopy(tree)
196-
end
197-
return PolygonHierarchy{I}(new_polygon_orientations, new_bounding_boxes, new_trees, new_reorder_cache)
181+
PolygonHierarchy{I}() where {I} = PolygonHierarchy{I}(BitVector(), BoundingBox[], Dict{I,PolygonTree{I}}(), PolygonTree{I}[])
182+
function Base.deepcopy_internal(hierarchy::PolygonHierarchy{I}, dict::IdDict) where {I}
183+
haskey(dict, hierarchy) && return dict[hierarchy]
184+
copy_polygon_orientations = Base.deepcopy_internal(get_polygon_orientations(hierarchy), dict)
185+
copy_bounding_boxes = Base.deepcopy_internal(get_bounding_boxes(hierarchy), dict)
186+
copy_trees = Dict{I, PolygonTree{I}}()
187+
for (key, tree) in get_trees(hierarchy)
188+
copy_trees[key] = Base.deepcopy_internal(tree, dict)
189+
end
190+
copy_reorder_cache = Vector{PolygonTree{I}}()
191+
for tree in get_reorder_cache(hierarchy)
192+
push!(copy_reorder_cache, Base.deepcopy_internal(tree, dict))
198193
end
194+
copy_hierarchy = PolygonHierarchy(
195+
copy_polygon_orientations,
196+
copy_bounding_boxes,
197+
copy_trees,
198+
copy_reorder_cache
199+
)
200+
dict[hierarchy] = copy_hierarchy
201+
return copy_hierarchy
199202
end
203+
function Base.copy(hierarchy::PolygonHierarchy{I}) where {I}
204+
return PolygonHierarchy(
205+
copy(get_polygon_orientations(hierarchy)),
206+
copy(get_bounding_boxes(hierarchy)),
207+
copy(get_trees(hierarchy)),
208+
copy(get_reorder_cache(hierarchy))
209+
)
210+
end
211+
200212
function Base.empty!(hierarchy::PolygonHierarchy)
201213
polygon_orientations = get_polygon_orientations(hierarchy)
202214
bounding_boxes = get_bounding_boxes(hierarchy)
@@ -288,7 +300,7 @@ get_exterior_curve_indices(hierarchy::PolygonHierarchy) = keys(get_trees(hierarc
288300
Returns the indices of the positively oriented curves of `hierarchy` as a generator, i.e.
289301
as a lazy result.
290302
"""
291-
function get_positive_curve_indices(hierarchy::PolygonHierarchy)
303+
function get_positive_curve_indices(hierarchy::PolygonHierarchy)
292304
orientations = get_polygon_orientations(hierarchy)
293305
return (index for (index, orientation) in enumerate(orientations) if orientation)
294306
end
@@ -365,7 +377,7 @@ end
365377
366378
Returns a [`PolygonHierarchy`](@ref) defining the polygon hierarchy for a given set of `points`. This defines a hierarchy with a single polygon.
367379
"""
368-
function construct_polygon_hierarchy(points; IntegerType = Int)
380+
function construct_polygon_hierarchy(points; IntegerType=Int)
369381
hierarchy = PolygonHierarchy{IntegerType}()
370382
return construct_polygon_hierarchy!(hierarchy, points)
371383
end
@@ -385,11 +397,11 @@ end
385397
Returns a [`PolygonHierarchy`](@ref) defining the polygon hierarchy for a given set of `boundary_nodes` that define a set of piecewise
386398
linear curves.
387399
"""
388-
function construct_polygon_hierarchy(points, boundary_nodes; IntegerType = Int)
400+
function construct_polygon_hierarchy(points, boundary_nodes; IntegerType=Int)
389401
hierarchy = PolygonHierarchy{IntegerType}()
390402
return construct_polygon_hierarchy!(hierarchy, points, boundary_nodes)
391403
end
392-
construct_polygon_hierarchy(points, ::Nothing; IntegerType = Int) = construct_polygon_hierarchy(points; IntegerType)
404+
construct_polygon_hierarchy(points, ::Nothing; IntegerType=Int) = construct_polygon_hierarchy(points; IntegerType)
393405
function construct_polygon_hierarchy!(hierarchy::PolygonHierarchy{I}, points, boundary_nodes) where {I}
394406
if !has_boundary_nodes(boundary_nodes)
395407
return construct_polygon_hierarchy!(hierarchy, points)
@@ -611,7 +623,7 @@ end
611623
612624
Expands the bounding boxes of `hierarchy` by a factor of `perc` in each direction.
613625
"""
614-
function expand_bounds!(hierarchy::PolygonHierarchy, perc = 0.1)
626+
function expand_bounds!(hierarchy::PolygonHierarchy, perc=0.1)
615627
bboxes = get_bounding_boxes(hierarchy)
616628
for (i, bbox) in enumerate(bboxes)
617629
bboxes[i] = expand(bbox, perc)
@@ -634,9 +646,9 @@ from the curves in `boundary_curves`. Uses [`polygonise`](@ref) to fill in the b
634646
- `IntegerType=Int`: The integer type to use for indexing the polygons.
635647
- `n=4096`: The number of points to use for filling in the boundary curves in [`polygonise`](@ref).
636648
"""
637-
function construct_polygon_hierarchy(points, boundary_nodes, boundary_curves; IntegerType = Int, n = 4096)
649+
function construct_polygon_hierarchy(points, boundary_nodes, boundary_curves; IntegerType=Int, n=4096)
638650
new_points, new_boundary_nodes = polygonise(points, boundary_nodes, boundary_curves; n)
639651
hierarchy = PolygonHierarchy{IntegerType}()
640652
return construct_polygon_hierarchy!(hierarchy, new_points, new_boundary_nodes)
641653
end
642-
construct_polygon_hierarchy(points, boundary_nodes, ::Tuple{}; IntegerType = Int, n = 4096) = construct_polygon_hierarchy(points, boundary_nodes; IntegerType)
654+
construct_polygon_hierarchy(points, boundary_nodes, ::Tuple{}; IntegerType=Int, n=4096) = construct_polygon_hierarchy(points, boundary_nodes; IntegerType)

test/data_structures/polygon_hierarchy.jl

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ for _ in 1:20 # Run many times to make sure the segfault is gone
4242
]
4343

4444
curve_IV = [CircularArc((1.0, 0.0), (1.0, 0.0), (0.0, 0.0))]
45-
points_IV = NTuple{2, Float64}[]
45+
points_IV = NTuple{2,Float64}[]
4646

4747
curve_V = [BezierCurve([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)])]
4848
points_V = [(0.0, 0.0), (0.2, 0.25)]
@@ -73,13 +73,13 @@ for _ in 1:20 # Run many times to make sure the segfault is gone
7373

7474
curve_IX =
7575
[
76-
[
77-
[1, 2, 3, 4, 5, 6, 7, 1],
78-
],
79-
[
80-
[CircularArc((0.6, 0.5), (0.6, 0.5), (0.5, 0.5), positive = false)],
81-
],
82-
]
76+
[
77+
[1, 2, 3, 4, 5, 6, 7, 1],
78+
],
79+
[
80+
[CircularArc((0.6, 0.5), (0.6, 0.5), (0.5, 0.5), positive=false)],
81+
],
82+
]
8383
points_IX = [(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.5, 1.5), (0.0, 1.0), (0.0, 0.5), (0.0, 0.2)]
8484

8585
curve_X = [
@@ -111,7 +111,7 @@ for _ in 1:20 # Run many times to make sure the segfault is gone
111111
[12, 11, 10, 12],
112112
],
113113
[
114-
[CircularArc((1.1, -3.0), (1.1, -3.0), (0.0, -3.0), positive = false)],
114+
[CircularArc((1.1, -3.0), (1.1, -3.0), (0.0, -3.0), positive=false)],
115115
],
116116
]
117117
points_XI = [(-2.0, 0.0), (0.0, 0.0), (2.0, 0.0), (-2.0, -5.0), (2.0, -5.0), (2.0, -1 / 10), (-2.0, -1 / 10), (-1.0, -3.0), (0.0, -4.0), (0.0, -2.3), (-0.5, -3.5), (0.9, -3.0)]
@@ -335,11 +335,16 @@ for _ in 1:20 # Run many times to make sure the segfault is gone
335335
@test traverse_tree(hierarchy.trees[7]) traverse_tree(hierarchy.trees[15])
336336
@test compare_trees(hierarchy, hierarchy)
337337
@test compare_trees(deepcopy(hierarchy), deepcopy(hierarchy))
338+
@test compare_trees(hierarchy, deepcopy(hierarchy))
339+
@test compare_trees(hierarchy, copy(hierarchy))
340+
@test compare_trees(copy(hierarchy), copy(hierarchy))
341+
@test !(hierarchy === deepcopy(hierarchy))
338342
@test !compare_trees(hierarchy, hierarchy2)
339343
@test hierarchy.trees[7] hierarchy.trees[15]
340344
@test hierarchy == hierarchy
341345
@test deepcopy(hierarchy) == hierarchy
342346
@test deepcopy(hierarchy) == deepcopy(hierarchy)
347+
@test !(hierarchy === deepcopy(hierarchy))
343348
@test hierarchy hierarchy2
344349
@test deepcopy(hierarchy) hierarchy2
345350
end
@@ -357,7 +362,7 @@ for _ in 1:20 # Run many times to make sure the segfault is gone
357362
hierarchy = DT.construct_polygon_hierarchy(points, nnew_boundary_nodes, boundary_curves)
358363
DT.expand_bounds!(hierarchy, DT.ε(Float64))
359364
@test DT.get_bounding_boxes(hierarchy) [DT.BoundingBox(-1 - 2DT.ε(Float64), 1 + 2DT.ε(Float64), -1 - 2DT.ε(Float64), 1 + 2DT.ε(Float64))] &&
360-
DT.get_polygon_orientations(hierarchy) BitVector([1])
365+
DT.get_polygon_orientations(hierarchy) BitVector([1])
361366
trees = DT.get_trees(hierarchy)
362367
@test length(trees) == 1
363368
tree = trees[1]
@@ -459,4 +464,4 @@ for _ in 1:20 # Run many times to make sure the segfault is gone
459464
tree2 = trees[2]
460465
@test DT.get_index(tree2) == 2 && DT.get_height(tree2) == 0 && !DT.has_parent(tree2) && !DT.has_children(tree2)
461466
end
462-
end
467+
end

0 commit comments

Comments
 (0)