Skip to content

Commit da4a214

Browse files
authored
Merge pull request #9 from sjkelly/sjk/sort2
Sjk/sort2
2 parents 1b2d4d5 + 7c7cf15 commit da4a214

File tree

8 files changed

+222
-160
lines changed

8 files changed

+222
-160
lines changed

bench/hilbert_perf.jl

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using DistMesh
2+
using BenchmarkTools
3+
using StaticArrays
4+
5+
a = [rand(SVector{3,Float64}) for i = 1:50000]
6+
7+
function test_hilbert(a)
8+
b = copy(a)
9+
DistMesh.hilbertsort!(b)
10+
end
11+
12+
@benchmark test_hilbert(a)

bench/perf.jl

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ println("Benchmarking DistMesh.jl...")
1818
# Algorithms to benchmark
1919
#
2020

21-
algos = [DistMeshSetup(deltat=0.1, distribution=:packed),DistMeshSetup(distribution=:packed)]
21+
algos = [DistMeshSetup(deltat=0.1, distribution=:packed),
22+
DistMeshSetup(distribution=:packed),
23+
DistMeshSetup(deltat=0.1, distribution=:packed,sort=true),
24+
DistMeshSetup(distribution=:packed,sort=true)]
2225

2326
fn_torus(v) = (sqrt(v[1]^2+v[2]^2)-0.5)^2 + v[3]^2 - 0.25 # torus
2427
fn_sphere(v) = sqrt(sum(v.^2)) -1

src/DistMesh.jl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,38 @@ abstract type AbstractDistMeshAlgorithm end
1313
"""
1414
DistMeshSetup
1515
16-
iso (default: 0): Value of which to extract the iso surface, inside negative
16+
Takes Keyword arguments as follows:
17+
18+
iso (default: 0): Value of which to extract the isosurface, inside surface is negative
1719
deltat (default: 0.1): the fraction of edge displacement to apply each iteration
20+
sort (default:false): If true and no fixed points, sort points using a hilbert sort.
21+
sort_interval (default:20) Use hilbert sort after the specified retriangulations
22+
distribution (default: :regular) Initial point distribution, either :regular or :packed.
1823
"""
1924
struct DistMeshSetup{T} <: AbstractDistMeshAlgorithm
2025
iso::T
2126
deltat::T
2227
ttol::T
2328
ptol::T
29+
sort::Bool # use hilbert sort to cache-localize points
30+
sort_interval::Int # retriangulations before resorting
2431
distribution::Symbol # intial point distribution
2532
end
2633

2734
function DistMeshSetup(;iso=0,
2835
ptol=.001,
2936
deltat=0.05,
3037
ttol=0.02,
38+
sort=false,
39+
sort_interval=20,
3140
distribution=:regular)
3241
T = promote_type(typeof(iso),typeof(ptol),typeof(deltat), typeof(ttol))
3342
DistMeshSetup{T}(iso,
3443
deltat,
3544
ttol,
3645
ptol,
46+
sort,
47+
sort_interval,
3748
distribution)
3849
end
3950

@@ -92,6 +103,7 @@ include("distmeshnd.jl")
92103
include("tetgen.jl")
93104
include("quality.jl")
94105
include("decompositions.jl")
106+
include("hilbertsort.jl")
95107

96108
#export distmeshsurface
97109
export distmesh, DistMeshSetup, DistMeshStatistics, HUniform

src/distmeshnd.jl

Lines changed: 14 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -89,147 +89,20 @@ function distmesh(fdist::Function,
8989
# information on each iteration
9090
statsdata = DistMeshStatistics()
9191
lcount = 0 # iteration counter
92+
triangulationcount = 0 # triangulation counter
9293

9394
@inbounds while true
9495
# if large move, retriangulation
9596
if maxmove>setup.ttol*h
9697

97-
delaunayn!(fdist, p, t, geps) # compute a new delaunay triangulation
98-
99-
tet_to_edges!(pair, pair_set, t) # Describe each edge by a unique pair of nodes
100-
101-
# resize arrays for new pair counts
102-
resize!(bars, length(pair))
103-
resize!(L, length(pair))
104-
non_uniform && resize!(L0, length(pair))
105-
106-
stats && push!(statsdata.retriangulations, lcount)
107-
end
108-
109-
compute_displacements!(fh, dp, pair, L, L0, bars, p, setup, VertType)
110-
111-
# Zero out forces on fix points
112-
if have_fixed
113-
for i in eachindex(fix)
114-
dp[i] = zero(VertType)
115-
end
116-
end
117-
118-
# apply point forces and
119-
# bring outside points back to the boundary
120-
maxdp = typemin(eltype(VertType))
121-
maxmove = typemin(eltype(VertType))
122-
for i in eachindex(p)
123-
124-
p0 = p[i] # store original point location
125-
p[i] = p[i].+setup.deltat.*dp[i] # apply displacements to points
126-
127-
# Check if we are verifiably within the bounds and use this value
128-
# to avoid recomputing fdist. This increases performance greatly on
129-
# complex distance functions and large node counts
130-
move = sqrt(sum((p[i]-p0).^2)) # compute movement from the displacement
131-
d_est = pt_dists[i] + move # apply the movement to our cache point
132-
d = d_est < -geps ? d_est : fdist(p[i]) # determine if we need correct or approximate distance
133-
pt_dists[i] = d # store distance
134-
135-
if d < -geps
136-
maxdp = max(maxdp, setup.deltat*sqrt(sum(dp[i].^2)))
98+
# use hilbert sort to improve cache locality of points
99+
if setup.sort && iszero(triangulationcount % setup.sort_interval)
100+
hilbertsort!(p)
137101
end
138102

139-
if d <= setup.iso
140-
maxmove = max(move,maxmove)
141-
continue
142-
end
143-
144-
# bring points back to boundary if outside using central difference
145-
p[i] = p[i] .- centraldiff(fdist,p[i]).*(d+setup.iso)
146-
maxmove = max(sqrt(sum((p[i]-p0).^2)), maxmove)
147-
pt_dists[i] = setup.iso # ideally
148-
end
149-
150-
# increment iteration counter
151-
lcount = lcount + 1
152-
153-
# save iteration stats
154-
if stats
155-
push!(statsdata.maxmove,maxmove)
156-
push!(statsdata.maxdp,maxdp)
157-
triangle_qualities!(tris,triset,qualities,p,t)
158-
sort!(qualities) # sort for median calc and robust summation
159-
mine, maxe = extrema(qualities)
160-
push!(statsdata.average_qual, sum(qualities)/length(qualities))
161-
push!(statsdata.median_qual, qualities[round(Int,length(qualities)/2)])
162-
push!(statsdata.minimum_qual, mine)
163-
push!(statsdata.maximum_qual, maxe)
164-
end
165-
166-
# Termination criterion
167-
if maxdp<setup.ptol*h
168-
return p, t, statsdata
169-
end
170-
end
171-
end
172-
173-
function distmesh(fdist::Function,
174-
fh,
175-
h::Number,
176-
setup::DistMeshQuality,
177-
origin,
178-
widths,
179-
fix,
180-
::Val{stats},
181-
::Type{VertType}) where {VertType, stats}
182-
183-
geps=1e-1*h+setup.iso # parameter for filtering tets outside bounds and considering for max displacment of a node
184-
185-
# static parameter info
186-
non_uniform = isa(fh, Function) # so we can elide fh calls
187-
have_fixed = !isa(fix, Nothing)
188-
189-
#ptol=.001; ttol=.1; L0mult=1+.4/2^(dim-1); deltat=.2; geps=1e-1*h;
190-
191-
# initialize Vertex Arrays
192-
# the first N points in the array correspond to'fix' points that do not move
193-
if have_fixed
194-
p = copy(fix)
195-
else
196-
p = VertType[]
197-
end
198-
199-
pt_dists = map(fdist, p) # cache to store point locations so we can minimize fdist calls
200-
201-
# add points to p based on the initial distribution
202-
if setup.distribution === :regular
203-
simplecubic!(fdist, p, pt_dists, h, setup.iso, origin, widths, VertType)
204-
elseif setup.distribution === :packed
205-
# face-centered cubic point distribution
206-
facecenteredcubic!(fdist, p, pt_dists, h, setup.iso, origin, widths, VertType)
207-
end
208-
209-
# initialize arrays
210-
pair_set = Set{Tuple{Int32,Int32}}() # set used for ensure we have a unique set of edges
211-
pair = Tuple{Int32,Int32}[] # edge indices (Int32 since we use Tetgen)
212-
dp = zeros(VertType, length(p)) # displacement at each node
213-
bars = VertType[] # the vector of each edge
214-
L = eltype(VertType)[] # vector length of each edge
215-
non_uniform && (L0 = eltype(VertType)[]) # desired edge length computed by dh (edge length function)
216-
t = GeometryBasics.SimplexFace{4,Int32}[] # tetrahedra indices from delaunay triangulation
217-
maxmove = typemax(eltype(VertType)) # stores an iteration max movement for retriangulation
218-
219-
# arrays for tracking quality metrics
220-
tris = NTuple{3,Int32}[] # triangles used for quality checks
221-
triset = Set{NTuple{3,Int32}}() # set for triangles to ensure uniqueness
222-
qualities = eltype(VertType)[]
223-
224-
# information on each iteration
225-
statsdata = DistMeshStatistics()
226-
lcount = 0 # iteration counter
227-
228-
@inbounds while true
229-
# if large move, retriangulation
230-
if maxmove>setup.ttol*h
231-
232-
delaunayn!(fdist, p, t, geps) # compute a new delaunay triangulation
103+
# compute a new delaunay triangulation
104+
# we use the default random insertion method
105+
delaunayn!(fdist, p, t, geps, false)
233106

234107
tet_to_edges!(pair, pair_set, t) # Describe each edge by a unique pair of nodes
235108

@@ -238,6 +111,13 @@ function distmesh(fdist::Function,
238111
resize!(L, length(pair))
239112
non_uniform && resize!(L0, length(pair))
240113

114+
# if the points were sorted we need to update the distance cache
115+
if setup.sort && iszero(triangulationcount % setup.sort_interval)
116+
for i in eachindex(p)
117+
pt_dists[i] = fdist(p[i])
118+
end
119+
end
120+
triangulationcount += 1
241121
stats && push!(statsdata.retriangulations, lcount)
242122
end
243123

src/hilbertsort.jl

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# implementing scale-free Hilbert ordering. Real all about it here:
2+
# http://doc.cgal.org/latest/Spatial_sorting/index.html
3+
4+
# original implementation in:
5+
# https://github.com/JuliaGeometry/GeometricalPredicates.jl
6+
# The GeometricalPredicates.jl package is licensed under the MIT "Expat" License:
7+
# Copyright (c) 2014: Ariel Keselman.
8+
9+
# modifications for StaticArrays: sjkelly
10+
11+
const coordinatex = 1
12+
const coordinatey = 2
13+
const coordinatez = 3
14+
next2d(c) = c % 2 + 1
15+
next3d(c) = c % 3 + 1
16+
nextnext3d(c) = (c + 1) % 3 + 1
17+
18+
const forward = true
19+
const backward = false
20+
21+
function select!(direction, coord, v::Array{T,1}, k::Integer, lo::Integer, hi::Integer) where T<:AbstractVector
22+
#lo <= k <= hi || error("select index $k is out of range $lo:$hi")
23+
if direction == forward
24+
@inbounds while lo < hi
25+
if isone(hi-lo)
26+
if v[hi][coord] < v[lo][coord]
27+
v[lo], v[hi] = v[hi], v[lo]
28+
end
29+
return #v[k]
30+
end
31+
pivot = v[(lo+hi)>>>1]
32+
i, j = lo, hi
33+
while true
34+
pivot_elt = pivot[coord]
35+
while v[i][coord] < pivot_elt; i += 1; end
36+
while pivot_elt < v[j][coord]; j -= 1; end
37+
i <= j || break
38+
v[i], v[j] = v[j], v[i]
39+
i += 1; j -= 1
40+
end
41+
if k <= j
42+
hi = j
43+
elseif i <= k
44+
lo = i
45+
else
46+
return #pivot
47+
end
48+
end
49+
else
50+
@inbounds while lo < hi
51+
if isone(hi-lo)
52+
if v[hi][coord] > v[lo][coord]
53+
v[lo], v[hi] = v[hi], v[lo]
54+
end
55+
return #v[k]
56+
end
57+
pivot = v[(lo+hi)>>>1]
58+
i, j = lo, hi
59+
while true
60+
pivot_elt = pivot[coord]
61+
while v[i][coord] > pivot_elt; i += 1; end
62+
while pivot_elt > v[j][coord]; j -= 1; end
63+
i <= j || break
64+
v[i], v[j] = v[j], v[i]
65+
i += 1; j -= 1
66+
end
67+
if k <= j
68+
hi = j
69+
elseif i <= k
70+
lo = i
71+
else
72+
return #pivot
73+
end
74+
end
75+
end
76+
#return v[lo]
77+
nothing
78+
end
79+
80+
81+
# 2D version
82+
# function hilbertsort!(directionx::AbstractDirection, directiony::AbstractDirection, coordinate::AbstractCoordinate, a::Array{T,1}, lo::Int64, hi::Int64, lim::Int64=4) where T<:AbstractPoint2D
83+
# hi-lo <= lim && return a
84+
85+
# i2 = (lo+hi)>>>1
86+
# i1 = (lo+i2)>>>1
87+
# i3 = (i2+hi)>>>1
88+
89+
# select!(directionx, coordinate, a, i2, lo, hi)
90+
# select!(directiony, next2d(coordinate), a, i1, lo, i2)
91+
# select!(!directiony, next2d(coordinate), a, i3, i2, hi)
92+
93+
# hilbertsort!(directiony, directionx, next2d(coordinate), a, lo, i1, lim)
94+
# hilbertsort!(directionx, directiony, coordinate, a, i1, i2, lim)
95+
# hilbertsort!(directionx, directiony, coordinate, a, i2, i3, lim)
96+
# hilbertsort!(!directiony, !directionx, next2d(coordinate), a, i3, hi, lim)
97+
98+
# return a
99+
# end
100+
101+
function hilbertsort!(directionx, directiony, directionz, coordinate, a::Vector, lo::Integer, hi::Integer, lim::Integer=8)
102+
hi-lo <= lim && return a
103+
104+
i4 = (lo+hi)>>>1
105+
i2 = (lo+i4)>>>1
106+
i1 = (lo+i2)>>>1
107+
i3 = (i2+i4)>>>1
108+
i6 = (i4+hi)>>>1
109+
i5 = (i4+i6)>>>1
110+
i7 = (i6+hi)>>>1
111+
112+
select!(directionx, coordinate, a, i4, lo, hi)
113+
select!(directiony, next3d(coordinate), a, i2, lo, i4)
114+
select!(directionz, nextnext3d(coordinate), a, i1, lo, i2)
115+
select!(!directionz, nextnext3d(coordinate), a, i3, i2, i4)
116+
select!(!directiony, next3d(coordinate), a, i6, i4, hi)
117+
select!(directionz, nextnext3d(coordinate), a, i5, i4, i6)
118+
select!(!directionz, nextnext3d(coordinate), a, i7, i6, hi)
119+
120+
hilbertsort!( directionz, directionx, directiony, nextnext3d(coordinate), a, lo, i1, lim)
121+
hilbertsort!( directiony, directionz, directionx, next3d(coordinate), a, i1, i2, lim)
122+
hilbertsort!( directiony, directionz, directionx, next3d(coordinate), a, i2, i3, lim)
123+
hilbertsort!( directionx, !directiony, !directionz, coordinate, a, i3, i4, lim)
124+
hilbertsort!( directionx, !directiony, !directionz, coordinate, a, i4, i5, lim)
125+
hilbertsort!(!directiony, directionz, !directionx, next3d(coordinate), a, i5, i6, lim)
126+
hilbertsort!(!directiony, directionz, !directionx, next3d(coordinate), a, i6, i7, lim)
127+
hilbertsort!(!directionz, !directionx, directiony, nextnext3d(coordinate), a, i7, hi, lim)
128+
129+
return a
130+
end
131+
132+
#hilbertsort!(a::Array{T,1}) where {T<:AbstractPoint2D} = hilbertsort!(backward, backward, coordinatey, a, 1, length(a))
133+
#hilbertsort!(a::Array{T,1}, lo::Int64, hi::Int64, lim::Int64) where {T<:AbstractPoint2D} = hilbertsort!(backward, backward, coordinatey, a, lo, hi, lim)
134+
hilbertsort!(a::Vector) = hilbertsort!(backward, backward, backward, coordinatez, a, 1, length(a))
135+
hilbertsort!(a::Vector, lo::Int64, hi::Int64, lim::Int64) = hilbertsort!(backward, backward, backward, coordinatey, a, lo, hi, lim)

src/tetgen.jl

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ function delaunayn(points)
33
tetio
44
end
55

6-
function delaunayn!(fdist, p, t, geps)
7-
triangulation = delaunayn(p)
6+
function delaunayn_nosort(points)
7+
tetio = tetrahedralize(TetGen.TetgenIO(points), "Qb/1") # Q- Quiet
8+
tetio
9+
end
10+
11+
function delaunayn!(fdist, p, t, geps, sorted_pts)
12+
triangulation = sorted_pts ? delaunayn_nosort(p) : delaunayn(p)
813
t_d = triangulation.tetrahedra
914
resize!(t, length(t_d))
1015
copyto!(t, t_d) # we need to copy since we have a shared reference with tetgen

0 commit comments

Comments
 (0)