bluemesh2d 0.1.1.dev0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- bluemesh2d/_version.py +24 -0
- bluemesh2d/aabb_tree/findball.py +121 -0
- bluemesh2d/aabb_tree/findtria.py +299 -0
- bluemesh2d/aabb_tree/maketree.py +147 -0
- bluemesh2d/aabb_tree/mapvert.py +72 -0
- bluemesh2d/aabb_tree/queryset.py +83 -0
- bluemesh2d/aabb_tree/scantree.py +124 -0
- bluemesh2d/dependencies.py +83 -0
- bluemesh2d/feedback.py +142 -0
- bluemesh2d/geom_util/boundary_util.py +216 -0
- bluemesh2d/geom_util/getiso.py +194 -0
- bluemesh2d/geom_util/poly_util.py +795 -0
- bluemesh2d/geom_util/proj_util.py +250 -0
- bluemesh2d/geomesh_util/border_util.py +283 -0
- bluemesh2d/geomesh_util/depth_field.py +333 -0
- bluemesh2d/geomesh_util/grd_util.py +1000 -0
- bluemesh2d/geomesh_util/interpolation_mesh.py +318 -0
- bluemesh2d/geomesh_util/merge_circumcenters.py +268 -0
- bluemesh2d/geomesh_util/water_polygon.py +406 -0
- bluemesh2d/hfun_util/build_hfun.py +589 -0
- bluemesh2d/hfun_util/hfun_dispersion.py +96 -0
- bluemesh2d/hfun_util/lfshfn.py +110 -0
- bluemesh2d/hfun_util/limhfn.py +65 -0
- bluemesh2d/hfun_util/make_constant_hfun.py +32 -0
- bluemesh2d/hfun_util/make_depth_hfun.py +51 -0
- bluemesh2d/hfun_util/smooth_and_precomput.py +188 -0
- bluemesh2d/hfun_util/trihfn.py +84 -0
- bluemesh2d/hjac_util/limgrad.py +105 -0
- bluemesh2d/mesh_ball/cdtbal1.py +38 -0
- bluemesh2d/mesh_ball/cdtbal2.py +104 -0
- bluemesh2d/mesh_ball/inv_2x2.py +61 -0
- bluemesh2d/mesh_ball/inv_3x3.py +72 -0
- bluemesh2d/mesh_ball/pwrbal2.py +134 -0
- bluemesh2d/mesh_ball/tribal2.py +27 -0
- bluemesh2d/mesh_cost/relhfn.py +62 -0
- bluemesh2d/mesh_cost/triang.py +59 -0
- bluemesh2d/mesh_cost/triarea.py +51 -0
- bluemesh2d/mesh_cost/trideg.py +44 -0
- bluemesh2d/mesh_cost/triscr.py +43 -0
- bluemesh2d/mesh_file/bnd_util.py +664 -0
- bluemesh2d/mesh_file/loadmsh.py +165 -0
- bluemesh2d/mesh_file/ugrid.py +308 -0
- bluemesh2d/mesh_util/cfmtri.py +118 -0
- bluemesh2d/mesh_util/deltri.py +131 -0
- bluemesh2d/mesh_util/idxtri.py +53 -0
- bluemesh2d/mesh_util/isfeat.py +90 -0
- bluemesh2d/mesh_util/minlen.py +46 -0
- bluemesh2d/mesh_util/setset.py +49 -0
- bluemesh2d/mesh_util/tricon.py +90 -0
- bluemesh2d/mesh_util/tridiv.py +187 -0
- bluemesh2d/meshgen.py +202 -0
- bluemesh2d/ortho_merge/constants.py +27 -0
- bluemesh2d/ortho_merge/geometry.py +64 -0
- bluemesh2d/ortho_merge/ortho_merge_iter.py +812 -0
- bluemesh2d/ortho_merge/orthogonalize.py +2989 -0
- bluemesh2d/pipeline.py +277 -0
- bluemesh2d/poly_data/airfoil.msh +958 -0
- bluemesh2d/poly_data/channel.msh +212 -0
- bluemesh2d/poly_data/islands.msh +13819 -0
- bluemesh2d/poly_data/lake.msh +612 -0
- bluemesh2d/poly_data/river.msh +690 -0
- bluemesh2d/poly_test/inpoly.py +105 -0
- bluemesh2d/poly_test/inpoly_mat.py +104 -0
- bluemesh2d/refine.py +972 -0
- bluemesh2d/smood.py +623 -0
- bluemesh2d/smooth.py +522 -0
- bluemesh2d/tricost.py +426 -0
- bluemesh2d/tridemo.py +723 -0
- bluemesh2d/triread.py +53 -0
- bluemesh2d-0.1.1.dev0.dist-info/METADATA +139 -0
- bluemesh2d-0.1.1.dev0.dist-info/RECORD +74 -0
- bluemesh2d-0.1.1.dev0.dist-info/WHEEL +5 -0
- bluemesh2d-0.1.1.dev0.dist-info/licenses/LICENSE +674 -0
- bluemesh2d-0.1.1.dev0.dist-info/top_level.txt +1 -0
bluemesh2d/smooth.py
ADDED
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import time
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
from scipy.sparse import csr_matrix
|
|
5
|
+
|
|
6
|
+
from .mesh_cost.triscr import triscr
|
|
7
|
+
from .mesh_util.deltri import deltri
|
|
8
|
+
from .mesh_util.setset import setset
|
|
9
|
+
from .mesh_util.tricon import tricon
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def smooth(
|
|
13
|
+
vert=None, conn=None, tria=None, tnum=None, opts=None, hfun=None, harg=[],
|
|
14
|
+
fixed=None,
|
|
15
|
+
):
|
|
16
|
+
"""Perform hill-climbing mesh smoothing for 2D triangle meshes.
|
|
17
|
+
|
|
18
|
+
Optimize vertex positions and local topology via a spring-based update
|
|
19
|
+
with monotonic quality guarantees.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
vert : ndarray of shape (V, 2), optional
|
|
24
|
+
Vertex coordinates.
|
|
25
|
+
conn : ndarray of shape (E, 2), optional
|
|
26
|
+
Constrained edges.
|
|
27
|
+
tria : ndarray of shape (T, 3), optional
|
|
28
|
+
Triangle connectivity (vertex indices).
|
|
29
|
+
tnum : ndarray of shape (T, 1), optional
|
|
30
|
+
Part index per triangle.
|
|
31
|
+
opts : dict, optional
|
|
32
|
+
Smoothing options (defaults via :func:`makeopt`):
|
|
33
|
+
|
|
34
|
+
- ``vtol`` : float, default ``1.0e-2`` — relative vertex movement tolerance
|
|
35
|
+
- ``iter`` : int, default ``32`` — maximum iterations
|
|
36
|
+
- ``disp`` : int or float, default ``4`` — progress interval; ``np.inf`` for quiet
|
|
37
|
+
hfun : callable, optional
|
|
38
|
+
Mesh-size function for local edge-length control.
|
|
39
|
+
harg : tuple, optional
|
|
40
|
+
Extra arguments passed to ``hfun``.
|
|
41
|
+
fixed : array_like of int, optional
|
|
42
|
+
Indices (into ``vert``) of vertices to hold fixed: they are never
|
|
43
|
+
moved, never removed, and edges incident to them are never
|
|
44
|
+
collapsed or split.
|
|
45
|
+
|
|
46
|
+
Returns
|
|
47
|
+
-------
|
|
48
|
+
vert : ndarray of shape (V, 2)
|
|
49
|
+
Smoothed vertex coordinates.
|
|
50
|
+
conn : ndarray of shape (E, 2)
|
|
51
|
+
Updated constrained edges.
|
|
52
|
+
tria : ndarray of shape (T, 3)
|
|
53
|
+
Updated triangle connectivity.
|
|
54
|
+
tnum : ndarray of shape (T, 1)
|
|
55
|
+
Updated part indices.
|
|
56
|
+
|
|
57
|
+
Notes
|
|
58
|
+
-----
|
|
59
|
+
Loosely based on the DISTMESH spring analogy with additional hill-climbing
|
|
60
|
+
quality and density control. See P.-O. Persson and G. Strang (2004),
|
|
61
|
+
*A Simple Mesh Generator in MATLAB*, SIAM Review 46(2): 329–345.
|
|
62
|
+
|
|
63
|
+
References
|
|
64
|
+
----------
|
|
65
|
+
Translation of the MESH2D function ``SMOOTH2``.
|
|
66
|
+
Original MATLAB source: https://github.com/dengwirda/mesh2d
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
if vert is None:
|
|
70
|
+
vert = np.empty((0, 2))
|
|
71
|
+
if conn is None:
|
|
72
|
+
conn = np.empty((0, 2), dtype=int)
|
|
73
|
+
if tria is None:
|
|
74
|
+
tria = np.empty((0, 3), dtype=int)
|
|
75
|
+
if tnum is None:
|
|
76
|
+
tnum = np.empty((0, 1), dtype=int)
|
|
77
|
+
if opts is None:
|
|
78
|
+
opts = {}
|
|
79
|
+
|
|
80
|
+
opts = makeopt(opts)
|
|
81
|
+
|
|
82
|
+
if conn.size == 0:
|
|
83
|
+
edge, _ = tricon(tria)
|
|
84
|
+
ebnd = edge[:, 3] < 1 # use boundary edge
|
|
85
|
+
conn = edge[ebnd, 0:2]
|
|
86
|
+
|
|
87
|
+
if tnum.size == 0:
|
|
88
|
+
tnum = np.ones((tria.shape[0], 1), dtype=int)
|
|
89
|
+
|
|
90
|
+
if not (
|
|
91
|
+
isinstance(vert, np.ndarray)
|
|
92
|
+
and isinstance(conn, np.ndarray)
|
|
93
|
+
and isinstance(tria, np.ndarray)
|
|
94
|
+
and isinstance(tnum, np.ndarray)
|
|
95
|
+
and isinstance(opts, dict)
|
|
96
|
+
):
|
|
97
|
+
raise TypeError("smooth:incorrectInputClass - Incorrect input class.")
|
|
98
|
+
|
|
99
|
+
nvrt = vert.shape[0]
|
|
100
|
+
|
|
101
|
+
if fixed is None:
|
|
102
|
+
fixed = np.empty((0,), dtype=int)
|
|
103
|
+
else:
|
|
104
|
+
fixed = np.unique(np.asarray(fixed, dtype=int).ravel())
|
|
105
|
+
if fixed.size and (fixed.min() < 0 or fixed.max() >= nvrt):
|
|
106
|
+
raise ValueError("smooth:invalidInputs - Invalid FIXED input array.")
|
|
107
|
+
|
|
108
|
+
if np.min(conn[:, :2]) < 0 or np.max(conn[:, :2]) > nvrt:
|
|
109
|
+
raise ValueError("smooth:invalidInputs - Invalid EDGE input array.")
|
|
110
|
+
|
|
111
|
+
if np.min(tria[:, :3]) < 0 or np.max(tria[:, :3]) > nvrt:
|
|
112
|
+
raise ValueError("smooth:invalidInputs - Invalid TRIA input array.")
|
|
113
|
+
|
|
114
|
+
if not np.isinf(opts["disp"]):
|
|
115
|
+
print("\n Smooth triangulation...\n")
|
|
116
|
+
print(" -------------------------------------------------------")
|
|
117
|
+
print(" |ITER.| |MOVE(X)| |DTRI(X)| ")
|
|
118
|
+
print(" -------------------------------------------------------")
|
|
119
|
+
|
|
120
|
+
node = vert.copy()
|
|
121
|
+
PSLG = conn.copy()
|
|
122
|
+
pmax = int(np.max(tnum))
|
|
123
|
+
part = [None for _ in range(pmax)]
|
|
124
|
+
|
|
125
|
+
for ppos in range(pmax):
|
|
126
|
+
tsel = tnum.flatten() == (ppos + 1)
|
|
127
|
+
tcur = tria[tsel, :]
|
|
128
|
+
ecur, tcur = tricon(tcur)
|
|
129
|
+
ebnd = ecur[:, 3] == -1
|
|
130
|
+
same, _ = setset(PSLG, ecur[ebnd, 0:2])
|
|
131
|
+
part[ppos] = np.where(same)[0]
|
|
132
|
+
|
|
133
|
+
vmin = np.min(vert, axis=0)
|
|
134
|
+
vmax = np.max(vert, axis=0)
|
|
135
|
+
|
|
136
|
+
vdel = vmax - 1.0 * vmin
|
|
137
|
+
vmin = vmin - 0.5 * vdel
|
|
138
|
+
vmax = vmax + 0.5 * vdel
|
|
139
|
+
|
|
140
|
+
vbox = np.array(
|
|
141
|
+
[
|
|
142
|
+
[vmin[0], vmin[1]],
|
|
143
|
+
[vmax[0], vmin[1]],
|
|
144
|
+
[vmax[0], vmax[1]],
|
|
145
|
+
[vmin[0], vmax[1]],
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
vert = np.vstack((vert, vbox))
|
|
150
|
+
|
|
151
|
+
tnow = time.time()
|
|
152
|
+
tcpu = {
|
|
153
|
+
"full": 0.0,
|
|
154
|
+
"dtri": 0.0,
|
|
155
|
+
"tcon": 0.0,
|
|
156
|
+
"iter": 0.0,
|
|
157
|
+
"undo": 0.0,
|
|
158
|
+
"keep": 0.0,
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for iter in range(int(opts["iter"])):
|
|
162
|
+
ttic = time.time()
|
|
163
|
+
edge, tria = tricon(tria, conn)
|
|
164
|
+
tcpu["tcon"] += time.time() - ttic
|
|
165
|
+
|
|
166
|
+
oscr = triscr(vert, tria)
|
|
167
|
+
|
|
168
|
+
ttic = time.time()
|
|
169
|
+
nvrt = vert.shape[0]
|
|
170
|
+
nedg = edge.shape[0]
|
|
171
|
+
|
|
172
|
+
IMAT = csr_matrix(
|
|
173
|
+
(np.ones(nedg), (edge[:, 0], np.arange(nedg))), shape=(nvrt, nedg)
|
|
174
|
+
)
|
|
175
|
+
JMAT = csr_matrix(
|
|
176
|
+
(np.ones(nedg), (edge[:, 1], np.arange(nedg))), shape=(nvrt, nedg)
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
EMAT = IMAT + JMAT
|
|
180
|
+
vdeg = np.array(EMAT.sum(axis=1)).flatten() # vertex |deg|
|
|
181
|
+
free = vdeg == 0
|
|
182
|
+
|
|
183
|
+
vold = vert.copy()
|
|
184
|
+
|
|
185
|
+
# Local iterations
|
|
186
|
+
for isub in range(max(1, min(8, iter))):
|
|
187
|
+
# compute HFUN at vert/midpoints
|
|
188
|
+
hvrt = evalhfn(vert, edge, EMAT, hfun, harg)
|
|
189
|
+
hmid = 0.5 * (hvrt[edge[:, 0]] + hvrt[edge[:, 1]])
|
|
190
|
+
# calc. relative edge extensions
|
|
191
|
+
evec = vert[edge[:, 1], :] - vert[edge[:, 0], :]
|
|
192
|
+
elen = np.sqrt(np.sum(evec**2, axis=1))
|
|
193
|
+
|
|
194
|
+
scal = 1.0 - elen / hmid
|
|
195
|
+
scal = np.clip(scal, -1.0, 1.0)
|
|
196
|
+
# projected points from each end
|
|
197
|
+
ipos = vert[edge[:, 0], :] - 0.67 * (scal[:, None] * evec)
|
|
198
|
+
jpos = vert[edge[:, 1], :] + 0.67 * (scal[:, None] * evec)
|
|
199
|
+
scal = np.maximum(np.abs(scal) ** 1, np.finfo(float).eps ** 0.75)
|
|
200
|
+
# sum contributions edge-to-vert
|
|
201
|
+
vnew = IMAT.dot(scal[:, None] * ipos) + JMAT.dot(scal[:, None] * jpos)
|
|
202
|
+
vsum = np.maximum(EMAT.dot(scal), np.finfo(float).eps ** 0.75)
|
|
203
|
+
|
|
204
|
+
vnew = vnew / vsum[:, None]
|
|
205
|
+
vnew[conn.flatten(), :] = vert[conn.flatten(), :]
|
|
206
|
+
vnew[fixed, :] = vert[fixed, :]
|
|
207
|
+
vnew[vdeg == 0, :] = vert[vdeg == 0, :]
|
|
208
|
+
vert = vnew
|
|
209
|
+
|
|
210
|
+
tcpu["iter"] += time.time() - ttic
|
|
211
|
+
ttic = time.time()
|
|
212
|
+
|
|
213
|
+
# unwind vert. upadte if score lower
|
|
214
|
+
nscr = np.ones(tria.shape[0])
|
|
215
|
+
btri = np.ones(tria.shape[0], dtype=bool)
|
|
216
|
+
|
|
217
|
+
umax = 8
|
|
218
|
+
for undo in range(umax):
|
|
219
|
+
nscr[btri] = triscr(vert, tria[btri, :])
|
|
220
|
+
|
|
221
|
+
# TRUE if tria needs "unwinding"
|
|
222
|
+
smin = 0.70
|
|
223
|
+
smax = 0.90
|
|
224
|
+
sdel = 0.025
|
|
225
|
+
|
|
226
|
+
stol = smin + iter * sdel
|
|
227
|
+
stol = min(smax, stol)
|
|
228
|
+
|
|
229
|
+
btri = (nscr <= stol) & (nscr < oscr)
|
|
230
|
+
|
|
231
|
+
if not np.any(btri):
|
|
232
|
+
break
|
|
233
|
+
|
|
234
|
+
# relax toward old vert. coord's
|
|
235
|
+
ivrt = np.unique(tria[btri, :3])
|
|
236
|
+
bvrt = np.zeros(vert.shape[0], dtype=bool)
|
|
237
|
+
bvrt[ivrt] = True
|
|
238
|
+
|
|
239
|
+
if undo != umax:
|
|
240
|
+
bnew = 0.75**undo
|
|
241
|
+
bold = 1.0 - bnew
|
|
242
|
+
else:
|
|
243
|
+
bnew = 0.0
|
|
244
|
+
bold = 1.0 - bnew
|
|
245
|
+
|
|
246
|
+
vert[bvrt, :] = bold * vold[bvrt, :] + bnew * vert[bvrt, :]
|
|
247
|
+
|
|
248
|
+
btri = np.any(bvrt[tria[:, :3]], axis=1)
|
|
249
|
+
|
|
250
|
+
oscr = nscr
|
|
251
|
+
tcpu["undo"] += time.time() - ttic
|
|
252
|
+
|
|
253
|
+
ttic = time.time()
|
|
254
|
+
|
|
255
|
+
vdel = np.sum((vert - vold) ** 2, axis=1)
|
|
256
|
+
|
|
257
|
+
evec = vert[edge[:, 1], :] - vert[edge[:, 0], :]
|
|
258
|
+
elen = np.sqrt(np.sum(evec**2, axis=1))
|
|
259
|
+
|
|
260
|
+
hvrt = evalhfn(vert, edge, EMAT, hfun, harg)
|
|
261
|
+
hmid = 0.5 * (hvrt[edge[:, 0]] + hvrt[edge[:, 1]])
|
|
262
|
+
scal = elen / hmid
|
|
263
|
+
|
|
264
|
+
emid = 0.5 * (vert[edge[:, 0], :] + vert[edge[:, 1], :])
|
|
265
|
+
|
|
266
|
+
keep = np.zeros(vert.shape[0], dtype=bool)
|
|
267
|
+
keep[vdeg > 4] = True
|
|
268
|
+
keep[conn.flatten()] = True
|
|
269
|
+
keep[free.flatten()] = True
|
|
270
|
+
keep[fixed] = True
|
|
271
|
+
|
|
272
|
+
lmax = 5.0 / 4.0
|
|
273
|
+
lmin = 1.0 / lmax
|
|
274
|
+
|
|
275
|
+
less = scal <= lmin
|
|
276
|
+
more = scal >= lmax
|
|
277
|
+
|
|
278
|
+
vbnd = np.zeros(vert.shape[0], dtype=bool)
|
|
279
|
+
vbnd[conn[:, 0]] = True
|
|
280
|
+
vbnd[conn[:, 1]] = True
|
|
281
|
+
vbnd[fixed] = True
|
|
282
|
+
|
|
283
|
+
ebad = vbnd[edge[:, 0]] | vbnd[edge[:, 1]] # not at boundaries
|
|
284
|
+
|
|
285
|
+
less[ebad] = False
|
|
286
|
+
more[ebad] = False
|
|
287
|
+
|
|
288
|
+
lidx = np.where(less)[0]
|
|
289
|
+
for epos in lidx:
|
|
290
|
+
inod = edge[epos, 0]
|
|
291
|
+
jnod = edge[epos, 1]
|
|
292
|
+
if keep[inod] and keep[jnod]:
|
|
293
|
+
keep[inod] = False
|
|
294
|
+
keep[jnod] = False
|
|
295
|
+
else:
|
|
296
|
+
less[epos] = False
|
|
297
|
+
|
|
298
|
+
ebad = keep[edge[less, 0]] & keep[edge[less, 1]]
|
|
299
|
+
indices = np.flatnonzero(ebad)
|
|
300
|
+
indices = indices[indices < more.size]
|
|
301
|
+
more[indices] = False
|
|
302
|
+
# more[ebad] = False
|
|
303
|
+
|
|
304
|
+
redo = np.zeros(vert.shape[0], dtype=int)
|
|
305
|
+
itop = np.count_nonzero(keep)
|
|
306
|
+
iend = np.count_nonzero(less)
|
|
307
|
+
|
|
308
|
+
redo[keep] = np.arange(itop)
|
|
309
|
+
redo[edge[less, 0]] = np.arange(itop, itop + iend) # to new midpoints
|
|
310
|
+
redo[edge[less, 1]] = np.arange(itop, itop + iend)
|
|
311
|
+
|
|
312
|
+
vnew = np.vstack([vert[keep, :], emid[less, :]])
|
|
313
|
+
tnew = redo[tria[:, 0:3]]
|
|
314
|
+
|
|
315
|
+
ttmp = np.sort(tnew, axis=1) # filter collapsed
|
|
316
|
+
okay = np.all(np.diff(ttmp, axis=1) != 0, axis=1)
|
|
317
|
+
okay = okay & (ttmp[:, 0] > 0)
|
|
318
|
+
tnew = tnew[okay, :]
|
|
319
|
+
|
|
320
|
+
nscr = triscr(vnew, tnew)
|
|
321
|
+
|
|
322
|
+
stol = 0.80
|
|
323
|
+
tbad = (nscr < stol) & (nscr < oscr[okay])
|
|
324
|
+
|
|
325
|
+
vbad = np.zeros(vnew.shape[0], dtype=bool)
|
|
326
|
+
vbad[tnew[tbad, :]] = True
|
|
327
|
+
|
|
328
|
+
lidx = np.where(less)[0]
|
|
329
|
+
ebad = vbad[redo[edge[lidx, 0]]] | vbad[redo[edge[lidx, 1]]]
|
|
330
|
+
|
|
331
|
+
less[lidx[ebad]] = False
|
|
332
|
+
keep[edge[lidx[ebad], 0:2].flatten()] = True
|
|
333
|
+
|
|
334
|
+
redo = np.zeros(vert.shape[0], dtype=int)
|
|
335
|
+
itop = np.count_nonzero(keep)
|
|
336
|
+
iend = np.count_nonzero(less)
|
|
337
|
+
|
|
338
|
+
redo[keep] = np.arange(itop)
|
|
339
|
+
redo[edge[less, 0]] = np.arange(itop, itop + iend)
|
|
340
|
+
redo[edge[less, 1]] = np.arange(itop, itop + iend)
|
|
341
|
+
|
|
342
|
+
vert = np.vstack([vert[keep, :], emid[less, :], emid[more, :]])
|
|
343
|
+
conn = redo[conn]
|
|
344
|
+
fixed = redo[fixed]
|
|
345
|
+
|
|
346
|
+
tcpu["keep"] += time.time() - ttic
|
|
347
|
+
|
|
348
|
+
ttic = time.time()
|
|
349
|
+
vert, conn, tria, tnum = deltri(vert, conn, node, PSLG, part)
|
|
350
|
+
tcpu["dtri"] += time.time() - ttic
|
|
351
|
+
|
|
352
|
+
vdel = vdel / (hvrt.flatten() ** 2)
|
|
353
|
+
move = vdel > opts["vtol"] ** 2
|
|
354
|
+
|
|
355
|
+
nmov = np.count_nonzero(move)
|
|
356
|
+
|
|
357
|
+
ntri = tria.shape[0]
|
|
358
|
+
if iter % opts["disp"] == 0:
|
|
359
|
+
print(f"{iter:11d} {nmov:18d} {ntri:18d}")
|
|
360
|
+
|
|
361
|
+
if nmov == 0:
|
|
362
|
+
break
|
|
363
|
+
|
|
364
|
+
tria = tria[:, 0:3]
|
|
365
|
+
|
|
366
|
+
keep = np.zeros(vert.shape[0], dtype=bool)
|
|
367
|
+
keep[tria.flatten()] = True
|
|
368
|
+
keep[conn.flatten()] = True
|
|
369
|
+
|
|
370
|
+
redo = np.zeros(vert.shape[0], dtype=int)
|
|
371
|
+
redo[keep] = np.arange(np.count_nonzero(keep))
|
|
372
|
+
|
|
373
|
+
conn = redo[conn]
|
|
374
|
+
tria = redo[tria]
|
|
375
|
+
|
|
376
|
+
vert = vert[keep, :]
|
|
377
|
+
|
|
378
|
+
tcpu["full"] += time.time() - tnow
|
|
379
|
+
|
|
380
|
+
if opts["dbug"]:
|
|
381
|
+
print("\n Mesh smoothing timer...\n")
|
|
382
|
+
print(f" FULL: {tcpu['full']:.6f}")
|
|
383
|
+
print(f" DTRI: {tcpu['dtri']:.6f}")
|
|
384
|
+
print(f" TCON: {tcpu['tcon']:.6f}")
|
|
385
|
+
print(f" ITER: {tcpu['iter']:.6f}")
|
|
386
|
+
print(f" UNDO: {tcpu['undo']:.6f}")
|
|
387
|
+
print(f" KEEP: {tcpu['keep']:.6f}\n")
|
|
388
|
+
|
|
389
|
+
if not np.isinf(opts["disp"]):
|
|
390
|
+
print("")
|
|
391
|
+
|
|
392
|
+
return vert, conn, tria, tnum
|
|
393
|
+
|
|
394
|
+
|
|
395
|
+
def evalhfn(vert, edge, EMAT, hfun=None, harg=[]):
|
|
396
|
+
"""Evaluate the mesh-size function at mesh vertices.
|
|
397
|
+
|
|
398
|
+
Parameters
|
|
399
|
+
----------
|
|
400
|
+
vert : ndarray of shape (N, 2)
|
|
401
|
+
Vertex coordinates.
|
|
402
|
+
edge : ndarray of shape (E, 2)
|
|
403
|
+
Edge connectivity.
|
|
404
|
+
EMAT : scipy.sparse matrix
|
|
405
|
+
Vertex–edge incidence matrix.
|
|
406
|
+
hfun : float, callable, or None, optional
|
|
407
|
+
Mesh-size function or constant spacing.
|
|
408
|
+
harg : tuple, optional
|
|
409
|
+
Extra arguments passed to ``hfun``.
|
|
410
|
+
|
|
411
|
+
Returns
|
|
412
|
+
-------
|
|
413
|
+
hvrt : ndarray of shape (N,)
|
|
414
|
+
Mesh-size value at each vertex.
|
|
415
|
+
|
|
416
|
+
References
|
|
417
|
+
----------
|
|
418
|
+
Translation of the MESH2D function ``EVALHFN``.
|
|
419
|
+
Original MATLAB source: https://github.com/dengwirda/mesh2d
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
# Default: mean edge length at each vertex (used when no hfun or as fallback)
|
|
423
|
+
evec = vert[edge[:, 1], :] - vert[edge[:, 0], :]
|
|
424
|
+
elen = np.sqrt(np.sum(evec**2, axis=1))
|
|
425
|
+
default_hvrt = np.ravel(EMAT @ elen) / np.maximum(
|
|
426
|
+
np.ravel(np.sum(EMAT, axis=1)), np.finfo(float).eps
|
|
427
|
+
)
|
|
428
|
+
free = np.ones(vert.shape[0], dtype=bool)
|
|
429
|
+
free[edge[:, 0]] = False
|
|
430
|
+
free[edge[:, 1]] = False
|
|
431
|
+
default_hvrt[free] = np.inf
|
|
432
|
+
|
|
433
|
+
if hfun is not None and (np.isscalar(hfun) or callable(hfun)):
|
|
434
|
+
if np.isscalar(hfun):
|
|
435
|
+
hvrt = hfun * np.ones(vert.shape[0])
|
|
436
|
+
else:
|
|
437
|
+
try:
|
|
438
|
+
hvrt = np.asarray(hfun(vert, *harg)).flatten()
|
|
439
|
+
except Exception:
|
|
440
|
+
# hfun may fail on out-of-domain vertices (e.g. bbox) -> fall back to default
|
|
441
|
+
hvrt = default_hvrt.copy()
|
|
442
|
+
else:
|
|
443
|
+
if hvrt.size != vert.shape[0]:
|
|
444
|
+
raise ValueError(
|
|
445
|
+
"smooth:evalhfn - hfun must return one value per vertex, "
|
|
446
|
+
f"got size {hvrt.size} for {vert.shape[0]} vertices."
|
|
447
|
+
)
|
|
448
|
+
# out-of-domain vertices (e.g. bbox) may yield NaN/inf: fall back to default
|
|
449
|
+
bad = ~np.isfinite(hvrt) | (hvrt <= 0)
|
|
450
|
+
hvrt[bad] = default_hvrt[bad]
|
|
451
|
+
else:
|
|
452
|
+
hvrt = default_hvrt.copy()
|
|
453
|
+
|
|
454
|
+
return hvrt
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def makeopt(opts=None):
|
|
458
|
+
"""Set up and validate the options dictionary for :func:`smooth`.
|
|
459
|
+
|
|
460
|
+
Parameters
|
|
461
|
+
----------
|
|
462
|
+
opts : dict or None, optional
|
|
463
|
+
User options; if ``None``, start from an empty dict.
|
|
464
|
+
|
|
465
|
+
Returns
|
|
466
|
+
-------
|
|
467
|
+
opts : dict
|
|
468
|
+
Validated options dictionary.
|
|
469
|
+
|
|
470
|
+
References
|
|
471
|
+
----------
|
|
472
|
+
Translation of the MESH2D function ``MAKEOPT``.
|
|
473
|
+
Original MATLAB source: https://github.com/dengwirda/mesh2d
|
|
474
|
+
"""
|
|
475
|
+
|
|
476
|
+
if opts is None:
|
|
477
|
+
opts = {}
|
|
478
|
+
|
|
479
|
+
if "iter" not in opts:
|
|
480
|
+
opts["iter"] = 32
|
|
481
|
+
else:
|
|
482
|
+
if not isinstance(opts["iter"], (int, float)):
|
|
483
|
+
raise TypeError("smooth:incorrectInputClass - Incorrect input class.")
|
|
484
|
+
if not (
|
|
485
|
+
isinstance(opts["iter"], (int, float))
|
|
486
|
+
and not isinstance(opts["iter"], bool)
|
|
487
|
+
):
|
|
488
|
+
raise TypeError("smooth:incorrectInputClass - Incorrect input class.")
|
|
489
|
+
if isinstance(opts["iter"], (list, tuple)) or hasattr(opts["iter"], "__len__"):
|
|
490
|
+
raise ValueError("smooth:incorrectDimensions - Incorrect input dimensions.")
|
|
491
|
+
if opts["iter"] <= 0:
|
|
492
|
+
raise ValueError("smooth:invalidOptionValues - Invalid OPT.ITER selection.")
|
|
493
|
+
|
|
494
|
+
if "disp" not in opts:
|
|
495
|
+
opts["disp"] = 4
|
|
496
|
+
else:
|
|
497
|
+
if not isinstance(opts["disp"], (int, float)):
|
|
498
|
+
raise TypeError("smooth:incorrectInputClass - Incorrect input class.")
|
|
499
|
+
if isinstance(opts["disp"], (list, tuple)) or hasattr(opts["disp"], "__len__"):
|
|
500
|
+
raise ValueError("smooth:incorrectDimensions - Incorrect input dimensions.")
|
|
501
|
+
if opts["disp"] <= 0:
|
|
502
|
+
raise ValueError("smooth:invalidOptionValues - Invalid OPT.DISP selection.")
|
|
503
|
+
|
|
504
|
+
if "vtol" not in opts:
|
|
505
|
+
opts["vtol"] = 1.0e-2
|
|
506
|
+
else:
|
|
507
|
+
if not isinstance(opts["vtol"], (int, float)):
|
|
508
|
+
raise TypeError("smooth:incorrectInputClass - Incorrect input class.")
|
|
509
|
+
if isinstance(opts["vtol"], (list, tuple)) or hasattr(opts["vtol"], "__len__"):
|
|
510
|
+
raise ValueError("smooth:incorrectDimensions - Incorrect input dimensions.")
|
|
511
|
+
if opts["vtol"] <= 0:
|
|
512
|
+
raise ValueError("smooth:invalidOptionValues - Invalid OPT.VTOL selection.")
|
|
513
|
+
|
|
514
|
+
if "dbug" not in opts:
|
|
515
|
+
opts["dbug"] = False
|
|
516
|
+
else:
|
|
517
|
+
if not isinstance(opts["dbug"], bool):
|
|
518
|
+
raise TypeError("refine:incorrectInputClass - Incorrect input class.")
|
|
519
|
+
if isinstance(opts["dbug"], (list, tuple)) or hasattr(opts["dbug"], "__len__"):
|
|
520
|
+
raise ValueError("refine:incorrectDimensions - Incorrect input dimensions.")
|
|
521
|
+
|
|
522
|
+
return opts
|