Skip to content

Commit 8dde767

Browse files
authored
Merge pull request #19 from JuliaGeometry/sjk/edge_qual
Sjk/edge qual
2 parents b117d92 + 6342a60 commit 8dde767

File tree

10 files changed

+246
-112
lines changed

10 files changed

+246
-112
lines changed

bench/benchmarks.jl

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ using Plots
44
using Dates
55
using Descartes
66
using GeometryBasics
7-
using JLD
8-
using HDF5
7+
#using JLD
8+
#using HDF5
99

1010
include("util.jl")
1111

@@ -60,31 +60,31 @@ println("Benchmarking DistMesh.jl...")
6060

6161
timestamp = Dates.format(now(), "yyyy-mm-ddTHH_MM")
6262
# generate an orthogonal test suite
63-
for ttol in 0.01:0.01:0.05, deltat in 0.02:0.02:0.2, el = 0.05:0.05:0.2
63+
for ttol in 0.01:0.01:0.05, deltat in 0.05:0.05:0.1, el = 0.1:0.05:0.2
6464
rt = time()
65-
p,t,s = distmesh(torus,
66-
huniform,
65+
result = distmesh(torus,
66+
HUniform(),
6767
el,
68-
DistMeshSetup(deltat=deltat, retriangulation_criteria=RetriangulateMaxMove(ttol)),
68+
DistMeshSetup(deltat=deltat,ttol=ttol,distribution=:packed),
6969
origin = GeometryBasics.Point{3,Float64}(-2),
7070
widths = GeometryBasics.Point{3,Float64}(4),
71-
stats=true,
72-
distribution=:packed)
71+
stats=true)
7372
running_time = time() - rt # approximate, since we mostly care about convergence factors
7473
item = "torus$timestamp"
75-
folder = joinpath(@__DIR__, "output/$item")
74+
folder = joinpath(@__DIR__, "output")
75+
!isdir(folder) && mkdir(folder)
76+
folder = joinpath(folder, "$item")
7677
!isdir(folder) && mkdir(folder)
7778
param_str = "_ttol=$(ttol)_deltat=$(deltat)_el=$(el)"
7879
# save plots
79-
plotout(s, DistMesh.triangle_qualities(p,t), folder, param_str)
80+
plotout(result.stats, DistMesh.triangle_qualities(result.points,result.tetrahedra), folder, param_str)
8081
# save dataset as JLD
81-
jldopen("$folder/$item.jld", "w") do file
82-
g = g_create(file, param_str)
83-
g["points"] = p
84-
g["tets"] = t
85-
g["stats"] = s
86-
g["running_time"] = running_time
87-
end
82+
# jldopen("$folder/$item.jld", "w") do file
83+
# g = g_create(file, param_str)
84+
# g["points"] = p
85+
# g["tets"] = t
86+
# g["stats"] = s
87+
# g["running_time"] = running_time
88+
# end
8889
println(param_str)
8990
end
90-

bench/util.jl

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,30 @@
22
function plotout(statsdata, qualities, folder, name)
33

44
qual_hist = Plots.histogram(qualities, title = "Quality", bins=30, legend=false)
5-
avg_plt = Plots.plot(statsdata.average_qual, title = "Average Quality", legend=false, ylabel="Quality")
6-
vline!(statsdata.retriangulations, line=(0.2, :dot, [:red]))
7-
med_plt = Plots.plot(statsdata.median_qual, title = "Median Quality", legend=false, ylabel="Quality")
5+
# avg_plt = Plots.plot(statsdata.average_qual, title = "Average Tri Quality", legend=false, ylabel="Quality")
6+
# vline!(statsdata.retriangulations, line=(0.2, :dot, [:red]))
7+
8+
# med_plt = Plots.plot(statsdata.median_qual, title = "Median Tri Quality", legend=false, ylabel="Quality")
9+
# vline!(statsdata.retriangulations, line=(0.2, :dot, [:red]))
10+
11+
# min_plt = Plots.plot(statsdata.minimum_qual, title = "Minimum Tri Quality", legend=false, ylabel="Quality")
12+
# vline!(statsdata.retriangulations, line=(0.2, :dot, [:red]))
13+
14+
# max_plt = Plots.plot(statsdata.maximum_qual, title = "Maximum Tri Quality", legend=false, ylabel="Quality")
15+
# vline!(statsdata.retriangulations, line=(0.2, :dot, [:red]))
16+
data = hcat(statsdata.average_volume_edge_ratio, statsdata.min_volume_edge_ratio, statsdata.max_volume_edge_ratio)
17+
18+
tq_plt = Plots.plot(data, title = "Tet Quality", legend=true, label=["Avg","Min","Max"], ylabel="Vol/Edge Ratio")
819
vline!(statsdata.retriangulations, line=(0.2, :dot, [:red]))
20+
921
maxdp_plt = Plots.plot(statsdata.maxdp, title = "Max Displacement", legend=false, ylabel="Edge Displacement")
1022
vline!(statsdata.retriangulations, line=(0.2, :dot, [:red]))
23+
1124
maxmove_plt = Plots.plot(statsdata.maxmove, title = "Max Move", legend=false, ylabel="Point Displacement")
1225
vline!(statsdata.retriangulations, line=(0.2, :dot, [:red]))
13-
plt = Plots.plot(avg_plt, med_plt,maxdp_plt,maxmove_plt,layout=(2,2), xlabel="Iteration")
26+
27+
plt = Plots.plot(tq_plt,maxdp_plt,maxmove_plt,layout=(3,1), xlabel="Iteration")
1428

1529
savefig(plt, "$folder/result_stat$name.svg")
1630
savefig(qual_hist, "$folder/result_qual$name.svg")
17-
end
31+
end

docs/src/index.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,50 @@
11
# DistMesh.jl
22

3+
4+
DistMesh.jl implements simplex refinement on signed distance functions, or anything that
5+
has a sign, distance, and called like a function. The algorithm was first presented
6+
in 2004 by Per-Olof Persson, and was initially a port of the corresponding Matlab Code.
7+
8+
## What is Simplex Refinement?
9+
10+
In layman's terms, a simplex is either a triangle in the 2D case, or a tetrahedra in the 3D case.
11+
12+
When simulating, you other want a few things from a mesh of simplices:
13+
- Accurate approximation of boundaries and features
14+
- Adaptive mesh sizes to improve accuracy
15+
- Near-Regular Simplicies
16+
17+
DistMesh is designed to address the above.
18+
19+
## Algorithm Overview
20+
21+
The basic processes is as follows:
22+
23+
24+
25+
## Comparison to other refinements
26+
27+
DistMesh generally has a very low memory footprint, and can refine without additional
28+
memory allocation. Similarly, since the global state of simplex qualities is accounted for
29+
in each refinement iteration, this leads to very high quality meshes.
30+
31+
Aside from the above, since DistMesh works on signed distance functions it can handle
32+
complex and varied input data that are not in the form of surface meshes (Piecewise Linear Complicies).
33+
34+
## Difference from the MatLab implementation
35+
36+
Given the same parameters, the Julia implementation of DistMesh will generally perform
37+
4-60 times faster than the MatLab implementation. Delaunay Triangulation in MatLab uses
38+
QHull, whereas DistMesh.jl uses TetGen.
39+
40+
## How do I get a Signed Distance Function?
41+
42+
Here are some libraries that turn gridded and level set data into an approximate signed
43+
distance function:
44+
45+
- Interpolations.jl
46+
- AdaptiveDistanceFields.jl
47+
348
```@index
449
```
550

src/DistMesh.jl

Lines changed: 27 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ abstract type AbstractDistMeshAlgorithm end
1313
"""
1414
DistMeshSetup
1515
16-
Takes Keyword arguments as follows:
16+
Takes Keyword arguments as follows:
1717
1818
iso (default: 0): Value of which to extract the isosurface, inside surface is negative
1919
deltat (default: 0.1): the fraction of edge displacement to apply each iteration
@@ -54,27 +54,39 @@ end
5454
"""
5555
DistMeshQuality
5656
57+
Use Tetrahedral quality analysis to control the meshing process
58+
5759
iso (default: 0): Value of which to extract the iso surface, inside negative
5860
deltat (default: 0.1): the fraction of edge displacement to apply each iteration
5961
"""
6062
struct DistMeshQuality{T} <: AbstractDistMeshAlgorithm
6163
iso::T
6264
deltat::T
63-
minimum::T
65+
filter_less_than::T # Remove tets less than the given quality
66+
#allow_n_regressions::Int # Might want this
67+
termination_quality::T # Once achieved, terminate
68+
sort::Bool # use hilbert sort to cache-localize points
69+
sort_interval::Int # retriangulations before resorting
70+
nonlinear::Bool # uses nonlinear edge force
6471
distribution::Symbol # intial point distribution
6572
end
6673

6774
function DistMeshQuality(;iso=0,
68-
ptol=.001,
6975
deltat=0.05,
70-
ttol=0.02,
76+
filter_less_than=0.02,
77+
termination_quality=0.3,
78+
sort=false,
79+
sort_interval=20,
80+
nonlinear=true,
7181
distribution=:regular)
72-
T = promote_type(typeof(iso),typeof(ptol),typeof(deltat), typeof(ttol))
73-
DistMeshQuality{T}(iso,
74-
deltat,
75-
ttol,
76-
ptol,
77-
distribution)
82+
DistMeshQuality(iso,
83+
deltat,
84+
filter_less_than,
85+
termination_quality,
86+
sort,
87+
sort_interval,
88+
nonlinear,
89+
distribution)
7890
end
7991

8092
"""
@@ -85,21 +97,20 @@ end
8597
struct DistMeshStatistics{T}
8698
maxmove::Vector{T} # max point move in an iteration
8799
maxdp::Vector{T} # max displacmeent induced by an edge
88-
average_qual::Vector{T}
89-
median_qual::Vector{T}
90-
minimum_qual::Vector{T}
91-
maximum_qual::Vector{T}
100+
min_volume_edge_ratio::Vector{T}
101+
max_volume_edge_ratio::Vector{T}
102+
average_volume_edge_ratio::Vector{T}
92103
retriangulations::Vector{Int} # Iteration num where retriangulation occured
93104
end
94105

95-
DistMeshStatistics() = DistMeshStatistics{Float64}([],[],[],[],[],[],[])
106+
DistMeshStatistics() = DistMeshStatistics{Float64}([],[],[],[],[],[])
96107

97108
"""
98109
Uniform edge length function.
99110
"""
100111
struct HUniform end
101112

102-
include("common.jl")
113+
103114
include("diff.jl")
104115
include("pointdistribution.jl")
105116
include("distmeshnd.jl")

src/common.jl

Lines changed: 0 additions & 47 deletions
This file was deleted.

src/distmeshnd.jl

Lines changed: 56 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,15 +73,10 @@ function distmesh(fdist::Function,
7373
p = VertType[]
7474
end
7575

76-
pt_dists = map(fdist, p) # cache to store point locations so we can minimize fdist calls
77-
78-
# add points to p based on the initial distribution
79-
if setup.distribution === :regular
80-
simplecubic!(fdist, p, pt_dists, h, setup.iso, origin, widths, VertType)
81-
elseif setup.distribution === :packed
82-
# face-centered cubic point distribution
83-
facecenteredcubic!(fdist, p, pt_dists, h, setup.iso, origin, widths, VertType)
84-
end
76+
pt_dists = eltype(VertType)[]
77+
78+
# setup the initial point distribution specified in setup
79+
point_distribution!(fdist,p,pt_dists,h, setup, origin, widths, VertType)
8580

8681
# Result struct for holding points, simplices, and iteration statistics
8782
result = DistMeshResult(p,
@@ -97,11 +92,6 @@ function distmesh(fdist::Function,
9792
L0 = non_uniform ? eltype(VertType)[] : nothing # desired edge length computed by dh (edge length function)
9893
maxmove = typemax(eltype(VertType)) # stores an iteration max movement for retriangulation
9994

100-
# arrays for tracking quality metrics
101-
tris = NTuple{3,Int32}[] # triangles used for quality checks
102-
triset = Set{NTuple{3,Int32}}() # set for triangles to ensure uniqueness
103-
qualities = eltype(VertType)[]
104-
10595
# information on each iteration
10696
lcount = 0 # iteration counter
10797
triangulationcount = 0 # triangulation counter
@@ -172,13 +162,10 @@ function distmesh(fdist::Function,
172162
if stats
173163
push!(result.stats.maxmove,maxmove)
174164
push!(result.stats.maxdp,maxdp)
175-
triangle_qualities!(tris,triset,qualities,result.points,result.tetrahedra)
176-
sort!(qualities) # sort for median calc and robust summation
177-
mine, maxe = extrema(qualities)
178-
push!(result.stats.average_qual, sum(qualities)/length(qualities))
179-
push!(result.stats.median_qual, qualities[round(Int,length(qualities)/2)])
180-
push!(result.stats.minimum_qual, mine)
181-
push!(result.stats.maximum_qual, maxe)
165+
min_v_edge, avg_v_edge, max_v_edge = volume_edge_stats(result.points,result.tetrahedra)
166+
push!(result.stats.min_volume_edge_ratio, min_v_edge)
167+
push!(result.stats.average_volume_edge_ratio, avg_v_edge)
168+
push!(result.stats.max_volume_edge_ratio, max_v_edge)
182169
end
183170

184171
# Termination criterion
@@ -226,3 +213,51 @@ function retriangulate!(fdist, result::DistMeshResult, geps, setup, triangulatio
226213
j <= lastindex(t) && resize!(t, j-1)
227214
nothing
228215
end
216+
217+
218+
function compute_displacements!(fh, dp, pair, L, L0, bars, p, setup,
219+
::Type{VertType}) where {VertType}
220+
221+
non_uniform = isa(typeof(L0), AbstractVector)
222+
223+
# compute edge lengths (L) and adaptive edge lengths (L0)
224+
# Lp norm (p=3) is partially computed here
225+
Lsum = zero(eltype(L))
226+
L0sum = non_uniform ? zero(eltype(L0)) : length(pair)
227+
for i in eachindex(pair)
228+
pb = pair[i]
229+
b1 = p[pb[1]]
230+
b2 = p[pb[2]]
231+
barvec = b1 - b2 # bar vector
232+
bars[i] = barvec
233+
L[i] = sqrt(sum(barvec.^2)) # length
234+
non_uniform && (L0[i] = fh((b1+b2)./2))
235+
Lsum = Lsum + L[i].^3
236+
non_uniform && (L0sum = L0sum + L0[i].^3)
237+
end
238+
239+
# zero out force at each node
240+
for i in eachindex(dp)
241+
dp[i] = zero(VertType)
242+
end
243+
244+
# this is not hoisted correctly in the loop so we initialize here
245+
# finish computing the Lp norm (p=3)
246+
lscbrt = (1+(0.4/2^2))*cbrt(Lsum/L0sum)
247+
248+
# Move mesh points based on edge lengths L and forces F
249+
for i in eachindex(pair)
250+
if non_uniform && L[i] < L0[i]*lscbrt || L[i] < lscbrt
251+
L0_f = non_uniform ? L0[i].*lscbrt : lscbrt
252+
# compute force vectors
253+
F = setup.nonlinear ? (L[i]+L0_f)*(L0_f-L[i])/(2*L0_f) : L0_f-L[i]
254+
# edges are not allowed to pull, only repel
255+
FBar = bars[i].*F./L[i]
256+
# add the force vector to the node
257+
b1 = pair[i][1]
258+
b2 = pair[i][2]
259+
dp[b1] = dp[b1] .+ FBar
260+
dp[b2] = dp[b2] .- FBar
261+
end
262+
end
263+
end

0 commit comments

Comments
 (0)